From 01d5feed7b66f418e47c6aeb82f2be1c7265d004 Mon Sep 17 00:00:00 2001 From: Timo Volkmann Date: Fri, 11 Dec 2020 01:18:05 +0100 Subject: [PATCH] [WIP] save point --- cmd/server/server.go | 4 +- core/collectors.go | 216 +++++++++++++++++++++++++++++++------------ core/config.go | 8 +- core/http.go | 46 +++++---- core/pipeline.go | 59 +++++++----- core/replay.go | 1 + core/repository.go | 46 +++++++++ core/trackings.go | 106 +++++++++++++++++++++ go.mod | 2 + go.sum | 50 ++++++++++ 10 files changed, 433 insertions(+), 105 deletions(-) create mode 100644 core/replay.go create mode 100644 core/repository.go create mode 100644 core/trackings.go diff --git a/cmd/server/server.go b/cmd/server/server.go index cd26270..feaa4f2 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -23,9 +23,9 @@ func main() { log.Println("initialize processing pipeline") processor := core.NewPipeline(dispatcher, 50, 494) processor.Run() - //collectRoutines(processor) + collectRoutines(processor) log.Println("start http server") - core.FiberListenAndServe(dispatcher) + core.FiberListen(dispatcher) //core.HttpListenAndServe(dispatcher, HTTP_PORT) } diff --git a/core/collectors.go b/core/collectors.go index 2f24378..71ca6e6 100644 --- a/core/collectors.go +++ b/core/collectors.go @@ -2,41 +2,162 @@ package core import ( "fmt" + "github.com/google/uuid" "log" "net" "os" + "time" "git.timovolkmann.de/gyrogpsc/ublox" "go.bug.st/serial" ) -func TcpCollector(proc Processor, tcpPort string) { - log.Println("start tcp collectors") +type Collector interface { + Collect() + Stop() +} - listener, err := net.Listen("tcp", tcpPort) - if err != nil { - fmt.Println("Error listening:", err.Error()) - os.Exit(1) - } - // Close the listener when the application closes. - defer listener.Close() +type CollectorType uint8 - for { - // Listen for an incoming connection. - conn, err := listener.Accept() - if err != nil { - fmt.Println("Error accepting: ", err.Error()) - os.Exit(1) +const ( + SERIAL CollectorType = iota + TCP +) + +var tcpSingleton *tcpCollector + +func NewCollector(typ CollectorType, proc Processor, config *Configuration) Collector { + var coll Collector + switch typ { + case SERIAL: + coll = newSerial(proc, config) + case TCP: + if tcpSingleton == nil { + tcpSingleton = newTcp(proc, config) + } else { + tcpSingleton.SetProcessor(proc) } - log.Println("...new incoming tcp connection...") + coll = tcpSingleton + //case DATABASE: + // if len(trackingId) != 1 { + // panic("only 1 tracking per collector") + // } + // coll = newDbReplay(proc, config, trackingId[0]) + default: + panic("selected collector type not implemented") + } + return coll +} - // Handle connections in a new goroutine. - go jsonHandler(conn, proc) +type serialCollector struct { + active bool + proc Processor + config *Configuration +} + +func (s *serialCollector) Collect() { + go func() { + log.Println("start serial collectors") + mode := &serial.Mode{ + BaudRate: 115200, + } + port, err := serial.Open(s.config.SerialCollectorPort, mode) + if err != nil { + log.Fatalln(err.Error()) + } + defer port.Close() + + decoder := ublox.NewDecoder(port) + + for s.active { + meas, err := decoder.Decode() + if err != nil { + if err.Error() == "NMEA not implemented" { + continue + } + log.Println("serial read err:", err) + break + } + sd, err := ConvertUbxToSensorData(meas) + if err != nil { + log.Println("convert err:", err, meas) + continue + } + if sd == nil { + continue + } + + err = s.proc.Push(sd) + if err != nil { + log.Println("process err:", err, *sd) + continue + } + } + + }() +} + +func (s *serialCollector) Stop() { + panic("implement me") +} + +func newSerial(proc Processor, config *Configuration) *serialCollector { + return &serialCollector{ + active: false, + proc: proc, + config: config, } } +type tcpCollector struct { + active bool + processor Processor + //config *Configuration +} + +func (t *tcpCollector) Collect() { + t.active = true +} + +func (t *tcpCollector) Stop() { + t.active = false +} + +func (t *tcpCollector) SetProcessor(p Processor) { + t.processor = p +} + +func newTcp(proc Processor, config *Configuration) *tcpCollector { + log.Println("start tcp collector") + + listener, err := net.Listen("tcp", config.TcpCollectorPort) + if err != nil { + fmt.Println("Error listening:", err.Error()) + //os.Exit(1) + } + coll := &tcpCollector{ + active: false, + processor: proc, + } + go func() { + for { + // Listen for an incoming connection. + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting: ", err.Error()) + os.Exit(1) + } + log.Println("...new incoming tcp connection...") + + // Handle connections in a new goroutine. + go coll.jsonHandler(conn) + } + }() + return coll +} + // handles incoming tcp connections with json payload. -func jsonHandler(conn net.Conn, proc Processor) { +func (c *tcpCollector) jsonHandler(conn net.Conn) { defer conn.Close() @@ -57,54 +178,27 @@ func jsonHandler(conn net.Conn, proc Processor) { log.Println(err) continue } - - err = proc.Process(sd) - if err != nil { - log.Println(err) + if !c.active { + time.Sleep(50 * time.Millisecond) continue } + err = c.processor.Push(sd) + if err != nil { + log.Fatalln(err) + } } } -func SerialCollector(proc Processor, serialPort string) { - log.Println("start serial collectors") - mode := &serial.Mode{ - BaudRate: 115200, - } - port, err := serial.Open(serialPort, mode) - if err != nil { - log.Fatalln(err.Error()) - } - defer port.Close() +type replayCollector struct{} - decoder := ublox.NewDecoder(port) - - for { - meas, err := decoder.Decode() - if err != nil { - if err.Error() == "NMEA not implemented" { - continue - } - log.Println("serial read err:", err) - break - } - sd, err := ConvertUbxToSensorData(meas) - if err != nil { - log.Println("convert err:", err, meas) - continue - } - if sd == nil { - continue - } - - err = proc.Process(sd) - if err != nil { - log.Println("process err:", err, *sd) - continue - } - } +func (r *replayCollector) Collect() { + panic("implement me") } -func replayCollector(storage string, proc Processor, trackingId int) { - +func (r *replayCollector) Stop() { + panic("implement me") +} + +func newDbReplay(proc Processor, config *Configuration, trackingId uuid.UUID) *replayCollector { + return nil } diff --git a/core/config.go b/core/config.go index bf93f32..b31e71f 100644 --- a/core/config.go +++ b/core/config.go @@ -1,9 +1,11 @@ package core type Configuration struct { - TcpCollectorPort string - SerialCollectorPort string - HttpPort string + TcpCollectorPort string + SerialCollectorPort string + HttpPort string + publishIntervalMs int + syncUpdateIntervalMs int } func LoadConfigYaml() error { diff --git a/core/http.go b/core/http.go index 108304f..b22c896 100644 --- a/core/http.go +++ b/core/http.go @@ -72,7 +72,7 @@ func HttpListenAndServe(sub Subscriber, httpPort string) { log.Fatal(http.ListenAndServe(httpPort, nil)) } -func FiberListenAndServe(sub Subscriber) { +func FiberListen(sub Subscriber) { app := fiber.New() app.Static("/static", "static") @@ -80,17 +80,20 @@ func FiberListenAndServe(sub Subscriber) { app.Get("/", fiberHomeHandler) // Websocket - app.Get("/ws", ws.New(fiberWebsocketHandler)) + app.Get("/ws", ws.New(createFiberWebsocketHandler(sub))) + + // TODO: Get all SerialPorts + app.Get("/serialports") // Tracking persistence controls trackings := app.Group("/trackings") trackings.Get("/") // Get all trackings Metadata - trackings.Post("/") // Initialize new tracking by websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId + trackings.Post("/") // Initialize new tracking, open websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId trackings.Patch("/") // Starts recording trackings.Put("/") // Stops current recording. Returns trackingId if record was successful trackings.Delete("/") // Stops websocket connection, pipelines and collectors - trackings.Get("/:trackingId") // Gets Tracking Metadata by Id. + trackings.Get("/:trackingId") // Gets Tracking Metadata and loads sensorRecords from database. trackings.Post("/:trackingId") // Starts Replay. trackings.Patch("/:trackingId") // Pauses Replay. trackings.Put("/:trackingId") // Stops Replay. @@ -99,20 +102,31 @@ func FiberListenAndServe(sub Subscriber) { log.Fatal(app.Listen(":3011")) } -func fiberWebsocketHandler(c *ws.Conn) { - for { - mt, msg, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - break - } - log.Printf("recv: %s", msg) - err = c.WriteMessage(mt, msg) - if err != nil { - log.Println("write:", err) - break +func createFiberWebsocketHandler(s Subscriber) func(conn *ws.Conn) { + return func(c *ws.Conn) { + // Handle and discard inbound messages + go func() { + for { + if _, _, err := c.NextReader(); err != nil { + c.Close() + break + } + } + }() + + dispatcherId, channel := s.Subscribe() + defer s.Unsubscribe(dispatcherId) + for { + cmsg := <-channel + err := c.WriteMessage(ws.TextMessage, []byte(cmsg)) + if err != nil { + log.Println("write:", err) + + break + } } } + } func fiberHomeHandler(c *fiber.Ctx) error { diff --git a/core/pipeline.go b/core/pipeline.go index bd7efd1..7ce3018 100644 --- a/core/pipeline.go +++ b/core/pipeline.go @@ -10,42 +10,59 @@ import ( ) type Processor interface { - Process(data *Sensordata) error + Push(data *Sensordata) error +} + +type Pipeline interface { + Run() + Stop() } type Storer interface { - Persist() + Store(data Sensordata) } type pipeline struct { - syn synchronizer - agr aggregator - pub Publisher - //stor Storer + active bool + syn synchronizer + agr aggregator + pub Publisher + stor Storer publishTicker *time.Ticker } -func NewPipeline(d Publisher, publishIntervalMs int, syncUpdateIntervalMs int) *pipeline { - // TODO: replace timing params with config struct +// pipe implements Runner & Processor +func NewPipeline(d Publisher, s Storer, conf *Configuration) *pipeline { return &pipeline{ + false, synchronizer{ //bufferSize: 100, mutex: &sync.Mutex{}, - updateTicker: time.NewTicker(time.Duration(syncUpdateIntervalMs) * time.Millisecond), + updateTicker: time.NewTicker(time.Duration(conf.syncUpdateIntervalMs) * time.Millisecond), }, aggregator{ tcpMutex: &sync.Mutex{}, serialMutex: &sync.Mutex{}, }, d, - time.NewTicker(time.Duration(publishIntervalMs) * time.Millisecond), + s, + time.NewTicker(time.Duration(conf.publishIntervalMs) * time.Millisecond), } } func (p *pipeline) Run() { - go p.scheduleSynchronizer() go func() { - for { + for p.active { + <-p.syn.updateTicker.C + err := p.refreshDelay() + if err != nil { + log.Println(err) + } + } + + }() + go func() { + for p.active { <-p.publishTicker.C err := p.Publish() if err != nil && err.Error() != "no data available" { @@ -53,7 +70,7 @@ func (p *pipeline) Run() { } } }() - log.Println("pipeline: processing service started") + log.Println("pipe: processing service started") } func (p *pipeline) Publish() error { @@ -109,14 +126,6 @@ type synchronizer struct { } func (p *pipeline) scheduleSynchronizer() { - log.Println("synchronizer: started") - for { - <-p.syn.updateTicker.C - err := p.refreshDelay() - if err != nil { - log.Println(err) - } - } } func (p *pipeline) refreshDelay() error { @@ -144,7 +153,7 @@ func (p *pipeline) refreshDelay() error { return nil } -func (p *pipeline) Process(data *Sensordata) error { +func (p *pipeline) Push(data *Sensordata) error { if data == nil { return errors.New("nil processing not allowed") } @@ -156,7 +165,7 @@ func (p *pipeline) Process(data *Sensordata) error { case SOURCE_SERIAL: go p.pushSerialDataToBuffer(*data) default: - panic("pipeline: invalid data source") + panic("pipe: invalid data source") } return nil } @@ -177,3 +186,7 @@ func (p *pipeline) pushSerialDataToBuffer(data Sensordata) { p.agr.serialSensorData = p.agr.serialSensorData.ConsolidateEpochsOnly(data) p.agr.serialMutex.Unlock() } + +func (p *pipeline) Stop() { + p.active = false +} diff --git a/core/replay.go b/core/replay.go new file mode 100644 index 0000000..9a8bc95 --- /dev/null +++ b/core/replay.go @@ -0,0 +1 @@ +package core diff --git a/core/repository.go b/core/repository.go new file mode 100644 index 0000000..d3c1fae --- /dev/null +++ b/core/repository.go @@ -0,0 +1,46 @@ +package core + +import ( + "github.com/google/uuid" + "sync" +) + +type writeRepo interface { + SaveTracking(tracking *Tracking) error +} + +type readRepo interface { + AllTrackings() ([]trackingMetadata, error) + LoadTracking(id uuid.UUID) (Tracking, error) +} + +type repository struct { + repoStubSave + loader repoStubLoad +} + +type repoStubSave struct { + sensordataBuffer +} +type repoStubLoad struct{} + +func (r *repoStubSave) SaveTracking(tracking *Tracking) error { + panic("implement me") +} + +func (r *repoStubLoad) AllTrackings() ([]trackingMetadata, error) { + panic("implement me") +} + +func (r *repoStubLoad) LoadTracking(id uuid.UUID) (Tracking, error) { + panic("implement me") +} + +type sensordataBuffer struct { + // TODO: database stub + data []Sensordata + mu sync.Mutex +} + +func (s *sensordataBuffer) Store(data Sensordata) { +} diff --git a/core/trackings.go b/core/trackings.go new file mode 100644 index 0000000..1d7a28b --- /dev/null +++ b/core/trackings.go @@ -0,0 +1,106 @@ +package core + +import ( + "github.com/google/uuid" + "log" + "time" +) + +type OpMode uint8 + +const ( + STOPPED OpMode = iota + LIVE + REPLAY +) + +type TrackingService struct { + Current *Tracking + config *Configuration + repo *repository + pipe *pipeline + opMode OpMode + collectors []Collector + //publish Publisher +} + +type Tracking struct { + trackingMetadata + data []*sensorRecord +} + +type trackingMetadata struct { + UUID uuid.UUID + TimeCreated time.Time +} + +type sensorRecord struct { + RecordTime time.Time + Sensordata +} + +func Service(c *Configuration) *TrackingService { + d := NewDispatcher() + r := &repository{} + return &TrackingService{ + Current: nil, + opMode: STOPPED, + config: c, + repo: r, + pipe: NewPipeline(d, r, c), + collectors: nil, + } +} + +func (t *TrackingService) NewTracking(cols ...CollectorType) { + t.opMode = LIVE + for _, col := range cols { + t.collectors = append(t.collectors, NewCollector(col, t.pipe, t.config)) + } + t.Current = emptyTracking() +} + +func (t *TrackingService) StartRecord() { + if t.opMode != LIVE { + log.Println("trackingservice: wrong mode of operation") + } + t.pipe.Run() + for _, e := range t.collectors { + e.Collect() + } +} + +func (t *TrackingService) StopRecord() { + if t.opMode != LIVE { + log.Println("trackingservice: wrong mode of operation") + } + t.pipe.Stop() + for _, e := range t.collectors { + e.Stop() + } + err := t.repo.SaveTracking(t.Current) + if err != nil { + log.Println(err) + } + t.Current = emptyTracking() +} + +func (t *TrackingService) Reset() { +} + +func (t *TrackingService) LoadTrackings() { + +} + +func (t *TrackingService) LoadTracking(trackingId uuid.UUID) { + +} + +func emptyTracking() *Tracking { + return &Tracking{ + trackingMetadata: trackingMetadata{ + UUID: uuid.New(), + }, + data: []*sensorRecord{}, + } +} diff --git a/go.mod b/go.mod index 3edfef5..1872bc9 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,11 @@ go 1.15 require ( github.com/gofiber/fiber/v2 v2.2.4 github.com/gofiber/websocket/v2 v2.0.2 + github.com/google/uuid v1.1.2 github.com/gorilla/websocket v1.4.2 github.com/stretchr/testify v1.6.1 // indirect github.com/tidwall/gjson v1.6.0 github.com/tidwall/pretty v1.0.2 // indirect + github.com/upper/db/v4 v4.0.1 // indirect go.bug.st/serial v1.1.1 ) diff --git a/go.sum b/go.sum index 5179a37..da3fa22 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,44 @@ +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fasthttp/websocket v1.4.3 h1:qjhRJ/rTy4KB8oBxljEC00SDt6HUY9jLRfM601SUdS4= github.com/fasthttp/websocket v1.4.3/go.mod h1:5r4oKssgS7W6Zn6mPWap3NWzNPJNzUUh3baWTOhcYQk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/fiber/v2 v2.1.0/go.mod h1:aG+lMkwy3LyVit4CnmYUbUdgjpc3UYOltvlJZ78rgQ0= github.com/gofiber/fiber/v2 v2.2.4 h1:t2V2SxlbQGdt8+SS/Mo+tQB0pDQn7OajKdA72qHcBVw= github.com/gofiber/fiber/v2 v2.2.4/go.mod h1:Aso7/M+EQOinVkWp4LUYjdlTpKTBoCk2Qo4djnMsyHE= github.com/gofiber/websocket/v2 v2.0.2 h1:UA/6NpyG+vmPGlvJvW8MJPJpRFuS7abinZ5HbLuV8u0= github.com/gofiber/websocket/v2 v2.0.2/go.mod h1:7VBnzEVRK0K0eTIVc5GbXPF1JWUFnllY0X4cRtG2v78= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.1 h1:AHx9Ra40wIzl+GelgX2X6AWxmT5tfxhI1PL0523HcSw= +github.com/mattn/go-sqlite3 v1.14.1/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 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/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/savsgio/gotils v0.0.0-20200608150037-a5f6f5aef16c h1:2nF5+FZ4/qp7pZVL7fR6DEaSTzuDmNaFTyqp92/hwF8= github.com/savsgio/gotils v0.0.0-20200608150037-a5f6f5aef16c/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= @@ -32,6 +50,10 @@ github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/upper/db v1.0.1 h1:AFzy0fFO7EZ5woZGE/LVl9BWLKqyAWlglu+ZyHQTlbU= +github.com/upper/db v3.7.1+incompatible h1:ehR+Q0msV25yZWXdFYD+3n4BvY9jefI336lmZy7FlAQ= +github.com/upper/db/v4 v4.0.1 h1:mHmGBffne8fdiJjmtYXCuHSghO9027q4TJA9oGP50OM= +github.com/upper/db/v4 v4.0.1/go.mod h1:pyAEIpPfnhpvO4zVbyUI+asgZjppZlq705fOFTyUOso= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE= @@ -40,16 +62,27 @@ github.com/valyala/fasthttp v1.17.0 h1:P8/koH4aSnJ4xbd0cUUFEGQs3jQqIxoDDyRQrUiAk github.com/valyala/fasthttp v1.17.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +gitlab.com/cznic/ebnf2y v1.0.0/go.mod h1:jx14dqOldV2pRvSi8HASTB/k5fkIv2TwjYAp5py0MTs= +gitlab.com/cznic/golex v1.0.0/go.mod h1:vkWdDgqbbThjRHoOLU7yNPgMxaubAkwnvF/4zeG8cvU= go.bug.st/serial v1.1.1 h1:5J1DpaIaSIruBi7jVnKXnhRS+YQ9+2PLJMtIZKoIgnc= go.bug.st/serial v1.1.1/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY= 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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190411193353-0480eff6dd7c/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -60,9 +93,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= +modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= +modernc.org/ebnfutil v1.0.0/go.mod h1:+2n/OnQXoild9pzrPa/2wmVtR+ufWjB/0fYkc0BV9sc= +modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= +modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= +modernc.org/lex v1.0.0/go.mod h1:G6rxMTy3cH2iA0iXL/HRRv4Znu8MK4higxph/lE7ypk= +modernc.org/lexer v1.0.0/go.mod h1:F/Dld0YKYdZCLQ7bD0USbWL4YKCyTDRDHiDTOs0q0vk= +modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/ql v1.1.0/go.mod h1:Fj1ylcVyzcu/fgWZTrvBO9j/aEUg/ixLFnGtmzh7quI= +modernc.org/sortutil v1.0.0/go.mod h1:1QO0q8IlIlmjBIwm6t/7sof874+xCfZouyqZMLIAtxM= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=