[WIP] save point

This commit is contained in:
Timo Volkmann 2020-12-11 01:18:05 +01:00
parent 2a9c3a1153
commit 01d5feed7b
10 changed files with 433 additions and 105 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

1
core/replay.go Normal file
View File

@ -0,0 +1 @@
package core

46
core/repository.go Normal file
View File

@ -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) {
}

106
core/trackings.go Normal file
View File

@ -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{},
}
}

2
go.mod
View File

@ -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
)

50
go.sum
View File

@ -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=