From 6d4d38ea85b6225f4b7abdf862aafb4e90d44e48 Mon Sep 17 00:00:00 2001 From: lihua <531456470@qq.com> Date: Wed, 20 Nov 2019 17:57:31 +0800 Subject: [PATCH] init --- .gitignore | 2 +- LoRaDTUConf.json | 12 + LoRaDTUMock.go | 838 +++++++++++++++++++++++++++++++++++ LoRaDTUMock.json | 9 + LoRaDTUMock.manifest | 15 + build.bat | 3 + go.mod | 14 + go.sum | 595 +++++++++++++++++++++++++ model/down.go | 107 +++++ model/up.go | 119 +++++ packets/packets.go | 126 ++++++ packets/packettype_string.go | 16 + packets/pull_ack.go | 38 ++ packets/pull_data.go | 43 ++ packets/pull_resp.go | 162 +++++++ packets/push_ack.go | 39 ++ packets/push_data.go | 283 ++++++++++++ packets/tx_ack.go | 73 +++ rsrc.syso | Bin 0 -> 68863 bytes syso.bat | 1 + 20 files changed, 2494 insertions(+), 1 deletion(-) create mode 100644 LoRaDTUConf.json create mode 100644 LoRaDTUMock.go create mode 100644 LoRaDTUMock.json create mode 100644 LoRaDTUMock.manifest create mode 100644 build.bat create mode 100644 go.mod create mode 100644 go.sum create mode 100644 model/down.go create mode 100644 model/up.go create mode 100644 packets/packets.go create mode 100644 packets/packettype_string.go create mode 100644 packets/pull_ack.go create mode 100644 packets/pull_data.go create mode 100644 packets/pull_resp.go create mode 100644 packets/push_ack.go create mode 100644 packets/push_data.go create mode 100644 packets/tx_ack.go create mode 100644 rsrc.syso create mode 100644 syso.bat diff --git a/.gitignore b/.gitignore index f3aa750..f32cc05 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,4 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser - +.idea diff --git a/LoRaDTUConf.json b/LoRaDTUConf.json new file mode 100644 index 0000000..e3c1e9e --- /dev/null +++ b/LoRaDTUConf.json @@ -0,0 +1,12 @@ +{ + "otaa": false, + "gatewayId": "0102030405060708", + "devEui": "6ef1881ba5541419", + "devAddr": "0c18699", + "appKey": "", + "appSKey": "33998df974421457437d986eff739ffa", + "nwkSKey": "33998df974421457437d986eff739ffa", + "fPort": 2, + "fCnt": 0, + "freq": 470.3 +} \ No newline at end of file diff --git a/LoRaDTUMock.go b/LoRaDTUMock.go new file mode 100644 index 0000000..68449a7 --- /dev/null +++ b/LoRaDTUMock.go @@ -0,0 +1,838 @@ +package main + +import ( + "LoRaDTUMock/model" + "LoRaDTUMock/packets" + "bytes" + "crypto/aes" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/brocaar/loraserver/api/gw" + "github.com/brocaar/lorawan" + paho "github.com/eclipse/paho.mqtt.golang" + "github.com/golang/protobuf/proto" + "github.com/lxn/walk" + . "github.com/lxn/walk/declarative" + "github.com/lxn/win" + "github.com/pkg/errors" + "io/ioutil" + "math/rand" + "os" + "syscall" + "time" +) + +type ConnectConfig struct { + Host string `json:"host"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + CACert string `json:"ca_cert"` + TLSCert string `json:"tls_cert"` + TLSKey string `json:"tls_key"` +} + +type DTUConfig struct { + OTAA bool `json:"otaa"` + GatewayId string `json:"gatewayId"` + DevEui string `json:"devEui"` + DevAddr string `json:"devAddr"` + AppKey string `json:"appKey"` + AppSKey string `json:"appSKey"` + NwkSKey string `json:"nwkSKey"` + FPort uint8 `json:"fPort"` + FCnt uint32 `json:"fCnt"` + Freq float64 `json:"freq"` + msg []byte + devNonce lorawan.DevNonce +} + +type DTUMainWindow struct { + *walk.MainWindow + mqttClient paho.Client + upModel *model.DTUUpModel + downModel *model.DTUDownModel + upTv,downTv *walk.TableView + host,username,password *walk.LineEdit + port *walk.NumberEdit + connect, disconnect,caConf,send *walk.PushButton + msg *walk.TextEdit + ssl *walk.CheckBox + connConf ConnectConfig + dtuConf DTUConfig + connConfFileName string + dtuConfFileName string +} +func BytesToString(b []byte) string { + _,err := syscall.UTF16FromString(string(b)) + if err == nil { + return string(b) + } + return "" +} +func main() { + mw := &DTUMainWindow{upModel: model.NewDTUUpModel(),downModel:model.NewDTUDownModel()} + maxWidth := int(win.GetSystemMetrics(win.SM_CXSCREEN)) - 200 + maxHeight := int(win.GetSystemMetrics(win.SM_CYSCREEN)) - 100 + err := MainWindow{ + AssignTo: &mw.MainWindow, + Title: "LoRaDTUMock", + //Icon: "test.ico", + Size: Size{maxWidth,maxHeight }, + Layout: VBox{}, + Children: []Widget{ + Composite{ + Layout: HBox{MarginsZero: true}, + Children: []Widget{ + Label{Text:"主机/IP:"}, + LineEdit{AssignTo:&mw.host}, + Label{Text:"端口:"}, + NumberEdit{AssignTo:&mw.port,MinSize:Size{Width:50}}, + Label{Text:"用户名:"}, + LineEdit{AssignTo:&mw.username}, + Label{Text:"密码:"}, + LineEdit{AssignTo:&mw.password,PasswordMode:true}, + PushButton{Text:"连接", AssignTo: &mw.connect,OnClicked:mw.Connect}, + PushButton{Text:"断开连接", AssignTo:&mw.disconnect, Enabled:false, OnClicked: mw.Disconnect}, + CheckBox{Text:"开启SSL/TLS",AssignTo:&mw.ssl,OnClicked:mw.SSL}, + PushButton{Text:"证书配置",Enabled:false,AssignTo:&mw.caConf,OnClicked: mw.ConnectConfig}, + PushButton{Text:"终端配置",OnClicked: mw.DTUConfig}, + PushButton{Text:"发送",AssignTo:&mw.send,OnClicked: mw.Send}, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + TableView{ + AssignTo: &mw.upTv, + CheckBoxes: true, + ColumnsOrderable: true, + MultiSelection: true, + Columns: []TableViewColumn{ + {Title: "序号"}, + {Title: "终端EUI"}, + {Title: "网关ID"}, + {Title: "信号强度"}, + {Title: "信噪比"}, + {Title: "频率"}, + {Title: "计数"}, + {Title: "端口"}, + {Title: "HEX数据"}, + {Title: "ASCII数据"}, + {Title: "时间"}, + }, + Model: mw.upModel, + OnItemActivated: mw.upTvItemActivated, + }, + TableView{ + AssignTo: &mw.downTv, + CheckBoxes: true, + ColumnsOrderable: true, + MultiSelection: true, + Columns: []TableViewColumn{ + {Title: "序号"}, + {Title: "终端EUI"}, + {Title: "网关ID"}, + {Title: "频率"}, + {Title: "计数"}, + {Title: "端口"}, + {Title: "HEX数据"}, + {Title: "ASCII数据"}, + {Title: "时间"}, + }, + Model: mw.downModel, + OnItemActivated: mw.downTvItemActivated, + }, + }, + }, + }, + }.Create() + if err != nil { + panic("LoRaDTUMock窗口创建失败") + } + _ = mw.port.SetValue(1883) + dir,_ := os.Getwd() + mw.connConfFileName = dir + "/LoRaDTUMock.json" + mw.dtuConfFileName = dir + "/LoRaDTUConf.json" + data, err := ioutil.ReadFile(mw.connConfFileName) + if err == nil { + err = json.Unmarshal(data, &mw.connConf) + if err != nil { + msg := "配置文件格式错误:" + err.Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return + } + _ = mw.host.SetText(mw.connConf.Host) + _ = mw.port.SetValue(float64(mw.connConf.Port)) + _ = mw.username.SetText(mw.connConf.Username) + _ = mw.password.SetText(mw.connConf.Password) + } + mw.Run() +} + +func newTLSConfig(cafile, certFile, certKeyFile string) (*tls.Config, error) { + if cafile == "" && certFile == "" && certKeyFile == "" { + return nil, nil + } + + tlsConfig := &tls.Config{} + + // Import trusted certificates from CAfile.pem. + if cafile != "" { + cacert, err := ioutil.ReadFile(cafile) + if err != nil { + return nil, err + } + certpool := x509.NewCertPool() + certpool.AppendCertsFromPEM(cacert) + + tlsConfig.RootCAs = certpool // RootCAs = certs used to verify server cert. + } + + // Import certificate and the key + if certFile != "" && certKeyFile != "" { + kp, err := tls.LoadX509KeyPair(certFile, certKeyFile) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{kp} + } + + return tlsConfig, nil +} + +func (mw *DTUMainWindow) Connect() { + go func() { + if mw.host.Text() != "" && mw.port.Value() > 0 { + opts := paho.NewClientOptions() + serverAddr := fmt.Sprintf("%s:%d",mw.host.Text(),int(mw.port.Value())) + if mw.ssl.Checked() { + if mw.connConf.CACert != "" { + serverAddr = fmt.Sprintf("ssl://%s:%d",mw.host.Text(),int(mw.port.Value())) + tlsconfig, err := newTLSConfig(mw.connConf.CACert, mw.connConf.TLSCert, mw.connConf.TLSKey) + if err != nil { + msg := "证书加载错误:" + err.Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + } + if tlsconfig != nil { + opts.SetTLSConfig(tlsconfig) + } + }else{ + msg := "证书未配置,服务端单向认证只需配置CA证书,双向认证还需配置客户端证书及秘钥" + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return + } + } + opts.AddBroker(serverAddr) + opts.SetUsername(mw.username.Text()) + opts.SetPassword(mw.password.Text()) + opts.SetConnectTimeout(5 * time.Second) + mw.mqttClient = paho.NewClient(opts) + token := mw.mqttClient.Connect() + if token.Wait() && token.Error() == nil { + mw.connConf.Host = mw.host.Text() + mw.connConf.Port = int(mw.port.Value()) + mw.connConf.Username = mw.username.Text() + mw.connConf.Password = mw.password.Text() + var confData bytes.Buffer + d,_ := json.Marshal(&mw.connConf) + _ = json.Indent(&confData, d, "", "\t") + _ = ioutil.WriteFile(mw.connConfFileName,confData.Bytes(),0644) + mw.host.SetEnabled(false) + mw.port.SetEnabled(false) + mw.username.SetEnabled(false) + mw.password.SetEnabled(false) + mw.disconnect.SetEnabled(true) + mw.connect.SetEnabled(false) + mw.caConf.SetEnabled(false) + topic := "gateway/+/command/#" + token := mw.mqttClient.Subscribe(topic,0, mw.HandleData) + if token.Wait() && token.Error() != nil { + msg := "订阅失败:" + token.Error().Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + } + }else{ + msg := "连接失败:" + token.Error().Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + } + }else{ + msg := "主机/IP不能为空,端口不能为0" + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + } + }() +} + +func (mw *DTUMainWindow) Disconnect() { + mw.connect.SetEnabled(true) + mw.host.SetEnabled(true) + mw.port.SetEnabled(true) + mw.username.SetEnabled(true) + mw.password.SetEnabled(true) + mw.disconnect.SetEnabled(false) + mw.caConf.SetEnabled(true) + mw.mqttClient.Disconnect(0) +} + +func (mw *DTUMainWindow) DTUConfig() { + var dlg *walk.Dialog + var otaa *walk.CheckBox + var gatewayId,devEUI,devAddr,appKey,appSKey,nwkSKey *walk.LineEdit + var fPort,fCnt,freq *walk.NumberEdit + var msg *walk.TextEdit + var acceptPB, cancelPB *walk.PushButton + _ = Dialog{ + Title: "DTU配置", + Layout: VBox{}, + AssignTo: &dlg, + DefaultButton: &acceptPB, + CancelButton: &cancelPB, + MinSize: Size{400, 200}, + Children: []Widget{ + Composite{ + Layout:Grid{Columns: 2}, + Children:[]Widget{ + Label{Text:"入网方式:"}, + CheckBox{AssignTo: &otaa,Text:"OTAA入网",OnClicked: func() { + if otaa.Checked() { + appKey.SetEnabled(true) + devAddr.SetEnabled(false) + appSKey.SetEnabled(false) + nwkSKey.SetEnabled(false) + }else{ + appKey.SetEnabled(false) + devAddr.SetEnabled(true) + appSKey.SetEnabled(true) + nwkSKey.SetEnabled(true) + } + }}, + Label{Text:"网关ID:"}, + LineEdit{AssignTo:&gatewayId}, + Label{Text:"终端EUI:"}, + LineEdit{AssignTo:&devEUI}, + Label{Text:"终端地址:"}, + LineEdit{AssignTo:&devAddr}, + Label{Text:"应用秘钥:"}, + LineEdit{AssignTo:&appKey,Enabled:false}, + Label{Text:"应用会话秘钥:"}, + LineEdit{AssignTo:&appSKey}, + Label{Text:"网络会话秘钥:"}, + LineEdit{AssignTo:&nwkSKey}, + Label{Text:"端口:"}, + NumberEdit{AssignTo:&fPort}, + Label{Text:"计数:"}, + NumberEdit{AssignTo:&fCnt}, + Label{Text:"频率:"}, + NumberEdit{AssignTo:&freq,Decimals:2}, + Label{Text:"消息:"}, + TextEdit{AssignTo:&msg}, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + HSpacer{}, + PushButton{ + AssignTo: &acceptPB, + Text: "确定", + OnClicked: func() { + mw.dtuConf.OTAA = otaa.Checked() + mw.dtuConf.GatewayId = gatewayId.Text() + mw.dtuConf.DevEui = devEUI.Text() + mw.dtuConf.DevAddr = devAddr.Text() + mw.dtuConf.AppKey = appKey.Text() + mw.dtuConf.AppSKey = appSKey.Text() + mw.dtuConf.NwkSKey = nwkSKey.Text() + mw.dtuConf.FPort = uint8(fPort.Value()) + mw.dtuConf.FCnt = uint32(fCnt.Value()) + mw.dtuConf.Freq = freq.Value() + mw.dtuConf.msg = []byte(msg.Text()) + + var confData bytes.Buffer + d,_ := json.Marshal(&mw.dtuConf) + _ = json.Indent(&confData, d, "", "\t") + _ = ioutil.WriteFile(mw.dtuConfFileName,confData.Bytes(),0644) + dlg.Accept() + }, + }, + PushButton{ + AssignTo: &cancelPB, + Text: "取消", + OnClicked: func() { dlg.Cancel() }, + }, + }, + }, + }, + }.Create(mw) + data, err := ioutil.ReadFile(mw.dtuConfFileName) + if err == nil { + err = json.Unmarshal(data, &mw.dtuConf) + if err != nil { + msg := "配置文件格式错误:" + err.Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return + } + } + otaa.SetChecked(mw.dtuConf.OTAA) + _ = gatewayId.SetText(mw.dtuConf.GatewayId) + _ = devEUI.SetText(mw.dtuConf.DevEui) + _ = devAddr.SetText(mw.dtuConf.DevAddr) + _ = appKey.SetText(mw.dtuConf.AppKey) + _ = appSKey.SetText(mw.dtuConf.AppSKey) + _ = nwkSKey.SetText(mw.dtuConf.NwkSKey) + _ = fPort.SetValue(float64(mw.dtuConf.FPort)) + _ = fCnt.SetValue(float64(mw.dtuConf.FCnt)) + _ = freq.SetValue(mw.dtuConf.Freq) + _ = msg.SetText(string(mw.dtuConf.msg)) + + dlg.Run() +} +func (mw *DTUMainWindow) SSL() { + if mw.ssl.Checked() { + mw.caConf.SetEnabled(true) + }else{ + mw.caConf.SetEnabled(false) + } +} + +func (mw *DTUMainWindow) ConnectConfig() { + var dlg *walk.Dialog + var caCert,tlsCert,tlsKey *walk.LineEdit + var acceptPB, cancelPB *walk.PushButton + _ = Dialog{ + Title: "连接配置", + Layout: VBox{}, + AssignTo: &dlg, + DefaultButton: &acceptPB, + CancelButton: &cancelPB, + MinSize: Size{400, 200}, + Children: []Widget{ + Composite{ + Layout:Grid{Columns: 3}, + Children:[]Widget{ + Label{Text:"CA证书:"}, + LineEdit{Text:Bind("CACert"),AssignTo:&caCert}, + PushButton{Text:"打开",OnClicked: func() { + dlg := new(walk.FileDialog) + dlg.Title = "请选择CA证书" + dlg.Filter = "CA证书 (*.crt)|*.crt|所有文件 (*.*)|*.*" + if ok, err := dlg.ShowOpen(mw); err != nil { + return + } else if !ok { + return + } + _ = caCert.SetText(dlg.FilePath) + }}, + Label{Text:"客户端证书:"}, + LineEdit{Text:Bind("TLSCert"),AssignTo:&tlsCert}, + PushButton{Text:"打开",OnClicked: func() { + dlg := new(walk.FileDialog) + dlg.Title = "请选择客户端证书" + dlg.Filter = "客户端证书 (*.crt)|*.crt|所有文件 (*.*)|*.*" + if ok, err := dlg.ShowOpen(mw); err != nil { + return + } else if !ok { + return + } + _ = tlsCert.SetText(dlg.FilePath) + }}, + Label{Text:"客户端证书秘钥:"}, + LineEdit{Text:Bind("TLSKey"),AssignTo:&tlsKey}, + PushButton{Text:"打开",OnClicked: func() { + dlg := new(walk.FileDialog) + dlg.Title = "请选择客户端证书秘钥" + dlg.Filter = "客户端证书秘钥 (*.key)|*.key|所有文件 (*.*)|*.*" + if ok, err := dlg.ShowOpen(mw); err != nil { + return + } else if !ok { + return + } + _ = tlsKey.SetText(dlg.FilePath) + }}, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + HSpacer{}, + PushButton{ + AssignTo: &acceptPB, + Text: "确定", + OnClicked: func() { + mw.connConf.CACert = caCert.Text() + mw.connConf.TLSCert = tlsCert.Text() + mw.connConf.TLSKey = tlsKey.Text() + dlg.Accept() + }, + }, + PushButton{ + AssignTo: &cancelPB, + Text: "取消", + OnClicked: func() { dlg.Cancel() }, + }, + }, + }, + }, + }.Create(mw) + _ = caCert.SetText(mw.connConf.CACert) + _ = tlsCert.SetText(mw.connConf.TLSCert) + _ = tlsKey.SetText(mw.connConf.TLSKey) + dlg.Run() +} + +func (mw *DTUMainWindow) BuildUpData() (*packets.PushDataPacket,*lorawan.PHYPayload) { + now := time.Now().Round(time.Second) + compactTS := packets.CompactTime(now) + tmms := int64(time.Second / time.Millisecond) + + var phy lorawan.PHYPayload + phy.MHDR.MType = lorawan.ConfirmedDataUp + phy.MHDR.Major = lorawan.LoRaWANR1 + var mac lorawan.MACPayload + _ = mac.FHDR.DevAddr.UnmarshalText([]byte(mw.dtuConf.DevAddr)) + _ = mac.FHDR.FCtrl.UnmarshalBinary([]byte{128}) + mac.FHDR.FCnt = mw.dtuConf.FCnt + mac.FPort = &mw.dtuConf.FPort + var dataPayload lorawan.DataPayload + _ = dataPayload.UnmarshalBinary(true, mw.dtuConf.msg) + mac.FRMPayload = []lorawan.Payload{&dataPayload} + phy.MACPayload = &mac + var aseKey lorawan.AES128Key + _ = aseKey.UnmarshalText([]byte(mw.dtuConf.NwkSKey)) + _ = phy.EncryptFRMPayload(aseKey) + sf :=[...]string{"SF12","SF11","SF10","SF9","SF8","SF7"} + var dr uint8 = 5 + var ch uint8 = 2 + _ = phy.SetUplinkDataMIC(lorawan.LoRaWAN1_0, 0, dr, ch, aseKey, aseKey) + data,_ := phy.MarshalBinary() + var gatewayMac lorawan.EUI64 + _ = gatewayMac.UnmarshalText([]byte(mw.dtuConf.GatewayId)) + return &packets.PushDataPacket{ + ProtocolVersion: packets.ProtocolVersion2, + RandomToken: uint16(rand.Uint32()), + GatewayMAC: gatewayMac, + Payload: packets.PushDataPayload{ + RXPK: []packets.RXPK{ + { + Time: &compactTS, + Tmst: 708016819, + Tmms: &tmms, + Freq: mw.dtuConf.Freq, + Chan: ch, + RFCh: 1, + Stat: 1, + Modu: "LORA", + DatR: packets.DatR{LoRa: sf[dr]+"BW125"}, + CodR: "4/5", + RSSI: -51, + LSNR: 7, + Size: uint16(len(data)), + Data: data, + }, + }, + }, + },&phy +} + +func (mw *DTUMainWindow) BuildJoin() (*packets.PushDataPacket,*lorawan.PHYPayload) { + now := time.Now().Round(time.Second) + compactTS := packets.CompactTime(now) + tmms := int64(time.Second / time.Millisecond) + var phy lorawan.PHYPayload + phy.MHDR.MType = lorawan.JoinRequest + phy.MHDR.Major = lorawan.LoRaWANR1 + var DevEUI lorawan.EUI64 + _ = DevEUI.UnmarshalText([]byte(mw.dtuConf.DevEui)) + mw.dtuConf.devNonce = lorawan.DevNonce(rand.Uint32()) + phy.MACPayload = &lorawan.JoinRequestPayload{ + DevEUI: DevEUI, + JoinEUI: lorawan.EUI64{8, 7, 6, 5, 4, 3, 2, 1}, + DevNonce: mw.dtuConf.devNonce, + } + var aseKey lorawan.AES128Key + _ = aseKey.UnmarshalText([]byte(mw.dtuConf.AppKey)) + sf :=[...]string{"SF12","SF11","SF10","SF9","SF8","SF7"} + var dr uint8 = 5 + var ch uint8 = 2 + _ = phy.SetUplinkJoinMIC(aseKey) + data,_ := phy.MarshalBinary() + var gatewayMac lorawan.EUI64 + _ = gatewayMac.UnmarshalText([]byte(mw.dtuConf.GatewayId)) + return &packets.PushDataPacket{ + ProtocolVersion: packets.ProtocolVersion2, + RandomToken: uint16(rand.Uint32()), + GatewayMAC: gatewayMac, + Payload: packets.PushDataPayload{ + RXPK: []packets.RXPK{ + { + Time: &compactTS, + Tmst: 708016819, + Tmms: &tmms, + Freq: mw.dtuConf.Freq, + Chan: ch, + RFCh: 1, + Stat: 1, + Modu: "LORA", + DatR: packets.DatR{LoRa: sf[dr]+"BW125"}, + CodR: "4/5", + RSSI: -51, + LSNR: 7, + Size: uint16(len(data)), + Data: data, + }, + }, + }, + },&phy +} + +// getNwkSKey returns the network session key. +func getNwkSKey(appkey lorawan.AES128Key, netID lorawan.NetID, joinNonce lorawan.JoinNonce, devNonce lorawan.DevNonce) (lorawan.AES128Key, error) { + return getSKey(0x01, appkey, netID, joinNonce, devNonce) +} + +// getAppSKey returns the application session key. +func getAppSKey(appkey lorawan.AES128Key, netID lorawan.NetID, joinNonce lorawan.JoinNonce, devNonce lorawan.DevNonce) (lorawan.AES128Key, error) { + return getSKey(0x02, appkey, netID, joinNonce, devNonce) +} + +func getSKey(typ byte, nwkKey lorawan.AES128Key, netID lorawan.NetID,joinNonce lorawan.JoinNonce, devNonce lorawan.DevNonce) (lorawan.AES128Key, error) { + var key lorawan.AES128Key + b := make([]byte, 16) + b[0] = typ + + netIDB, err := netID.MarshalBinary() + if err != nil { + return key, errors.Wrap(err, "marshal binary error") + } + + joinNonceB, err := joinNonce.MarshalBinary() + if err != nil { + return key, errors.Wrap(err, "marshal binary error") + } + + devNonceB, err := devNonce.MarshalBinary() + if err != nil { + return key, errors.Wrap(err, "marshal binary error") + } + + copy(b[1:4], joinNonceB) + copy(b[4:7], netIDB) + copy(b[7:9], devNonceB) + + block, err := aes.NewCipher(nwkKey[:]) + if err != nil { + return key, err + } + if block.BlockSize() != len(b) { + return key, fmt.Errorf("block-size of %d bytes is expected", len(b)) + } + block.Encrypt(key[:], b) + + return key, nil +} + +func MarshalFRMPayload(p *lorawan.MACPayload) ([]byte, error) { + var out []byte + var b []byte + var err error + for _, fp := range p.FRMPayload { + if mac, ok := fp.(*lorawan.MACCommand); ok { + if p.FPort == nil || (p.FPort != nil && *p.FPort != 0) { + return []byte{}, errors.New("lorawan: a MAC command is only allowed when FPort=0") + } + b, err = mac.MarshalBinary() + } else { + b, err = fp.MarshalBinary() + } + if err != nil { + return nil, err + } + out = append(out, b...) + } + return out, nil +} + +func (mw *DTUMainWindow) HandleData(client paho.Client, message paho.Message){ + var downlinkFrame gw.DownlinkFrame + err := proto.Unmarshal(message.Payload(),&downlinkFrame) + if err == nil { + packet,err := packets.GetPullRespPacket(packets.ProtocolVersion2, uint16(rand.Uint32()),downlinkFrame) + if err == nil{ + var phy lorawan.PHYPayload + err = phy.UnmarshalBinary(packet.Payload.TXPK.Data) + if err == nil { + switch phy.MHDR.MType { + case lorawan.JoinAccept: + mw.HandleJoinAccept(&phy) + case lorawan.ConfirmedDataDown,lorawan.UnconfirmedDataDown: + mw.HandleDataDown(&phy) + default: + fmt.Println("未处理的帧") + } + var origData bytes.Buffer + jsonData,_ := phy.MarshalJSON() + _ = json.Indent(&origData, jsonData, "", " ") + dd := &model.DTUDown{ + Index: mw.downModel.Len() + 1, + DevEUI: mw.dtuConf.DevEui, + DevAddr: mw.dtuConf.DevAddr, + GatewayID: hex.EncodeToString(downlinkFrame.TxInfo.GatewayId), + Time:time.Now().Format("2006-01-02 15:04:05"), + OrigData:origData.String(), + } + if phy.MHDR.MType == lorawan.ConfirmedDataDown || phy.MHDR.MType == lorawan.UnconfirmedDataDown { + mpl := phy.MACPayload.(*lorawan.MACPayload) + if mpl.FPort != nil { + dd.FPort = *mpl.FPort + } + dd.FCnt = mpl.FHDR.FCnt + p,err := MarshalFRMPayload(mpl) + if err == nil { + dd.AsciiData = BytesToString(p) + dd.HexData = hex.EncodeToString(p) + } + } + mw.downModel.Items = append(mw.downModel.Items, dd) + mw.downModel.PublishRowsReset() + _ = mw.downTv.SetSelectedIndexes([]int{}) + } + } + } +} + +func (mw *DTUMainWindow) HandleJoinAccept(phy *lorawan.PHYPayload){ + key := mw.dtuConf.AppKey + var aseKey lorawan.AES128Key + _ = aseKey.UnmarshalText([]byte(key)) + err := phy.DecryptJoinAcceptPayload(aseKey) + if err != nil { + fmt.Println(err) + } + jap,ok := phy.MACPayload.(*lorawan.JoinAcceptPayload) + if !ok { + fmt.Println("lorawan: MACPayload must be of type *JoinAcceptPayload") + } + mw.dtuConf.DevAddr = jap.DevAddr.String() + dn := mw.dtuConf.devNonce + appSKey,err := getAppSKey(aseKey,jap.HomeNetID,jap.JoinNonce,dn) + if err == nil { + mw.dtuConf.AppSKey = appSKey.String() + fmt.Println(mw.dtuConf.AppSKey) + } + nwkSKey,err := getNwkSKey(aseKey,jap.HomeNetID,jap.JoinNonce,dn) + if err == nil { + mw.dtuConf.NwkSKey = nwkSKey.String() + fmt.Println(mw.dtuConf.NwkSKey) + } +} + +func (mw *DTUMainWindow) HandleDataDown(phy *lorawan.PHYPayload){ + key := mw.dtuConf.NwkSKey + var aseKey lorawan.AES128Key + _ = aseKey.UnmarshalText([]byte(key)) + err := phy.DecryptFRMPayload(aseKey) + if err == nil { + mpl := phy.MACPayload.(*lorawan.MACPayload) + mw.dtuConf.DevAddr = mpl.FHDR.DevAddr.String() + } +} + +func (mw *DTUMainWindow) PushData(gatewayEUI string,event string, msg proto.Message) { + topic := fmt.Sprintf("gateway/%s/event/%s",gatewayEUI,event) + b, err := proto.Marshal(msg) + if err != nil { + fmt.Println("marshal message error") + } + + if token := mw.mqttClient.Publish(topic, 0, false, b); token.Wait() && token.Error() == nil { + fmt.Println("mqtt message ok") + }else{ + fmt.Println("mqtt message error") + } +} + +func (mw *DTUMainWindow) Send(){ + if mw.dtuConf.OTAA && mw.dtuConf.DevAddr == ""{ + packet,phy := mw.BuildJoin() + var origData bytes.Buffer + jsonData,_ := phy.MarshalJSON() + _ = json.Indent(&origData, jsonData, "", " ") + du := &model.DTUUp{ + Index:mw.upModel.Len() + 1, + DevEUI:mw.dtuConf.DevEui, + MType:phy.MHDR.MType.String(), + GatewayID:mw.dtuConf.GatewayId, + Rssi:packet.Payload.RXPK[0].RSSI, + LoRaSNR:packet.Payload.RXPK[0].LSNR, + Frequency:packet.Payload.RXPK[0].Freq, + Time:time.Now().Format("2006-01-02 15:04:05"), + OrigData:origData.String(), + } + mw.upModel.Items = append(mw.upModel.Items, du) + mw.upModel.PublishRowsReset() + _ = mw.upTv.SetSelectedIndexes([]int{}) + frames,_:= packet.GetUplinkFrames(true,false) + for j := range frames { + mw.PushData(mw.dtuConf.GatewayId,"up",&frames[j]) + } + fmt.Println("push join ") + for cnt := 0;mw.dtuConf.DevAddr == "" && cnt < 5;cnt ++ { + time.Sleep(time.Second) + } + if mw.dtuConf.DevAddr != "" { + fmt.Println("join ok") + }else{ + fmt.Println("join failed") + return + } + } + packet,phy := mw.BuildUpData() + var origData bytes.Buffer + jsonData,_ := phy.MarshalJSON() + _ = json.Indent(&origData, jsonData, "", " ") + du := &model.DTUUp{ + Index:mw.upModel.Len() + 1, + DevEUI:mw.dtuConf.DevEui, + MType:phy.MHDR.MType.String(), + GatewayID:mw.dtuConf.GatewayId, + Rssi:packet.Payload.RXPK[0].RSSI, + LoRaSNR:packet.Payload.RXPK[0].LSNR, + Frequency:packet.Payload.RXPK[0].Freq, + FCnt:mw.dtuConf.FCnt, + FPort:mw.dtuConf.FPort, + HexData:hex.EncodeToString(mw.dtuConf.msg), + AsciiData:BytesToString(mw.dtuConf.msg), + Time:time.Now().Format("2006-01-02 15:04:05"), + OrigData:origData.String(), + } + mw.upModel.Items = append(mw.upModel.Items, du) + mw.upModel.PublishRowsReset() + _ = mw.upTv.SetSelectedIndexes([]int{}) + frames,_:= packet.GetUplinkFrames(true,false) + for j := range frames { + mw.PushData(mw.dtuConf.GatewayId,"up",&frames[j]) + } + mw.dtuConf.FCnt ++ + var confData bytes.Buffer + d,_ := json.Marshal(&mw.dtuConf) + _ = json.Indent(&confData, d, "", "\t") + _ = ioutil.WriteFile(mw.dtuConfFileName,confData.Bytes(),0644) +} + +func (mw *DTUMainWindow) upTvItemActivated() { + msg := "" + for _, i := range mw.upTv.SelectedIndexes() { + msg += mw.upModel.Items[i].OrigData + "\n" + } + walk.MsgBox(mw, "原始数据", msg, walk.MsgBoxOK) +} + +func (mw *DTUMainWindow) downTvItemActivated() { + msg := "" + for _, i := range mw.downTv.SelectedIndexes() { + msg += mw.downModel.Items[i].OrigData + "\n" + } + walk.MsgBox(mw, "原始数据", msg, walk.MsgBoxOK) +} \ No newline at end of file diff --git a/LoRaDTUMock.json b/LoRaDTUMock.json new file mode 100644 index 0000000..93f074e --- /dev/null +++ b/LoRaDTUMock.json @@ -0,0 +1,9 @@ +{ + "host": "39.98.253.192", + "port": 1885, + "username": "", + "password": "", + "ca_cert": "", + "tls_cert": "", + "tls_key": "" +} \ No newline at end of file diff --git a/LoRaDTUMock.manifest b/LoRaDTUMock.manifest new file mode 100644 index 0000000..1492a19 --- /dev/null +++ b/LoRaDTUMock.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + PerMonitorV2, PerMonitor + True + + + \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..1828dd9 --- /dev/null +++ b/build.bat @@ -0,0 +1,3 @@ +rem set GOARCH=386 +set CGO_ENABLED=0 +go build -ldflags "-H windowsgui" \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c1d80af --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module LoRaDTUMock + +go 1.12 + +require ( + github.com/brocaar/loraserver v0.0.0-20190729122155-2f0bb9c308bc + github.com/brocaar/lorawan v0.0.0-20190709091804-c3a80883a8fa + github.com/eclipse/paho.mqtt.golang v1.2.0 + github.com/golang/protobuf v1.3.2 + github.com/lxn/walk v0.0.0-20191113135339-bf589de20b3c + github.com/lxn/win v0.0.0-20191106123917-121afc750dd3 + github.com/pkg/errors v0.8.1 + gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2b615ab --- /dev/null +++ b/go.sum @@ -0,0 +1,595 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +github.com/Azure/azure-amqp-common-go v1.1.4/go.mod h1:FhZtXirFANw40UXI2ntweO+VOkfaw8s6vZxUiRhLYW8= +github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v27.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.5.1/go.mod h1:Tctgev//M0s8G6mdA+ZH9esoIXuiq7qEkxoDLZwfCrA= +github.com/Azure/go-autorest v11.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/NickBall/go-aes-key-wrap v0.0.0-20170929221519-1c3aa3e4dfc5/go.mod h1:w5D10RxC0NmPYxmQ438CC1S07zaC1zpvuNW7s5sUk2Q= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.15.64/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/brocaar/loraserver v0.0.0-20190729122155-2f0bb9c308bc h1:bum9Ye6oZBx+H1yHqZhlNy9V7CDf/fBCYadMpV9xcng= +github.com/brocaar/loraserver v0.0.0-20190729122155-2f0bb9c308bc/go.mod h1:nu05fgUCXdcEJQ7KJHMw5lHO5Efxq7j+g4r5g9PryhY= +github.com/brocaar/loraserver v2.5.0+incompatible h1:Fna4CF0jW2Vl4UpjLIhR5ifW4g+oZD/w3Dq09TiJ8Z8= +github.com/brocaar/loraserver v2.5.0+incompatible/go.mod h1:VBTim0YtfWAKehjJ6k17jCnG44DzXVdL4iu+hwxg2ik= +github.com/brocaar/lorawan v0.0.0-20190709091804-c3a80883a8fa h1:UcT94Q1RMA+lTAD0kxDY5RjkhagJgcoAjIKlKvs7A8o= +github.com/brocaar/lorawan v0.0.0-20190709091804-c3a80883a8fa/go.mod h1:Fm+51pxK6mZoAQjIaWJqPmnRuXecozsM5Mf9c+kr/ko= +github.com/brocaar/lorawan v0.0.0-20191115102621-6095d473cf60 h1:jecx8QFcsUAWZojNIqOZaoAWFwkBbd3VOHZ3kv6FQxE= +github.com/brocaar/lorawan v0.0.0-20191115102621-6095d473cf60/go.mod h1:VgyRGAJ/wl1JfqZZmNlCTM8fyaIKF11YvYAGIVtedL8= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= +github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4= +github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs= +github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4= +github.com/gobuffalo/buffalo-plugins v1.4.3/go.mod h1:uCzTY0woez4nDMdQjkcOYKanngeUVRO2HZi7ezmAjWY= +github.com/gobuffalo/buffalo-plugins v1.5.1/go.mod h1:jbmwSZK5+PiAP9cC09VQOrGMZFCa/P0UMlIS3O12r5w= +github.com/gobuffalo/buffalo-plugins v1.6.4/go.mod h1:/+N1aophkA2jZ1ifB2O3Y9yGwu6gKOVMtUmJnbg+OZI= +github.com/gobuffalo/buffalo-plugins v1.6.5/go.mod h1:0HVkbgrVs/MnPZ/FOseDMVanCTm2RNcdM0PuXcL1NNI= +github.com/gobuffalo/buffalo-plugins v1.6.7/go.mod h1:ZGZRkzz2PiKWHs0z7QsPBOTo2EpcGRArMEym6ghKYgk= +github.com/gobuffalo/buffalo-plugins v1.6.9/go.mod h1:yYlYTrPdMCz+6/+UaXg5Jm4gN3xhsvsQ2ygVatZV5vw= +github.com/gobuffalo/buffalo-plugins v1.6.11/go.mod h1:eAA6xJIL8OuynJZ8amXjRmHND6YiusVAaJdHDN1Lu8Q= +github.com/gobuffalo/buffalo-plugins v1.8.2/go.mod h1:9te6/VjEQ7pKp7lXlDIMqzxgGpjlKoAcAANdCgoR960= +github.com/gobuffalo/buffalo-plugins v1.8.3/go.mod h1:IAWq6vjZJVXebIq2qGTLOdlXzmpyTZ5iJG5b59fza5U= +github.com/gobuffalo/buffalo-plugins v1.9.4/go.mod h1:grCV6DGsQlVzQwk6XdgcL3ZPgLm9BVxlBmXPMF8oBHI= +github.com/gobuffalo/buffalo-plugins v1.10.0/go.mod h1:4osg8d9s60txLuGwXnqH+RCjPHj9K466cDFRl3PErHI= +github.com/gobuffalo/buffalo-plugins v1.11.0/go.mod h1:rtIvAYRjYibgmWhnjKmo7OadtnxuMG5ZQLr25ozAzjg= +github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8= +github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc= +github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.6/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.7/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.8/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.9/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.10/go.mod h1:X0CFllQjTV5ogsnUrg+Oks2yTI+PU2dGYBJOEI2D1Uo= +github.com/gobuffalo/envy v1.6.11/go.mod h1:Fiq52W7nrHGDggFPhn2ZCcHw4u/rqXkqo+i7FB6EAcg= +github.com/gobuffalo/envy v1.6.12/go.mod h1:qJNrJhKkZpEW0glh5xP2syQHH5kgdmgsKss2Kk8PTP0= +github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw= +github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ= +github.com/gobuffalo/events v1.0.8/go.mod h1:A5KyqT1sA+3GJiBE4QKZibse9mtOcI9nw8gGrDdqYGs= +github.com/gobuffalo/events v1.1.3/go.mod h1:9yPGWYv11GENtzrIRApwQRMYSbUgCsZ1w6R503fCfrk= +github.com/gobuffalo/events v1.1.4/go.mod h1:09/YRRgZHEOts5Isov+g9X2xajxdvOAcUuAHIX/O//A= +github.com/gobuffalo/events v1.1.5/go.mod h1:3YUSzgHfYctSjEjLCWbkXP6djH2M+MLaVRzb4ymbAK0= +github.com/gobuffalo/events v1.1.7/go.mod h1:6fGqxH2ing5XMb3EYRq9LEkVlyPGs4oO/eLzh+S8CxY= +github.com/gobuffalo/events v1.1.8/go.mod h1:UFy+W6X6VbCWS8k2iT81HYX65dMtiuVycMy04cplt/8= +github.com/gobuffalo/events v1.1.9/go.mod h1:/0nf8lMtP5TkgNbzYxR6Bl4GzBy5s5TebgNTdRfRbPM= +github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc= +github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181018182602-fd24a256709f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181019110701-3d6f0b585514/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181024204909-8f6be1a8c6c2/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181104133451-1f6e9779237a/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181114183036-47375f6d8328/go.mod h1:0HvNbHdfh+WOvDSIASqJOSxTOWSxCCUF++k/Y53v9rI= +github.com/gobuffalo/flect v0.0.0-20181210151238-24a2b68e0316/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190104192022-4af577e09bf2/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190117212819-a62e61d96794/go.mod h1:397QT6v05LkZkn07oJXXT6y9FCfwC8Pug0WA2/2mE9k= +github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g= +github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181017160347-90a774534246/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181024195656-51392254bf53/go.mod h1:o9GEH5gn5sCKLVB5rHFC4tq40rQ3VRUzmx6WwmaqISE= +github.com/gobuffalo/genny v0.0.0-20181025145300-af3f81d526b8/go.mod h1:uZ1fFYvdcP8mu0B/Ynarf6dsGvp7QFIpk/QACUuFUVI= +github.com/gobuffalo/genny v0.0.0-20181027191429-94d6cfb5c7fc/go.mod h1:x7SkrQQBx204Y+O9EwRXeszLJDTaWN0GnEasxgLrQTA= +github.com/gobuffalo/genny v0.0.0-20181027195209-3887b7171c4f/go.mod h1:JbKx8HSWICu5zyqWOa0dVV1pbbXOHusrSzQUprW6g+w= +github.com/gobuffalo/genny v0.0.0-20181106193839-7dcb0924caf1/go.mod h1:x61yHxvbDCgQ/7cOAbJCacZQuHgB0KMSzoYcw5debjU= +github.com/gobuffalo/genny v0.0.0-20181107223128-f18346459dbe/go.mod h1:utQD3aKKEsdb03oR+Vi/6ztQb1j7pO10N3OBoowRcSU= +github.com/gobuffalo/genny v0.0.0-20181114215459-0a4decd77f5d/go.mod h1:kN2KZ8VgXF9VIIOj/GM0Eo7YK+un4Q3tTreKOf0q1ng= +github.com/gobuffalo/genny v0.0.0-20181119162812-e8ff4adce8bb/go.mod h1:BA9htSe4bZwBDJLe8CUkoqkypq3hn3+CkoHqVOW718E= +github.com/gobuffalo/genny v0.0.0-20181127225641-2d959acc795b/go.mod h1:l54xLXNkteX/PdZ+HlgPk1qtcrgeOr3XUBBPDbH+7CQ= +github.com/gobuffalo/genny v0.0.0-20181128191930-77e34f71ba2a/go.mod h1:FW/D9p7cEEOqxYA71/hnrkOWm62JZ5ZNxcNIVJEaWBU= +github.com/gobuffalo/genny v0.0.0-20181203165245-fda8bcce96b1/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181203201232-849d2c9534ea/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181206121324-d6fb8a0dbe36/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181207164119-84844398a37d/go.mod h1:y0ysCHGGQf2T3vOhCrGHheYN54Y/REj0ayd0Suf4C/8= +github.com/gobuffalo/genny v0.0.0-20181211165820-e26c8466f14d/go.mod h1:sHnK+ZSU4e2feXP3PA29ouij6PUEiN+RCwECjCTB3yM= +github.com/gobuffalo/genny v0.0.0-20190104222617-a71664fc38e7/go.mod h1:QPsQ1FnhEsiU8f+O0qKWXz2RE4TiDqLVChWkBuh1WaY= +github.com/gobuffalo/genny v0.0.0-20190112155932-f31a84fcacf5/go.mod h1:CIaHCrSIuJ4il6ka3Hub4DR4adDrGoXGEEt2FbBxoIo= +github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I= +github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY= +github.com/gobuffalo/github_flavored_markdown v1.0.7/go.mod h1:w93Pd9Lz6LvyQXEG6DktTPHkOtCbr+arAD5mkwMzXLI= +github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= +github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= +github.com/gobuffalo/licenser v0.0.0-20181025145548-437d89de4f75/go.mod h1:x3lEpYxkRG/XtGCUNkio+6RZ/dlOvLzTI9M1auIwFcw= +github.com/gobuffalo/licenser v0.0.0-20181027200154-58051a75da95/go.mod h1:BzhaaxGd1tq1+OLKObzgdCV9kqVhbTulxOpYbvMQWS0= +github.com/gobuffalo/licenser v0.0.0-20181109171355-91a2a7aac9a7/go.mod h1:m+Ygox92pi9bdg+gVaycvqE8RVSjZp7mWw75+K5NPHk= +github.com/gobuffalo/licenser v0.0.0-20181128165715-cc7305f8abed/go.mod h1:oU9F9UCE+AzI/MueCKZamsezGOOHfSirltllOVeRTAE= +github.com/gobuffalo/licenser v0.0.0-20181203160806-fe900bbede07/go.mod h1:ph6VDNvOzt1CdfaWC+9XwcBnlSTBz2j49PBwum6RFaU= +github.com/gobuffalo/licenser v0.0.0-20181211173111-f8a311c51159/go.mod h1:ve/Ue99DRuvnTaLq2zKa6F4KtHiYf7W046tDjuGYPfM= +github.com/gobuffalo/logger v0.0.0-20181022175615-46cfb361fc27/go.mod h1:8sQkgyhWipz1mIctHF4jTxmJh1Vxhp7mP8IqbljgJZo= +github.com/gobuffalo/logger v0.0.0-20181027144941-73d08d2bb969/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181027193913-9cf4dd0efe46/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181109185836-3feeab578c17/go.mod h1:oNErH0xLe+utO+OW8ptXMSA5DkiSEDW1u3zGIt8F9Ew= +github.com/gobuffalo/logger v0.0.0-20181117211126-8e9b89b7c264/go.mod h1:5etB91IE0uBlw9k756fVKZJdS+7M7ejVhmpXXiSFj0I= +github.com/gobuffalo/logger v0.0.0-20181127160119-5b956e21995c/go.mod h1:+HxKANrR9VGw9yN3aOAppJKvhO05ctDi63w4mDnKv2U= +github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw= +github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/meta v0.0.0-20181018155829-df62557efcd3/go.mod h1:XTTOhwMNryif3x9LkTTBO/Llrveezd71u3quLd0u7CM= +github.com/gobuffalo/meta v0.0.0-20181018192820-8c6cef77dab3/go.mod h1:E94EPzx9NERGCY69UWlcj6Hipf2uK/vnfrF4QD0plVE= +github.com/gobuffalo/meta v0.0.0-20181025145500-3a985a084b0a/go.mod h1:YDAKBud2FP7NZdruCSlmTmDOZbVSa6bpK7LJ/A/nlKg= +github.com/gobuffalo/meta v0.0.0-20181114191255-b130ebedd2f7/go.mod h1:K6cRZ29ozr4Btvsqkjvg5nDFTLOgTqf03KA70Ks0ypE= +github.com/gobuffalo/meta v0.0.0-20181127070345-0d7e59dd540b/go.mod h1:RLO7tMvE0IAKAM8wny1aN12pvEKn7EtkBLkUZR00Qf8= +github.com/gobuffalo/meta v0.0.0-20190120163247-50bbb1fa260d/go.mod h1:KKsH44nIK2gA8p0PJmRT9GvWJUdphkDUA8AJEvFWiqM= +github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0= +github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No= +github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo= +github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ= +github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4= +github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME= +github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c= +github.com/gobuffalo/packd v0.0.0-20181027182251-01ad393492c8/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027190505-aafc0d02c411/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027194105-7ae579e6d213/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181104210303-d376b15f8e96/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181111195323-b2e760a5f0ff/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181114190715-f25c5d2471d7/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181124090624-311c6248e5fb/go.mod h1:Foenia9ZvITEvG05ab6XpiD5EfBHPL8A6hush8SJ0o8= +github.com/gobuffalo/packd v0.0.0-20181207120301-c49825f8f6f4/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packd v0.0.0-20181212173646-eca3b8fd6687/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24= +github.com/gobuffalo/packr v1.15.0/go.mod h1:t5gXzEhIviQwVlNx/+3SfS07GS+cZ2hn76WLzPp6MGI= +github.com/gobuffalo/packr v1.15.1/go.mod h1:IeqicJ7jm8182yrVmNbM6PR4g79SjN9tZLH8KduZZwE= +github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU= +github.com/gobuffalo/packr v1.20.0/go.mod h1:JDytk1t2gP+my1ig7iI4NcVaXr886+N0ecUga6884zw= +github.com/gobuffalo/packr v1.21.0/go.mod h1:H00jGfj1qFKxscFJSw8wcL4hpQtPe1PfU2wa6sg/SR0= +github.com/gobuffalo/packr v1.22.0/go.mod h1:Qr3Wtxr3+HuQEwWqlLnNW4t1oTvK+7Gc/Rnoi/lDFvA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.8/go.mod h1:y60QCdzwuMwO2R49fdQhsjCPv7tLQFR0ayzxxla9zes= +github.com/gobuffalo/packr/v2 v2.0.0-rc.9/go.mod h1:fQqADRfZpEsgkc7c/K7aMew3n4aF1Kji7+lIZeR98Fc= +github.com/gobuffalo/packr/v2 v2.0.0-rc.10/go.mod h1:4CWWn4I5T3v4c1OsJ55HbHlUEKNWMITG5iIkdr4Px4w= +github.com/gobuffalo/packr/v2 v2.0.0-rc.11/go.mod h1:JoieH/3h3U4UmatmV93QmqyPUdf4wVM9HELaHEu+3fk= +github.com/gobuffalo/packr/v2 v2.0.0-rc.12/go.mod h1:FV1zZTsVFi1DSCboO36Xgs4pzCZBjB/tDV9Cz/lSaR8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.13/go.mod h1:2Mp7GhBFMdJlOK8vGfl7SYtfMP3+5roE39ejlfjw0rA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.14/go.mod h1:06otbrNvDKO1eNQ3b8hst+1010UooI2MFg+B2Ze4MV8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.15/go.mod h1:IMe7H2nJvcKXSF90y4X1rjYIRlNMJYCxEhssBXNZwWs= +github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.21+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.22+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.23+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.30+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.31+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.32+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plushgen v0.0.0-20181128164830-d29dcb966cb2/go.mod h1:r9QwptTFnuvSaSRjpSp4S2/4e2D3tJhARYbvEBcKSb4= +github.com/gobuffalo/plushgen v0.0.0-20181203163832-9fc4964505c2/go.mod h1:opEdT33AA2HdrIwK1aibqnTJDVVKXC02Bar/GT1YRVs= +github.com/gobuffalo/plushgen v0.0.0-20181207152837-eedb135bd51b/go.mod h1:Lcw7HQbEVm09sAQrCLzIxuhFbB3nAgp4c55E+UlynR0= +github.com/gobuffalo/plushgen v0.0.0-20190104222512-177cd2b872b3/go.mod h1:tYxCozi8X62bpZyKXYHw1ncx2ZtT2nFvG42kuLwYjoc= +github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.42/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.52/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.53/go.mod h1:FdF257nd8rqhNaqtDWFGhxdJ/Ig4J7VcS3KL7n/a+aA= +github.com/gobuffalo/release v1.0.54/go.mod h1:Pe5/RxRa/BE8whDpGfRqSI7D1a0evGK1T4JDm339tJc= +github.com/gobuffalo/release v1.0.61/go.mod h1:mfIO38ujUNVDlBziIYqXquYfBF+8FDHUjKZgYC1Hj24= +github.com/gobuffalo/release v1.0.72/go.mod h1:NP5NXgg/IX3M5XmHmWR99D687/3Dt9qZtTK/Lbwc1hU= +github.com/gobuffalo/release v1.1.1/go.mod h1:Sluak1Xd6kcp6snkluR1jeXAogdJZpFFRzTYRs/2uwg= +github.com/gobuffalo/release v1.1.3/go.mod h1:CuXc5/m+4zuq8idoDt1l4va0AXAn/OSs08uHOfMVr8E= +github.com/gobuffalo/release v1.1.6/go.mod h1:18naWa3kBsqO0cItXZNJuefCKOENpbbUIqRL1g+p6z0= +github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA= +github.com/gobuffalo/syncx v0.0.0-20181120191700-98333ab04150/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.0.0-20181120194010-558ac7de985f/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.14+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.15+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc= +github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY= +github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f h1:4Gslotqbs16iAg+1KR/XdabIfq8TlAWHdwS5QJFksLc= +github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/goreleaser/goreleaser v0.106.0/go.mod h1:YCWszXb4t6HZ7gzeg5TcbPJC2Ad8cFvsknUS0CwS3yY= +github.com/goreleaser/nfpm v0.11.0/go.mod h1:F2yzin6cBAL9gb+mSiReuXdsfTrOQwDMsuSpULof+y4= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jacobsa/crypto v0.0.0-20180924003735-d95898ceee07 h1:/PaS1RNKtbBEndIvzCqIgYh6GAH9ZFc8Mj4tVRVyfOA= +github.com/jacobsa/crypto v0.0.0-20180924003735-d95898ceee07/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU= +github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 h1:YuDUUFNM21CAbyPOpOP8BicaTD/0klJEKt5p8yuw+uY= +github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU= +github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= +github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= +github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= +github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff/go.mod h1:gJWba/XXGl0UoOmBQKRWCJdHrr3nE0T65t6ioaj3mLI= +github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 h1:BMb8s3ENQLt5ulwVIHVDWFHp8eIXmbfSExkvdn9qMXI= +github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11/go.mod h1:+DBdDyfoO2McrOyDemRBq0q9CMEByef7sYl7JH5Q3BI= +github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb h1:uSWBjJdMf47kQlXMwWEfmc864bA1wAC+Kl3ApryuG9Y= +github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb/go.mod h1:ivcmUvxXWjb27NsPEaiYK7AidlZXS7oQ5PowUS9z3I4= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kamilsk/retry/v4 v4.0.0/go.mod h1:0af33qDvzbhQqdOBi7iOjEpmP4brbPmNZpo7chYlgcc= +github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.7/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lxn/walk v0.0.0-20191113135339-bf589de20b3c h1:4pJw1uBKndwiBBJpcbqP1Bf90YfqTJUsHG9DVoAfIQ8= +github.com/lxn/walk v0.0.0-20191113135339-bf589de20b3c/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20191106123917-121afc750dd3 h1:DvGEvKK/Qnhph/EgdBN9zXA7pEosgJ0k57ojII51JAo= +github.com/lxn/win v0.0.0-20191106123917-121afc750dd3/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= +github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= +github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= +github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= +github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= +github.com/markbates/inflect v1.0.3/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc= +github.com/markbates/safe v1.0.0/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= +github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-zglob v0.0.0-20171230104132-4959821b4817/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rubenv/sql-migrate v0.0.0-20181213081019-5a8808c14925/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0= +github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.0/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190102171810-8d7daa0c54b3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI= +golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181022134430-8a28ead16f52/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026064943-731415f00dce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181030150119-7e31e0c00fa0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181106135930-3a76605856fd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190402054613-e4093980e83e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181024171208-a2dc47679d30/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181105230042-78dc5bac0cac/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181107215632-34b416bd17b3/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181114190951-94339b83286c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181119130350-139d099f6620/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127195227-b4e97c0ed882/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127232545-e782529d0ddd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181203210056-e5f3ab76ea4b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181205224935-3576414c54a4/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181206194817-bcd4e47d0288/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181207183836-8bc39b988060/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181212172921-837e80568c09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190102213336-ca9055ed7d04/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190104182027-498d95493402/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190708203411-c8855242db9c/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +gonum.org/v1/gonum v0.0.0-20190115205657-1b07048b32c6/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/netlib v0.0.0-20190219113230-9992c5f5eae4/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= +gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +pack.ag/amqp v0.8.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= diff --git a/model/down.go b/model/down.go new file mode 100644 index 0000000..aa02fbf --- /dev/null +++ b/model/down.go @@ -0,0 +1,107 @@ +package model + +import ( + "github.com/lxn/walk" + "sort" +) + +type DTUDown struct { + Index int + DevEUI string + MType string + DevAddr string + GatewayID string + FCnt uint32 + FPort uint8 + HexData string + AsciiData string + Time string + checked bool + OrigData string +} + +type DTUDownModel struct { + walk.TableModelBase + walk.SorterBase + SortColumn int + SortOrder walk.SortOrder + Items []*DTUDown +} + +func (m *DTUDownModel) RowCount() int { + return len(m.Items) +} + +func (m *DTUDownModel) Value(row, col int) interface{} { + item := m.Items[row] + + switch col { + case 0: + return item.Index + case 1: + return item.DevEUI + case 2: + return item.MType + case 3: + return item.DevAddr + case 4: + return item.GatewayID + case 5: + return item.FCnt + case 6: + return item.FPort + case 7: + return item.HexData + case 8: + return item.AsciiData + case 9: + return item.Time + } + panic("unexpected col") +} + +func (m *DTUDownModel) Checked(row int) bool { + return m.Items[row].checked +} + +func (m *DTUDownModel) SetChecked(row int, checked bool) error { + m.Items[row].checked = checked + return nil +} + +func (m *DTUDownModel) Sort(col int, order walk.SortOrder) error { + m.SortColumn, m.SortOrder = col, order + sort.Stable(m) + return m.SorterBase.Sort(col, order) +} + +func (m *DTUDownModel) Len() int { + return len(m.Items) +} + +func (m *DTUDownModel) Less(i, j int) bool { + a, b := m.Items[i], m.Items[j] + c := func(ls bool) bool { + if m.SortOrder == walk.SortAscending { + return ls + } + return !ls + } + + switch m.SortColumn { + case 0: + return c(a.Index < b.Index) + case 2: + return c(a.DevEUI < b.DevEUI) + default: + return false + } +} + +func (m *DTUDownModel) Swap(i, j int) { + m.Items[i], m.Items[j] = m.Items[j], m.Items[i] +} + +func NewDTUDownModel() *DTUDownModel { + return new(DTUDownModel) +} \ No newline at end of file diff --git a/model/up.go b/model/up.go new file mode 100644 index 0000000..c7ae98c --- /dev/null +++ b/model/up.go @@ -0,0 +1,119 @@ +package model + +import ( + "github.com/lxn/walk" + "sort" +) + +type DTUUp struct { + Index int + DevEUI string + MType string + GatewayID string + Rssi int16 + LoRaSNR float64 + Frequency float64 + FCnt uint32 + FPort uint8 + HexData string + AsciiData string + Time string + checked bool + OrigData string +} + +type DTUUpModel struct { + walk.TableModelBase + walk.SorterBase + SortColumn int + SortOrder walk.SortOrder + Items []*DTUUp +} + +func (m *DTUUpModel) RowCount() int { + return len(m.Items) +} + +func (m *DTUUpModel) Value(row, col int) interface{} { + item := m.Items[row] + + switch col { + case 0: + return item.Index + case 1: + return item.DevEUI + case 2: + return item.MType + case 3: + return item.GatewayID + case 4: + return item.Rssi + case 5: + return item.LoRaSNR + case 6: + return item.Frequency + case 7: + return item.FCnt + case 8: + return item.FPort + case 9: + return item.HexData + case 10: + return item.AsciiData + case 11: + return item.Time + } + panic("unexpected col") +} + +func (m *DTUUpModel) Checked(row int) bool { + return m.Items[row].checked +} + +func (m *DTUUpModel) SetChecked(row int, checked bool) error { + m.Items[row].checked = checked + return nil +} + +func (m *DTUUpModel) Sort(col int, order walk.SortOrder) error { + m.SortColumn, m.SortOrder = col, order + sort.Stable(m) + return m.SorterBase.Sort(col, order) +} + +func (m *DTUUpModel) Len() int { + return len(m.Items) +} + +func (m *DTUUpModel) Less(i, j int) bool { + a, b := m.Items[i], m.Items[j] + c := func(ls bool) bool { + if m.SortOrder == walk.SortAscending { + return ls + } + return !ls + } + + switch m.SortColumn { + case 0: + return c(a.Index < b.Index) + case 2: + return c(a.DevEUI < b.DevEUI) + case 4: + return c(a.Rssi < b.Rssi) + case 5: + return c(a.LoRaSNR < b.LoRaSNR) + case 6: + return c(a.Frequency < b.Frequency) + default: + return false + } +} + +func (m *DTUUpModel) Swap(i, j int) { + m.Items[i], m.Items[j] = m.Items[j], m.Items[i] +} + +func NewDTUUpModel() *DTUUpModel { + return new(DTUUpModel) +} \ No newline at end of file diff --git a/packets/packets.go b/packets/packets.go new file mode 100644 index 0000000..d874f48 --- /dev/null +++ b/packets/packets.go @@ -0,0 +1,126 @@ +//go:generate stringer -type=PacketType + +package packets + +import ( + "errors" + "strconv" + "strings" + "time" +) + +// PacketType defines the packet type. +type PacketType byte + +// Available packet types +const ( + PushData PacketType = iota + PushACK + PullData + PullResp + PullACK + TXACK +) + +// Protocol versions +const ( + ProtocolVersion1 uint8 = 0x01 + ProtocolVersion2 uint8 = 0x02 +) + +// Errors +var ( + ErrInvalidProtocolVersion = errors.New("gateway: invalid protocol version") +) + +// GetPacketType returns the packet type for the given packet data. +func GetPacketType(data []byte) (PacketType, error) { + if len(data) < 4 { + return PacketType(0), errors.New("gateway: at least 4 bytes of data are expected") + } + + if !protocolSupported(data[0]) { + return PacketType(0), ErrInvalidProtocolVersion + } + + return PacketType(data[3]), nil +} + +func protocolSupported(p uint8) bool { + if p == ProtocolVersion1 || p == ProtocolVersion2 { + return true + } + return false +} + +// ExpandedTime implements time.Time but (un)marshals to and from +// ISO 8601 'expanded' format. +type ExpandedTime time.Time + +// MarshalJSON implements the json.Marshaler interface. +func (t ExpandedTime) MarshalJSON() ([]byte, error) { + return []byte(time.Time(t).UTC().Format(`"2006-01-02 15:04:05 MST"`)), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *ExpandedTime) UnmarshalJSON(data []byte) error { + t2, err := time.Parse(`"2006-01-02 15:04:05 MST"`, string(data)) + if err != nil { + return err + } + *t = ExpandedTime(t2) + return nil +} + +// CompactTime implements time.Time but (un)marshals to and from +// ISO 8601 'compact' format. +type CompactTime time.Time + +// MarshalJSON implements the json.Marshaler interface. +func (t CompactTime) MarshalJSON() ([]byte, error) { + t2 := time.Time(t) + if t2.IsZero() { + return []byte("null"), nil + } + return []byte(t2.UTC().Format(`"` + time.RFC3339Nano + `"`)), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *CompactTime) UnmarshalJSON(data []byte) error { + if string(data) == `""` { + return nil + } + + t2, err := time.Parse(`"`+time.RFC3339Nano+`"`, string(data)) + if err != nil { + return err + } + *t = CompactTime(t2) + return nil +} + +// DatR implements the data rate which can be either a string (LoRa identifier) +// or an unsigned integer in case of FSK (bits per second). +type DatR struct { + LoRa string + FSK uint32 +} + +// MarshalJSON implements the json.Marshaler interface. +func (d DatR) MarshalJSON() ([]byte, error) { + if d.LoRa != "" { + return []byte(`"` + d.LoRa + `"`), nil + } + return []byte(strconv.FormatUint(uint64(d.FSK), 10)), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *DatR) UnmarshalJSON(data []byte) error { + i, err := strconv.ParseUint(string(data), 10, 32) + if err != nil { + d.LoRa = strings.Trim(string(data), `"`) + return nil + } + d.FSK = uint32(i) + return nil +} diff --git a/packets/packettype_string.go b/packets/packettype_string.go new file mode 100644 index 0000000..1c7ad02 --- /dev/null +++ b/packets/packettype_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=PacketType"; DO NOT EDIT. + +package packets + +import "strconv" + +const _PacketType_name = "PushDataPushACKPullDataPullRespPullACKTXACK" + +var _PacketType_index = [...]uint8{0, 8, 15, 23, 31, 38, 43} + +func (i PacketType) String() string { + if i >= PacketType(len(_PacketType_index)-1) { + return "PacketType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _PacketType_name[_PacketType_index[i]:_PacketType_index[i+1]] +} diff --git a/packets/pull_ack.go b/packets/pull_ack.go new file mode 100644 index 0000000..70ad266 --- /dev/null +++ b/packets/pull_ack.go @@ -0,0 +1,38 @@ +package packets + +import ( + "encoding/binary" + "errors" +) + +// PullACKPacket is used by the server to confirm that the network route is +// open and that the server can send PULL_RESP packets at any time. +type PullACKPacket struct { + ProtocolVersion uint8 + RandomToken uint16 +} + +// MarshalBinary marshals the object in binary form. +func (p PullACKPacket) MarshalBinary() ([]byte, error) { + out := make([]byte, 4) + out[0] = p.ProtocolVersion + binary.LittleEndian.PutUint16(out[1:3], p.RandomToken) + out[3] = byte(PullACK) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *PullACKPacket) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("gateway: 4 bytes of data are expected") + } + if data[3] != byte(PullACK) { + return errors.New("gateway: identifier mismatch (PULL_ACK expected)") + } + if !protocolSupported(data[0]) { + return ErrInvalidProtocolVersion + } + p.ProtocolVersion = data[0] + p.RandomToken = binary.LittleEndian.Uint16(data[1:3]) + return nil +} diff --git a/packets/pull_data.go b/packets/pull_data.go new file mode 100644 index 0000000..d3b4fd0 --- /dev/null +++ b/packets/pull_data.go @@ -0,0 +1,43 @@ +package packets + +import ( + "encoding/binary" + "errors" +) + +// PullDataPacket is used by the gateway to poll data from the server. +type PullDataPacket struct { + ProtocolVersion uint8 + RandomToken uint16 + GatewayMAC [8]byte +} + +// MarshalBinary marshals the object in binary form. +func (p PullDataPacket) MarshalBinary() ([]byte, error) { + out := make([]byte, 4, 12) + out[0] = p.ProtocolVersion + binary.LittleEndian.PutUint16(out[1:3], p.RandomToken) + out[3] = byte(PullData) + out = append(out, p.GatewayMAC[0:len(p.GatewayMAC)]...) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *PullDataPacket) UnmarshalBinary(data []byte) error { + if len(data) != 12 { + return errors.New("gateway: 12 bytes of data are expected") + } + if data[3] != byte(PullData) { + return errors.New("gateway: identifier mismatch (PULL_DATA expected)") + } + + if !protocolSupported(data[0]) { + return ErrInvalidProtocolVersion + } + p.ProtocolVersion = data[0] + p.RandomToken = binary.LittleEndian.Uint16(data[1:3]) + for i := 0; i < 8; i++ { + p.GatewayMAC[i] = data[4+i] + } + return nil +} diff --git a/packets/pull_resp.go b/packets/pull_resp.go new file mode 100644 index 0000000..0ae114b --- /dev/null +++ b/packets/pull_resp.go @@ -0,0 +1,162 @@ +package packets + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "time" + + "github.com/golang/protobuf/ptypes" + "github.com/pkg/errors" + + "github.com/brocaar/loraserver/api/common" + "github.com/brocaar/loraserver/api/gw" +) + +// PullRespPacket is used by the server to send RF packets and associated +// metadata that will have to be emitted by the gateway. +type PullRespPacket struct { + ProtocolVersion uint8 + RandomToken uint16 + Payload PullRespPayload +} + +// MarshalBinary marshals the object in binary form. +func (p PullRespPacket) MarshalBinary() ([]byte, error) { + pb, err := json.Marshal(&p.Payload) + if err != nil { + return nil, err + } + out := make([]byte, 4, 4+len(pb)) + out[0] = p.ProtocolVersion + + if p.ProtocolVersion != ProtocolVersion1 { + // these two bytes are unused in ProtocolVersion1 + binary.LittleEndian.PutUint16(out[1:3], p.RandomToken) + } + out[3] = byte(PullResp) + out = append(out, pb...) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *PullRespPacket) UnmarshalBinary(data []byte) error { + if len(data) < 5 { + return errors.New("gateway: at least 5 bytes of data are expected") + } + if data[3] != byte(PullResp) { + return errors.New("gateway: identifier mismatch (PULL_RESP expected)") + } + if !protocolSupported(data[0]) { + return ErrInvalidProtocolVersion + } + p.ProtocolVersion = data[0] + p.RandomToken = binary.LittleEndian.Uint16(data[1:3]) + return json.Unmarshal(data[4:], &p.Payload) +} + +// PullRespPayload represents the downstream JSON data structure. +type PullRespPayload struct { + TXPK TXPK `json:"txpk"` +} + +// TXPK contains a RF packet to be emitted and associated metadata. +type TXPK struct { + Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) + RFCh uint8 `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) + Powe uint8 `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) + Ant uint8 `json:"ant"` // Antenna number on which signal has been received + Brd uint32 `json:"brd"` // Concentrator board used for RX (unsigned integer) + Tmst *uint32 `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Tmms *int64 `json:"tmms,omitempty"` // Send packet at a certain GPS time (GPS synchronization required) + Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + DatR DatR `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK datarate (unsigned, in bits per second) + CodR string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + FDev uint16 `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + NCRC bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + IPol bool `json:"ipol"` // Lora modulation polarization inversion + Prea uint16 `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Size uint16 `json:"size"` // RF packet payload size in bytes (unsigned integer) + Data []byte `json:"data"` // Base64 encoded RF packet payload, padding optional +} + +// GetPullRespPacket returns a PullRespPacket for the given gw.DownlinkFrame. +func GetPullRespPacket(protoVersion uint8, randomToken uint16, frame gw.DownlinkFrame) (PullRespPacket, error) { + packet := PullRespPacket{ + ProtocolVersion: protoVersion, + RandomToken: randomToken, + Payload: PullRespPayload{ + TXPK: TXPK{ + Freq: float64(frame.TxInfo.Frequency) / 1000000, + Powe: uint8(frame.TxInfo.Power), + Modu: frame.TxInfo.Modulation.String(), + Size: uint16(len(frame.PhyPayload)), + Data: frame.PhyPayload, + Ant: uint8(frame.TxInfo.Antenna), + Brd: uint32(frame.TxInfo.Board), + }, + }, + } + + if frame.TxInfo.Modulation == common.Modulation_LORA { + modInfo := frame.TxInfo.GetLoraModulationInfo() + if modInfo == nil { + return packet, errors.New("gateway: lora_modulation_info must not be nil") + } + packet.Payload.TXPK.DatR.LoRa = fmt.Sprintf("SF%dBW%d", modInfo.SpreadingFactor, modInfo.Bandwidth) + packet.Payload.TXPK.CodR = modInfo.CodeRate + packet.Payload.TXPK.IPol = modInfo.PolarizationInversion + } + + if frame.TxInfo.Modulation == common.Modulation_FSK { + modInfo := frame.TxInfo.GetFskModulationInfo() + if modInfo == nil { + return packet, errors.New("gateway: fsk_modulation_info must not be nil") + } + packet.Payload.TXPK.DatR.FSK = modInfo.Bitrate + packet.Payload.TXPK.FDev = uint16(modInfo.Bitrate / 2) // TODO: is this correct?! + } + + switch frame.TxInfo.Timing { + case gw.DownlinkTiming_IMMEDIATELY: + packet.Payload.TXPK.Imme = true + + case gw.DownlinkTiming_DELAY: + timingInfo := frame.TxInfo.GetDelayTimingInfo() + if timingInfo == nil { + return packet, errors.New("delay_timing_info must not be nil") + } + + delay, err := ptypes.Duration(timingInfo.Delay) + if err != nil { + return packet, errors.Wrap(err, "get delay duration error") + } + + if len(frame.TxInfo.Context) < 4 { + return packet, fmt.Errorf("context must contain at least 4 bytes, got: %d", len(frame.TxInfo.Context)) + } + timestamp := binary.BigEndian.Uint32(frame.TxInfo.Context[0:4]) + timestamp += uint32(delay / time.Microsecond) + packet.Payload.TXPK.Tmst = ×tamp + + case gw.DownlinkTiming_GPS_EPOCH: + timingInfo := frame.TxInfo.GetGpsEpochTimingInfo() + if timingInfo == nil { + return packet, errors.New("gps_epoch_timing must not be nil") + } + + dur, err := ptypes.Duration(timingInfo.TimeSinceGpsEpoch) + if err != nil { + return packet, errors.Wrap(err, "parse time_since_gps_epoch error") + } + + durMS := int64(dur / time.Millisecond) + packet.Payload.TXPK.Tmms = &durMS + + default: + return packet, fmt.Errorf("unexpected downlink timing: %s", frame.TxInfo.Timing) + } + + return packet, nil +} diff --git a/packets/push_ack.go b/packets/push_ack.go new file mode 100644 index 0000000..20e135d --- /dev/null +++ b/packets/push_ack.go @@ -0,0 +1,39 @@ +package packets + +import ( + "encoding/binary" + "errors" +) + +// PushACKPacket is used by the server to acknowledge immediately all the +// PUSH_DATA packets received. +type PushACKPacket struct { + ProtocolVersion uint8 + RandomToken uint16 +} + +// MarshalBinary marshals the object in binary form. +func (p PushACKPacket) MarshalBinary() ([]byte, error) { + out := make([]byte, 4) + out[0] = p.ProtocolVersion + binary.LittleEndian.PutUint16(out[1:3], p.RandomToken) + out[3] = byte(PushACK) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *PushACKPacket) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("gateway: 4 bytes of data are expected") + } + if data[3] != byte(PushACK) { + return errors.New("gateway: identifier mismatch (PUSH_ACK expected)") + } + + if !protocolSupported(data[0]) { + return ErrInvalidProtocolVersion + } + p.ProtocolVersion = data[0] + p.RandomToken = binary.LittleEndian.Uint16(data[1:3]) + return nil +} diff --git a/packets/push_data.go b/packets/push_data.go new file mode 100644 index 0000000..25972ab --- /dev/null +++ b/packets/push_data.go @@ -0,0 +1,283 @@ +package packets + +import ( + "encoding/binary" + "encoding/json" + "regexp" + "strconv" + "time" + + "github.com/golang/protobuf/ptypes" + "github.com/pkg/errors" + + "github.com/brocaar/loraserver/api/common" + "github.com/brocaar/loraserver/api/gw" + "github.com/brocaar/lorawan" +) + +// loRaDataRateRegex contains a regexp for parsing the data-rate string. +var loRaDataRateRegex = regexp.MustCompile(`SF(\d+)BW(\d+)`) + +// PushDataPacket type is used by the gateway mainly to forward the RF packets +// received, and associated metadata, to the server. +type PushDataPacket struct { + ProtocolVersion uint8 + RandomToken uint16 + GatewayMAC lorawan.EUI64 + Payload PushDataPayload +} + +// MarshalBinary encodes the packet into binary form compatible with the +// Semtech UDP protocol. +func (p PushDataPacket) MarshalBinary() ([]byte, error) { + pb, err := json.Marshal(&p.Payload) + if err != nil { + return nil, err + } + + out := make([]byte, 4, len(pb)+12) + out[0] = p.ProtocolVersion + binary.LittleEndian.PutUint16(out[1:3], p.RandomToken) + out[3] = byte(PushData) + out = append(out, p.GatewayMAC[0:len(p.GatewayMAC)]...) + out = append(out, pb...) + return out, nil +} + +// GetGatewayStats returns the gw.GatewayStats object (if the packet contains stats). +func (p PushDataPacket) GetGatewayStats() (*gw.GatewayStats, error) { + if p.Payload.Stat == nil { + return nil, nil + } + + stats := gw.GatewayStats{ + GatewayId: p.GatewayMAC[:], + RxPacketsReceived: p.Payload.Stat.RXNb, + RxPacketsReceivedOk: p.Payload.Stat.RXOK, + TxPacketsEmitted: p.Payload.Stat.TXNb, + TxPacketsReceived: p.Payload.Stat.DWNb, + } + + // time + ts, err := ptypes.TimestampProto(time.Time(p.Payload.Stat.Time)) + if err != nil { + return nil, errors.Wrap(err, "timestamp proto error") + } + stats.Time = ts + + // location + if p.Payload.Stat.Lati != 0 && p.Payload.Stat.Long != 0 && p.Payload.Stat.Alti != 0 { + stats.Location = &common.Location{ + Latitude: p.Payload.Stat.Lati, + Longitude: p.Payload.Stat.Long, + Altitude: float64(p.Payload.Stat.Alti), + Source: common.LocationSource_GPS, + } + } + + return &stats, nil +} + +// GetUplinkFrames returns a slice of gw.UplinkFrame. +func (p PushDataPacket) GetUplinkFrames(skipCRCCheck bool, FakeRxInfoTime bool) ([]gw.UplinkFrame, error) { + var frames []gw.UplinkFrame + + for i := range p.Payload.RXPK { + // validate CRC + if p.Payload.RXPK[i].Stat != 1 && !skipCRCCheck { + continue + } + + if len(p.Payload.RXPK[i].RSig) == 0 { + frame, err := getUplinkFrame(p.GatewayMAC[:], p.Payload.RXPK[i], FakeRxInfoTime) + if err != nil { + return nil, errors.Wrap(err, "gateway: get uplink frame error") + } + frames = append(frames, frame) + } else { + for j := range p.Payload.RXPK[i].RSig { + frame, err := getUplinkFrame(p.GatewayMAC[:], p.Payload.RXPK[i], FakeRxInfoTime) + if err != nil { + return nil, errors.Wrap(err, "gateway: get uplink frame error") + } + frame = setUplinkFrameRSig(frame, p.Payload.RXPK[i], p.Payload.RXPK[i].RSig[j]) + frames = append(frames, frame) + } + } + } + + return frames, nil +} + +func setUplinkFrameRSig(frame gw.UplinkFrame, rxPK RXPK, rSig RSig) gw.UplinkFrame { + frame.RxInfo.Antenna = uint32(rSig.Ant) + frame.RxInfo.Channel = uint32(rSig.Chan) + frame.RxInfo.Rssi = int32(rSig.RSSIC) + frame.RxInfo.LoraSnr = rSig.LSNR + + if len(rSig.ETime) != 0 { + frame.RxInfo.FineTimestampType = gw.FineTimestampType_ENCRYPTED + frame.RxInfo.FineTimestamp = &gw.UplinkRXInfo_EncryptedFineTimestamp{ + EncryptedFineTimestamp: &gw.EncryptedFineTimestamp{ + EncryptedNs: rSig.ETime, + AesKeyIndex: uint32(rxPK.AESK), + }, + } + } + + return frame +} + +func getUplinkFrame(gatewayID []byte, rxpk RXPK, FakeRxInfoTime bool) (gw.UplinkFrame, error) { + frame := gw.UplinkFrame{ + PhyPayload: rxpk.Data, + TxInfo: &gw.UplinkTXInfo{ + Frequency: uint32(rxpk.Freq * 1000000), + }, + RxInfo: &gw.UplinkRXInfo{ + GatewayId: gatewayID, + Rssi: int32(rxpk.RSSI), + LoraSnr: rxpk.LSNR, + Channel: uint32(rxpk.Chan), + RfChain: uint32(rxpk.RFCh), + Board: uint32(rxpk.Brd), + Context: make([]byte, 4), + }, + } + + // Context + binary.BigEndian.PutUint32(frame.RxInfo.Context, rxpk.Tmst) + + // Time. + if rxpk.Time != nil && !time.Time(*rxpk.Time).IsZero() { + ts, err := ptypes.TimestampProto(time.Time(*rxpk.Time)) + if err != nil { + return frame, errors.Wrap(err, "gateway: timestamp proto error") + } + frame.RxInfo.Time = ts + } else if FakeRxInfoTime { + ts, _ := ptypes.TimestampProto(time.Now().UTC()) + frame.RxInfo.Time = ts + } + + // Time since GPS epoch + if rxpk.Tmms != nil { + d := time.Duration(*rxpk.Tmms) * time.Millisecond + frame.RxInfo.TimeSinceGpsEpoch = ptypes.DurationProto(d) + } + + // LoRa data-rate + if rxpk.DatR.LoRa != "" { + frame.TxInfo.Modulation = common.Modulation_LORA + + match := loRaDataRateRegex.FindStringSubmatch(rxpk.DatR.LoRa) + // parse e.g. SF12BW250 into separate variables + if len(match) != 3 { + return frame, errors.New("gateway: could not parse LoRa data-rate") + } + + // cast variables to ints + sf, err := strconv.Atoi(match[1]) + if err != nil { + return frame, errors.Wrap(err, "gateway: could not convert sf to int") + } + + bw, err := strconv.Atoi(match[2]) + if err != nil { + return frame, errors.Wrap(err, "gateway: could not parse bandwidth to int") + } + + frame.TxInfo.ModulationInfo = &gw.UplinkTXInfo_LoraModulationInfo{ + LoraModulationInfo: &gw.LoRaModulationInfo{ + Bandwidth: uint32(bw), + SpreadingFactor: uint32(sf), + CodeRate: rxpk.CodR, + }, + } + } + + // FSK data-rate + if rxpk.DatR.FSK != 0 { + frame.TxInfo.Modulation = common.Modulation_FSK + + frame.TxInfo.ModulationInfo = &gw.UplinkTXInfo_FskModulationInfo{ + FskModulationInfo: &gw.FSKModulationInfo{ + Bitrate: uint32(rxpk.DatR.FSK), + }, + } + } + + return frame, nil +} + +// UnmarshalBinary decodes the packet from Semtech UDP binary form. +func (p *PushDataPacket) UnmarshalBinary(data []byte) error { + if len(data) < 13 { + return errors.New("gateway: at least 13 bytes are expected") + } + if data[3] != byte(PushData) { + return errors.New("gateway: identifier mismatch (PUSH_DATA expected)") + } + + if !protocolSupported(data[0]) { + return ErrInvalidProtocolVersion + } + + p.ProtocolVersion = data[0] + p.RandomToken = binary.LittleEndian.Uint16(data[1:3]) + for i := 0; i < 8; i++ { + p.GatewayMAC[i] = data[4+i] + } + + return json.Unmarshal(data[12:], &p.Payload) +} + +// PushDataPayload represents the upstream JSON data structure. +type PushDataPayload struct { + RXPK []RXPK `json:"rxpk,omitempty"` + Stat *Stat `json:"stat,omitempty"` +} + +// Stat contains the status of the gateway. +type Stat struct { + Time ExpandedTime `json:"time"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format (e.g 2014-01-12 08:59:28 GMT) + Lati float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) + Long float64 `json:"long"` // GPS latitude of the gateway in degree (float, E is +) + Alti int32 `json:"alti"` // GPS altitude of the gateway in meter RX (integer) + RXNb uint32 `json:"rxnb"` // Number of radio packets received (unsigned integer) + RXOK uint32 `json:"rxok"` // Number of radio packets received with a valid PHY CRC + RXFW uint32 `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) + ACKR float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged + DWNb uint32 `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) + TXNb uint32 `json:"txnb"` // Number of packets emitted (unsigned integer) +} + +// RXPK contain a RF packet and associated metadata. +type RXPK struct { + Time *CompactTime `json:"time"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format (e.g. 2013-03-31T16:21:17.528002Z) + Tmms *int64 `json:"tmms"` // GPS time of pkt RX, number of milliseconds since 06.Jan.1980 + Tmst uint32 `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) + AESK uint8 `json:"aesk"` //AES key index used for encrypting fine timestamps + Chan uint8 `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) + RFCh uint8 `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) + Stat int8 `json:"stat"` // CRC status: 1 = OK, -1 = fail, 0 = no CRC + Freq float64 `json:"freq"` // RX central frequency in MHz (unsigned float, Hz precision) + Brd uint32 `json:"brd"` // Concentrator board used for RX (unsigned integer) + RSSI int16 `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) + Size uint16 `json:"size"` // RF packet payload size in bytes (unsigned integer) + DatR DatR `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK datarate (unsigned, in bits per second) + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + CodR string `json:"codr"` // LoRa ECC coding rate identifier + LSNR float64 `json:"lsnr"` // Lora SNR ratio in dB (signed float, 0.1 dB precision) + Data []byte `json:"data"` // Base64 encoded RF packet payload, padded + RSig []RSig `json:"rsig"` // Received signal information, per antenna (Optional) +} + +// RSig contains the received signal information per antenna. +type RSig struct { + Ant uint8 `json:"ant"` // Antenna number on which signal has been received + Chan uint8 `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) + RSSIC int16 `json:"rssic"` // RSSI in dBm of the channel (signed integer, 1 dB precision) + LSNR float64 `json:"lsnr"` // Lora SNR ratio in dB (signed float, 0.1 dB precision) + ETime []byte `json:"etime"` // Encrypted 'main' fine timestamp, ns precision [0..999999999] (Optional) +} diff --git a/packets/tx_ack.go b/packets/tx_ack.go new file mode 100644 index 0000000..c2a4d6e --- /dev/null +++ b/packets/tx_ack.go @@ -0,0 +1,73 @@ +package packets + +import ( + "encoding/binary" + "encoding/json" + "errors" + + "github.com/brocaar/lorawan" +) + +// TXACKPacket is used by the gateway to send a feedback to the server +// to inform if a downlink request has been accepted or rejected by the +// gateway. +type TXACKPacket struct { + ProtocolVersion uint8 + RandomToken uint16 + GatewayMAC lorawan.EUI64 + Payload *TXACKPayload +} + +// MarshalBinary marshals the object into binary form. +func (p TXACKPacket) MarshalBinary() ([]byte, error) { + var pb []byte + if p.Payload != nil { + var err error + pb, err = json.Marshal(p.Payload) + if err != nil { + return nil, err + } + } + + out := make([]byte, 4, len(pb)+12) + out[0] = p.ProtocolVersion + binary.LittleEndian.PutUint16(out[1:3], p.RandomToken) + out[3] = byte(TXACK) + out = append(out, p.GatewayMAC[:]...) + out = append(out, pb...) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *TXACKPacket) UnmarshalBinary(data []byte) error { + if len(data) < 12 { + return errors.New("gateway: at least 12 bytes of data are expected") + } + if data[3] != byte(TXACK) { + return errors.New("gateway: identifier mismatch (TXACK expected)") + } + if !protocolSupported(data[0]) { + return ErrInvalidProtocolVersion + } + p.ProtocolVersion = data[0] + p.RandomToken = binary.LittleEndian.Uint16(data[1:3]) + for i := 0; i < 8; i++ { + p.GatewayMAC[i] = data[4+i] + } + if len(data) > 13 { // the min payload + the length of at least "{}" + p.Payload = &TXACKPayload{} + return json.Unmarshal(data[12:], p.Payload) + } + return nil +} + +// TXACKPayload contains the TXACKPacket payload. +type TXACKPayload struct { + TXPKACK TXPKACK `json:"txpk_ack"` +} + +// TXPKACK contains the status information of the associated PULL_RESP +// packet. +type TXPKACK struct { + Error string `json:"error"` +} diff --git a/rsrc.syso b/rsrc.syso new file mode 100644 index 0000000000000000000000000000000000000000..8d3a7e60dabe5e6ee45b8200df56ec5e14e13bed GIT binary patch literal 68863 zcmeI&&2QUe9Ki7>Yo)yi0S6A;uq+NyUy`pO`4dX2&rXoLb#6GMosLr1OANNSZ|X!Aa1)2Zg66|aX^&clh}DN$(FTQ>MCD%?&IgN{k(jBk6+T|vrIFB7tEH2&#ayi7F8vq~MmEk$2T`o5$;NE#o} zOf%}5Y;5!>zd5PYqwa$xmub{n?Q{H-9>?om{@CaEr?T%4S+h5qW06hu?#+yPPj5ua zhmt?kgNzm#Q*#hq5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5Ew{cYHDgu)`v4Q zGpEzj)4xi3rq^iuRPWQr3{)8hT^Rw1>9-Q&-{W|{D%Hp8bFRz)>1a;@eJyuh=dX%; za=$(|o6Q0&;y0{2A}O z^Fy}*clrb*oo-Su7ffILB_^Xv0pWZr=Mx6UKA5BKH9O z{g1zf9eZ6T-~TvwSVnrH#Oh7F3vP^b5HxlnfoKj4<36D4AodrE#^s!>p9T7vE%8f! zmv5lFK9_s`<-~57t?JJO63b_U&I5g{tltc?57c$}0{T8KaXpdrr_T8x+xi}%%TDw? zo+Z4`lJQ8! zs}qlH1Q3V>l*-7@@v%+~mA#Qs*HmHsyGq%3;P^qwb63sz+?;7RuI&{|?*6Lz<<8yN zk4z&7Ew^ZuJ=a+^>rP;d$RdS2oQINaiRVto4d&hG_-zx`Zdr-@ING~5$wYFO>*`GS~ z9q-VQ$Sf~?ytKQ#IKQ}P+k5lHJ<}*#?tabMZx}?QYjif>XEcS@j(F?^t6G&ttWaV< zU>t9oZJL6}>Va-MVJOojXzyvFR!f^lmhZR{wYc_!Fs!cR^U*}fwI)hVCQ5#L>t23g zZhko*0mk*3YhO72mgkm2&%eJgW3+QJq>e`VwEr4y&n^AG?H#}7wA*R7QONfkej(q1 jD%VLqzEIX4spfKBJ!x${ZEfZJz_+7ZM$!p2DZl>%0}+#_ literal 0 HcmV?d00001 diff --git a/syso.bat b/syso.bat new file mode 100644 index 0000000..573118c --- /dev/null +++ b/syso.bat @@ -0,0 +1 @@ +rsrc -manifest LoRaDTUMock.manifest -ico dot.ico -o rsrc.syso \ No newline at end of file