3 Commits

Author SHA1 Message Date
joy.zhou
8eb18410d6 fixbug 2019-11-11 11:40:48 +08:00
joy.zhou
716614e626 update auth file 2019-10-30 14:43:36 +08:00
joyz
a02007ce6b update 2019-10-27 08:58:41 +08:00
36 changed files with 629 additions and 1938 deletions

View File

@@ -1,18 +0,0 @@
name: MacOS build
on: [push, pull_request]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Build
run: go build -v ./...

View File

@@ -1,18 +0,0 @@
name: Ubuntu build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Build
run: go build -v ./...

View File

@@ -1,18 +0,0 @@
name: Windows build
on: [push, pull_request]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Build
run: go build -v ./...

7
.gitignore vendored
View File

@@ -1,9 +1,4 @@
hmq hmq
log log
log/* log/*
*.test *.test
# ide
.idea
.vscode/settings.json
.pre-commit-config.yaml
hmq.exe

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"go.lintFlags": [
"--disable=all",
"--enable=errcheck,varcheck,deadcode",
"--enable=varcheck",
"--enable=deadcode"
]
}

View File

@@ -1,12 +1,12 @@
FROM golang:1.18 as builder FROM golang:1.12 as builder
WORKDIR /go/src/github.com/fhmq/hmq WORKDIR /go/src/github.com/fhmq/hmq
COPY . . COPY . .
RUN CGO_ENABLED=0 go build -o hmq -a -ldflags '-extldflags "-static"' . RUN CGO_ENABLED=0 go build -o hmq -a -ldflags '-extldflags "-static"' .
FROM alpine FROM alpine:3.8
WORKDIR / WORKDIR /
COPY --from=builder /go/src/github.com/fhmq/hmq/hmq . COPY --from=builder /go/src/github.com/fhmq/hmq/hmq .
EXPOSE 1883 EXPOSE 1883
ENTRYPOINT ["/hmq"] CMD ["/hmq"]

View File

@@ -1,7 +1,3 @@
![build](https://img.shields.io/github/workflow/status/fhmq/hmq/Ubuntu%20build?label=Ubuntu&style=for-the-badge)
![build](https://img.shields.io/github/workflow/status/fhmq/hmq/MacOS%20build?label=MacOS&style=for-the-badge)
![build](https://img.shields.io/github/workflow/status/fhmq/hmq/Windows%20build?label=Windows&style=for-the-badge)
Free and High Performance MQTT Broker Free and High Performance MQTT Broker
============ ============
@@ -85,7 +81,7 @@ Common Options:
* TLS/SSL Support * TLS/SSL Support
* Auth Support * AuthHTTP Support
* Auth Connect * Auth Connect
* Auth ACL * Auth ACL
* Cache Support * Cache Support
@@ -139,7 +135,7 @@ Other Version Of Cluster Based On gRPC: [click here](https://github.com/fhmq/rhm
## Reference ## Reference
* Surgermq.(https://github.com/zentures/surgemq) * Surgermq.(https://github.com/surgemq/surgemq)
## Benchmark Tool ## Benchmark Tool

View File

@@ -1,3 +1,5 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import ( import (

View File

@@ -5,13 +5,11 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func (b *Broker) Publish(e *bridge.Elements) bool { func (b *Broker) Publish(e *bridge.Elements) {
if b.bridgeMQ != nil { if b.bridgeMQ != nil {
cost, err := b.bridgeMQ.Publish(e) err := b.bridgeMQ.Publish(e)
if err != nil { if err != nil {
log.Error("send message to mq error.", zap.Error(err)) log.Error("send message to mq error.", zap.Error(err))
} }
return cost
} }
return false
} }

View File

@@ -1,3 +1,5 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import ( import (
@@ -5,18 +7,21 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"runtime/debug"
"sync" "sync"
"time" "time"
"github.com/fhmq/hmq/plugins/bridge"
"github.com/fhmq/hmq/plugins/auth"
"github.com/fhmq/hmq/broker/lib/sessions" "github.com/fhmq/hmq/broker/lib/sessions"
"github.com/fhmq/hmq/broker/lib/topics" "github.com/fhmq/hmq/broker/lib/topics"
"github.com/fhmq/hmq/plugins/auth"
"github.com/fhmq/hmq/plugins/bridge"
"github.com/fhmq/hmq/pool"
"github.com/eclipse/paho.mqtt.golang/packets" "github.com/eclipse/paho.mqtt.golang/packets"
"github.com/fhmq/hmq/pool"
"github.com/shirou/gopsutil/mem"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
@@ -56,37 +61,6 @@ func newMessagePool() []chan *Message {
return pool return pool
} }
func getAdditionalLogFields(clientIdentifier string, conn net.Conn, additionalFields ...zapcore.Field) []zapcore.Field {
var wsConn *websocket.Conn = nil
var wsEnabled bool
result := []zapcore.Field{}
switch conn.(type) {
case *websocket.Conn:
wsEnabled = true
wsConn = conn.(*websocket.Conn)
case *net.TCPConn:
wsEnabled = false
}
// add optional fields
if len(additionalFields) > 0 {
result = append(result, additionalFields...)
}
// add client ID
result = append(result, zap.String("clientID", clientIdentifier))
// add remote connection address
if !wsEnabled && conn != nil && conn.RemoteAddr() != nil {
result = append(result, zap.Stringer("addr", conn.RemoteAddr()))
} else if wsEnabled && wsConn != nil && wsConn.Request() != nil {
result = append(result, zap.String("addr", wsConn.Request().RemoteAddr))
}
return result
}
func NewBroker(config *Config) (*Broker, error) { func NewBroker(config *Config) (*Broker, error) {
if config == nil { if config == nil {
config = DefaultConfig config = DefaultConfig
@@ -122,8 +96,8 @@ func NewBroker(config *Config) (*Broker, error) {
b.tlsConfig = tlsconfig b.tlsConfig = tlsconfig
} }
b.auth = b.config.Plugin.Auth b.auth = auth.NewAuth(b.config.Plugin.Auth)
b.bridgeMQ = b.config.Plugin.Bridge b.bridgeMQ = bridge.NewBridgeMQ(b.config.Plugin.Bridge)
return b, nil return b, nil
} }
@@ -153,7 +127,7 @@ func (b *Broker) Start() {
go InitHTTPMoniter(b) go InitHTTPMoniter(b)
} }
//listen client over tcp //listen clinet over tcp
if b.config.Port != "" { if b.config.Port != "" {
go b.StartClientListening(false) go b.StartClientListening(false)
} }
@@ -179,23 +153,37 @@ func (b *Broker) Start() {
b.ConnectToDiscovery() b.ConnectToDiscovery()
} }
//system monitor
go StateMonitor()
}
func StateMonitor() {
v, _ := mem.VirtualMemory()
timeSticker := time.NewTicker(time.Second * 30)
for {
select {
case <-timeSticker.C:
if v.UsedPercent > 75 {
debug.FreeOSMemory()
}
}
}
} }
func (b *Broker) StartWebsocketListening() { func (b *Broker) StartWebsocketListening() {
path := b.config.WsPath path := b.config.WsPath
hp := ":" + b.config.WsPort hp := ":" + b.config.WsPort
log.Info("Start Websocket Listener on:", zap.String("hp", hp), zap.String("path", path)) log.Info("Start Websocket Listener on:", zap.String("hp", hp), zap.String("path", path))
ws := &websocket.Server{Handler: websocket.Handler(b.wsHandler)} http.Handle(path, websocket.Handler(b.wsHandler))
mux := http.NewServeMux()
mux.Handle(path, ws)
var err error var err error
if b.config.WsTLS { if b.config.WsTLS {
err = http.ListenAndServeTLS(hp, b.config.TlsInfo.CertFile, b.config.TlsInfo.KeyFile, mux) err = http.ListenAndServeTLS(hp, b.config.TlsInfo.CertFile, b.config.TlsInfo.KeyFile, nil)
} else { } else {
err = http.ListenAndServe(hp, mux) err = http.ListenAndServe(hp, nil)
} }
if err != nil { if err != nil {
log.Error("ListenAndServe" + err.Error()) log.Error("ListenAndServe:" + err.Error())
return return
} }
} }
@@ -207,54 +195,71 @@ func (b *Broker) wsHandler(ws *websocket.Conn) {
} }
func (b *Broker) StartClientListening(Tls bool) { func (b *Broker) StartClientListening(Tls bool) {
var hp string
var err error var err error
var l net.Listener var l net.Listener
// Retry listening indefinitely so that specifying IP addresses if Tls {
// (e.g. --host=10.0.0.217) starts working once the IP address is actually hp = b.config.TlsHost + ":" + b.config.TlsPort
// configured on the interface. l, err = tls.Listen("tcp", hp, b.tlsConfig)
for { log.Info("Start TLS Listening client on ", zap.String("hp", hp))
if Tls { } else {
hp := b.config.TlsHost + ":" + b.config.TlsPort hp := b.config.Host + ":" + b.config.Port
l, err = tls.Listen("tcp", hp, b.tlsConfig) l, err = net.Listen("tcp", hp)
log.Info("Start TLS Listening client on ", zap.String("hp", hp)) log.Info("Start Listening client on ", zap.String("hp", hp))
} else { }
hp := b.config.Host + ":" + b.config.Port if err != nil {
l, err = net.Listen("tcp", hp) log.Error("Error listening on ", zap.Error(err))
log.Info("Start Listening client on ", zap.String("hp", hp)) return
}
if err == nil {
break // successfully listening
}
log.Error("Error listening on ", zap.Error(err))
time.Sleep(1 * time.Second)
} }
tmpDelay := 10 * ACCEPT_MIN_SLEEP tmpDelay := 10 * ACCEPT_MIN_SLEEP
for { for {
conn, err := l.Accept() conn, err := l.Accept()
if err != nil { if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() { if ne, ok := err.(net.Error); ok && ne.Temporary() {
log.Error( log.Error("Temporary Client Accept Error(%v), sleeping %dms",
"Temporary Client Accept Error(%v), sleeping %dms", zap.Error(ne), zap.Duration("sleeping", tmpDelay/time.Millisecond))
zap.Error(ne),
zap.Duration("sleeping", tmpDelay/time.Millisecond),
)
time.Sleep(tmpDelay) time.Sleep(tmpDelay)
tmpDelay *= 2 tmpDelay *= 2
if tmpDelay > ACCEPT_MAX_SLEEP { if tmpDelay > ACCEPT_MAX_SLEEP {
tmpDelay = ACCEPT_MAX_SLEEP tmpDelay = ACCEPT_MAX_SLEEP
} }
} else { } else {
log.Error("Accept error", zap.Error(err)) log.Error("Accept error: %v", zap.Error(err))
} }
continue continue
} }
tmpDelay = ACCEPT_MIN_SLEEP tmpDelay = ACCEPT_MIN_SLEEP
go b.handleConnection(CLIENT, conn) go b.handleConnection(CLIENT, conn)
}
}
func (b *Broker) Handshake(conn net.Conn) bool {
nc := tls.Server(conn, b.tlsConfig)
time.AfterFunc(DEFAULT_TLS_TIMEOUT, func() { TlsTimeout(nc) })
nc.SetReadDeadline(time.Now().Add(DEFAULT_TLS_TIMEOUT))
// Force handshake
if err := nc.Handshake(); err != nil {
log.Error("TLS handshake error, ", zap.Error(err))
return false
}
nc.SetReadDeadline(time.Time{})
return true
}
func TlsTimeout(conn *tls.Conn) {
nc := conn
// Check if already closed
if nc == nil {
return
}
cs := nc.ConnectionState()
if !cs.HandshakeComplete {
log.Error("TLS handshake timeout")
nc.Close()
} }
} }
@@ -264,7 +269,7 @@ func (b *Broker) StartClusterListening() {
l, e := net.Listen("tcp", hp) l, e := net.Listen("tcp", hp)
if e != nil { if e != nil {
log.Error("Error listening on", zap.Error(e)) log.Error("Error listening on ", zap.Error(e))
return return
} }
@@ -273,19 +278,15 @@ func (b *Broker) StartClusterListening() {
conn, err := l.Accept() conn, err := l.Accept()
if err != nil { if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() { if ne, ok := err.(net.Error); ok && ne.Temporary() {
log.Error( log.Error("Temporary Client Accept Error(%v), sleeping %dms",
"Temporary Client Accept Error(%v), sleeping %dms", zap.Error(ne), zap.Duration("sleeping", tmpDelay/time.Millisecond))
zap.Error(ne),
zap.Duration("sleeping", tmpDelay/time.Millisecond),
)
time.Sleep(tmpDelay) time.Sleep(tmpDelay)
tmpDelay *= 2 tmpDelay *= 2
if tmpDelay > ACCEPT_MAX_SLEEP { if tmpDelay > ACCEPT_MAX_SLEEP {
tmpDelay = ACCEPT_MAX_SLEEP tmpDelay = ACCEPT_MAX_SLEEP
} }
} else { } else {
log.Error("Accept error", zap.Error(err)) log.Error("Accept error: %v", zap.Error(err))
} }
continue continue
} }
@@ -295,24 +296,11 @@ func (b *Broker) StartClusterListening() {
} }
} }
func (b *Broker) DisConnClientByClientId(clientId string) {
cli, loaded := b.clients.LoadAndDelete(clientId)
if !loaded {
return
}
conn, success := cli.(*client)
if !success {
return
}
conn.Close()
}
func (b *Broker) handleConnection(typ int, conn net.Conn) { func (b *Broker) handleConnection(typ int, conn net.Conn) {
//process connect packet //process connect packet
packet, err := packets.ReadPacket(conn) packet, err := packets.ReadPacket(conn)
if err != nil { if err != nil {
log.Error("read connect packet error", zap.Error(err)) log.Error("read connect packet error: ", zap.Error(err))
conn.Close()
return return
} }
if packet == nil { if packet == nil {
@@ -325,38 +313,46 @@ func (b *Broker) handleConnection(typ int, conn net.Conn) {
return return
} }
log.Info("read connect from ", getAdditionalLogFields(msg.ClientIdentifier, conn)...) log.Info("read connect from ", zap.String("clientID", msg.ClientIdentifier))
connack := packets.NewControlPacket(packets.Connack).(*packets.ConnackPacket) connack := packets.NewControlPacket(packets.Connack).(*packets.ConnackPacket)
connack.SessionPresent = msg.CleanSession connack.SessionPresent = msg.CleanSession
connack.ReturnCode = msg.Validate() connack.ReturnCode = msg.Validate()
if connack.ReturnCode != packets.Accepted { if connack.ReturnCode != packets.Accepted {
func() { err = connack.Write(conn)
defer conn.Close() if err != nil {
if err := connack.Write(conn); err != nil { log.Error("send connack error, ", zap.Error(err), zap.String("clientID", msg.ClientIdentifier))
log.Error("send connack error", getAdditionalLogFields(msg.ClientIdentifier, conn, zap.Error(err))...) return
} }
}()
return return
} }
if typ == CLIENT && !b.CheckConnectAuth(msg.ClientIdentifier, msg.Username, string(msg.Password)) { if typ == CLIENT && !b.CheckConnectAuth(string(msg.ClientIdentifier), string(msg.Username), string(msg.Password)) {
connack.ReturnCode = packets.ErrRefusedNotAuthorised connack.ReturnCode = packets.ErrRefusedNotAuthorised
func() { err = connack.Write(conn)
defer conn.Close() if err != nil {
if err := connack.Write(conn); err != nil { log.Error("send connack error, ", zap.Error(err), zap.String("clientID", msg.ClientIdentifier))
log.Error("send connack error", getAdditionalLogFields(msg.ClientIdentifier, conn, zap.Error(err))...) return
} }
}()
return return
} }
if err := connack.Write(conn); err != nil { err = connack.Write(conn)
log.Error("send connack error", getAdditionalLogFields(msg.ClientIdentifier, conn, zap.Error(err))...) if err != nil {
log.Error("send connack error, ", zap.Error(err), zap.String("clientID", msg.ClientIdentifier))
return return
} }
if typ == CLIENT {
b.Publish(&bridge.Elements{
ClientID: string(msg.ClientIdentifier),
Username: string(msg.Username),
Action: bridge.Connect,
Timestamp: time.Now().Unix(),
})
}
willmsg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) willmsg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket)
if msg.WillFlag { if msg.WillFlag {
willmsg.Qos = msg.WillQos willmsg.Qos = msg.WillQos
@@ -384,41 +380,36 @@ func (b *Broker) handleConnection(typ int, conn net.Conn) {
c.init() c.init()
if err := b.getSession(c, msg, connack); err != nil { err = b.getSession(c, msg, connack)
log.Error("get session error", getAdditionalLogFields(c.info.clientID, conn, zap.Error(err))...) if err != nil {
log.Error("get session error: ", zap.String("clientID", c.info.clientID))
return return
} }
cid := c.info.clientID cid := c.info.clientID
var exists bool var exist bool
var old interface{} var old interface{}
switch typ { switch typ {
case CLIENT: case CLIENT:
old, exists = b.clients.Load(cid) old, exist = b.clients.Load(cid)
if exists { if exist {
if ol, ok := old.(*client); ok { log.Warn("client exist, close old...", zap.String("clientID", c.info.clientID))
log.Warn("client exists, close old client", getAdditionalLogFields(ol.info.clientID, ol.conn)...) ol, ok := old.(*client)
if ok {
ol.Close() ol.Close()
} }
} }
b.clients.Store(cid, c) b.clients.Store(cid, c)
b.OnlineOfflineNotification(cid, true) b.OnlineOfflineNotification(cid, true)
{
b.Publish(&bridge.Elements{
ClientID: msg.ClientIdentifier,
Username: msg.Username,
Action: bridge.Connect,
Timestamp: time.Now().Unix(),
})
}
case ROUTER: case ROUTER:
old, exists = b.routes.Load(cid) old, exist = b.routes.Load(cid)
if exists { if exist {
if ol, ok := old.(*client); ok { log.Warn("router exist, close old...")
log.Warn("router exists, close old router", getAdditionalLogFields(ol.info.clientID, ol.conn)...) ol, ok := old.(*client)
if ok {
ol.Close() ol.Close()
} }
} }
@@ -435,8 +426,8 @@ func (b *Broker) ConnectToDiscovery() {
for { for {
conn, err = net.Dial("tcp", b.config.Router) conn, err = net.Dial("tcp", b.config.Router)
if err != nil { if err != nil {
log.Error("Error trying to connect to route", zap.Error(err)) log.Error("Error trying to connect to route: ", zap.Error(err))
log.Debug("Connect to route timeout, retry...") log.Debug("Connect to route timeout ,retry...")
if 0 == tempDelay { if 0 == tempDelay {
tempDelay = 1 * time.Second tempDelay = 1 * time.Second
@@ -452,7 +443,7 @@ func (b *Broker) ConnectToDiscovery() {
} }
break break
} }
log.Debug("connect to router success", zap.String("Router", b.config.Router)) log.Debug("connect to router success :", zap.String("Router", b.config.Router))
cid := b.id cid := b.id
info := info{ info := info{
@@ -502,13 +493,13 @@ func (b *Broker) connectRouter(id, addr string) {
conn, err = net.Dial("tcp", addr) conn, err = net.Dial("tcp", addr)
if err != nil { if err != nil {
log.Error("Error trying to connect to route", zap.Error(err)) log.Error("Error trying to connect to route: ", zap.Error(err))
if retryTimes > 50 { if retryTimes > 50 {
return return
} }
log.Debug("Connect to route timeout, retry...") log.Debug("Connect to route timeout ,retry...")
if 0 == timeDelay { if 0 == timeDelay {
timeDelay = 1 * time.Second timeDelay = 1 * time.Second
@@ -548,6 +539,7 @@ func (b *Broker) connectRouter(id, addr string) {
c.SendConnect() c.SendConnect()
// mpool := b.messagePool[fnv1a.HashString64(cid)%MessagePoolNum]
go c.readLoop() go c.readLoop()
go c.StartPing() go c.StartPing()
@@ -576,71 +568,71 @@ func (b *Broker) checkNodeExist(id, url string) bool {
} }
func (b *Broker) CheckRemoteExist(remoteID, url string) bool { func (b *Broker) CheckRemoteExist(remoteID, url string) bool {
exists := false exist := false
b.remotes.Range(func(key, value interface{}) bool { b.remotes.Range(func(key, value interface{}) bool {
v, ok := value.(*client) v, ok := value.(*client)
if ok { if ok {
if v.route.remoteUrl == url { if v.route.remoteUrl == url {
v.route.remoteID = remoteID v.route.remoteID = remoteID
exists = true exist = true
return false return false
} }
} }
return true return true
}) })
return exists return exist
} }
func (b *Broker) SendLocalSubsToRouter(c *client) { func (b *Broker) SendLocalSubsToRouter(c *client) {
subInfo := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket) subInfo := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket)
b.clients.Range(func(key, value interface{}) bool { b.clients.Range(func(key, value interface{}) bool {
client, ok := value.(*client) client, ok := value.(*client)
if !ok { if ok {
return true subs := client.subMap
for _, sub := range subs {
subInfo.Topics = append(subInfo.Topics, sub.topic)
subInfo.Qoss = append(subInfo.Qoss, sub.qos)
}
} }
client.subMapMu.RLock()
defer client.subMapMu.RUnlock()
subs := client.subMap
for _, sub := range subs {
subInfo.Topics = append(subInfo.Topics, sub.topic)
subInfo.Qoss = append(subInfo.Qoss, sub.qos)
}
return true return true
}) })
if len(subInfo.Topics) > 0 { if len(subInfo.Topics) > 0 {
if err := c.WriterPacket(subInfo); err != nil { err := c.WriterPacket(subInfo)
log.Error("Send localsubs To Router error", zap.Error(err)) if err != nil {
log.Error("Send localsubs To Router error :", zap.Error(err))
} }
} }
} }
func (b *Broker) BroadcastInfoMessage(remoteID string, msg *packets.PublishPacket) { func (b *Broker) BroadcastInfoMessage(remoteID string, msg *packets.PublishPacket) {
b.routes.Range(func(key, value interface{}) bool { b.routes.Range(func(key, value interface{}) bool {
if r, ok := value.(*client); ok { r, ok := value.(*client)
if ok {
if r.route.remoteID == remoteID { if r.route.remoteID == remoteID {
return true return true
} }
r.WriterPacket(msg) r.WriterPacket(msg)
} }
return true return true
}) })
// log.Info("BroadcastInfoMessage success ")
} }
func (b *Broker) BroadcastSubOrUnsubMessage(packet packets.ControlPacket) { func (b *Broker) BroadcastSubOrUnsubMessage(packet packets.ControlPacket) {
b.routes.Range(func(key, value interface{}) bool { b.routes.Range(func(key, value interface{}) bool {
if r, ok := value.(*client); ok { r, ok := value.(*client)
if ok {
r.WriterPacket(packet) r.WriterPacket(packet)
} }
return true return true
}) })
// log.Info("BroadcastSubscribeMessage remotes: ", s.remotes)
} }
func (b *Broker) removeClient(c *client) { func (b *Broker) removeClient(c *client) {
clientId := c.info.clientID clientId := string(c.info.clientID)
typ := c.typ typ := c.typ
switch typ { switch typ {
case CLIENT: case CLIENT:
@@ -650,6 +642,7 @@ func (b *Broker) removeClient(c *client) {
case REMOTE: case REMOTE:
b.remotes.Delete(clientId) b.remotes.Delete(clientId)
} }
// log.Info("delete client ,", clientId)
} }
func (b *Broker) PublishMessage(packet *packets.PublishPacket) { func (b *Broker) PublishMessage(packet *packets.PublishPacket) {
@@ -659,40 +652,31 @@ func (b *Broker) PublishMessage(packet *packets.PublishPacket) {
err := b.topicsMgr.Subscribers([]byte(packet.TopicName), packet.Qos, &subs, &qoss) err := b.topicsMgr.Subscribers([]byte(packet.TopicName), packet.Qos, &subs, &qoss)
b.mu.Unlock() b.mu.Unlock()
if err != nil { if err != nil {
log.Error("search sub client error", zap.Error(err)) log.Error("search sub client error, ", zap.Error(err))
return return
} }
for _, sub := range subs { for _, sub := range subs {
s, ok := sub.(*subscription) s, ok := sub.(*subscription)
if ok { if ok {
if err := s.client.WriterPacket(packet); err != nil { err := s.client.WriterPacket(packet)
log.Error("write message error", zap.Error(err)) if err != nil {
log.Error("write message error, ", zap.Error(err))
} }
} }
} }
} }
func (b *Broker) PublishMessageByClientId(packet *packets.PublishPacket, clientId string) error { func (b *Broker) BroadcastUnSubscribe(subs map[string]*subscription) {
cli, loaded := b.clients.LoadAndDelete(clientId)
if !loaded {
return fmt.Errorf("clientId %s not connected", clientId)
}
conn, success := cli.(*client)
if !success {
return fmt.Errorf("clientId %s loaded fail", clientId)
}
return conn.WriterPacket(packet)
}
func (b *Broker) BroadcastUnSubscribe(topicsToUnSubscribeFrom []string) {
if len(topicsToUnSubscribeFrom) == 0 {
return
}
unsub := packets.NewControlPacket(packets.Unsubscribe).(*packets.UnsubscribePacket) unsub := packets.NewControlPacket(packets.Unsubscribe).(*packets.UnsubscribePacket)
unsub.Topics = append(unsub.Topics, topicsToUnSubscribeFrom...) for topic, _ := range subs {
b.BroadcastSubOrUnsubMessage(unsub) unsub.Topics = append(unsub.Topics, topic)
}
if len(unsub.Topics) > 0 {
b.BroadcastSubOrUnsubMessage(unsub)
}
} }
func (b *Broker) OnlineOfflineNotification(clientID string, online bool) { func (b *Broker) OnlineOfflineNotification(clientID string, online bool) {

View File

@@ -1,7 +1,8 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"math/rand" "math/rand"
@@ -11,19 +12,17 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"unicode/utf8"
"github.com/eapache/queue"
"github.com/eclipse/paho.mqtt.golang/packets"
"github.com/fhmq/hmq/broker/lib/sessions" "github.com/fhmq/hmq/broker/lib/sessions"
"github.com/fhmq/hmq/broker/lib/topics" "github.com/fhmq/hmq/broker/lib/topics"
"github.com/fhmq/hmq/plugins/bridge" "github.com/fhmq/hmq/plugins/bridge"
"github.com/eclipse/paho.mqtt.golang/packets"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/net/websocket"
) )
const ( const (
// BrokerInfoTopic special pub topic for cluster info // special pub topic for cluster info BrokerInfoTopic
BrokerInfoTopic = "broker000100101info" BrokerInfoTopic = "broker000100101info"
// CLIENT is an end user. // CLIENT is an end user.
CLIENT = 0 CLIENT = 0
@@ -43,56 +42,29 @@ const (
Disconnected = 2 Disconnected = 2
) )
const (
awaitRelTimeout int64 = 20
retryInterval int64 = 20
)
var ( var (
groupCompile = regexp.MustCompile(_GroupTopicRegexp) groupCompile = regexp.MustCompile(_GroupTopicRegexp)
) )
type client struct { type client struct {
typ int typ int
mu sync.Mutex mu sync.Mutex
broker *Broker broker *Broker
conn net.Conn conn net.Conn
info info info info
route route route route
status int status int
ctx context.Context ctx context.Context
cancelFunc context.CancelFunc cancelFunc context.CancelFunc
session *sessions.Session session *sessions.Session
subMap map[string]*subscription subMap map[string]*subscription
subMapMu sync.RWMutex topicsMgr *topics.Manager
topicsMgr *topics.Manager subs []interface{}
subs []interface{} qoss []byte
qoss []byte rmsgs []*packets.PublishPacket
rmsgs []*packets.PublishPacket routeSubMap map[string]uint64
routeSubMap map[string]uint64
routeSubMapMu sync.Mutex
awaitingRel map[uint16]int64
awaitingRelMu sync.RWMutex
maxAwaitingRel int
inflight map[uint16]*inflightElem
inflightMu sync.RWMutex
mqueue *queue.Queue
retryTimer *time.Timer
retryTimerLock sync.Mutex
} }
type InflightStatus uint8
const (
Publish InflightStatus = 0
Pubrel InflightStatus = 1
)
type inflightElem struct {
status InflightStatus
packet *packets.PublishPacket
timestamp int64
}
type subscription struct { type subscription struct {
client *client client *client
topic string topic string
@@ -117,29 +89,17 @@ type route struct {
} }
var ( var (
DisconnectedPacket = packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket) DisconnectdPacket = packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket)
r = rand.New(rand.NewSource(time.Now().UnixNano())) r = rand.New(rand.NewSource(time.Now().UnixNano()))
) )
func (c *client) init() { func (c *client) init() {
c.status = Connected c.status = Connected
c.info.localIP, _, _ = net.SplitHostPort(c.conn.LocalAddr().String()) c.info.localIP = strings.Split(c.conn.LocalAddr().String(), ":")[0]
remoteAddr := c.conn.RemoteAddr() c.info.remoteIP = strings.Split(c.conn.RemoteAddr().String(), ":")[0]
remoteNetwork := remoteAddr.Network()
c.info.remoteIP = ""
if remoteNetwork != "websocket" {
c.info.remoteIP, _, _ = net.SplitHostPort(remoteAddr.String())
} else {
ws := c.conn.(*websocket.Conn)
c.info.remoteIP, _, _ = net.SplitHostPort(ws.Request().RemoteAddr)
}
c.ctx, c.cancelFunc = context.WithCancel(context.Background()) c.ctx, c.cancelFunc = context.WithCancel(context.Background())
c.subMap = make(map[string]*subscription) c.subMap = make(map[string]*subscription)
c.topicsMgr = c.broker.topicsMgr c.topicsMgr = c.broker.topicsMgr
c.routeSubMap = make(map[string]uint64)
c.awaitingRel = make(map[uint16]int64)
c.inflight = make(map[uint16]*inflightElem)
c.mqueue = queue.New()
} }
func (c *client) readLoop() { func (c *client) readLoop() {
@@ -158,16 +118,14 @@ func (c *client) readLoop() {
return return
default: default:
//add read timeout //add read timeout
if keepAlive > 0 { if err := nc.SetReadDeadline(time.Now().Add(timeOut)); err != nil {
if err := nc.SetReadDeadline(time.Now().Add(timeOut)); err != nil { log.Error("set read timeout error: ", zap.Error(err), zap.String("ClientID", c.info.clientID))
log.Error("set read timeout error: ", zap.Error(err), zap.String("ClientID", c.info.clientID)) msg := &Message{
msg := &Message{ client: c,
client: c, packet: DisconnectdPacket,
packet: DisconnectedPacket,
}
b.SubmitWork(c.info.clientID, msg)
return
} }
b.SubmitWork(c.info.clientID, msg)
return
} }
packet, err := packets.ReadPacket(nc) packet, err := packets.ReadPacket(nc)
@@ -175,18 +133,12 @@ func (c *client) readLoop() {
log.Error("read packet error: ", zap.Error(err), zap.String("ClientID", c.info.clientID)) log.Error("read packet error: ", zap.Error(err), zap.String("ClientID", c.info.clientID))
msg := &Message{ msg := &Message{
client: c, client: c,
packet: DisconnectedPacket, packet: DisconnectdPacket,
} }
b.SubmitWork(c.info.clientID, msg) b.SubmitWork(c.info.clientID, msg)
return return
} }
// if packet is disconnect from client, then need to break the read packet loop and clear will msg.
if _, isDisconnect := packet.(*packets.DisconnectPacket); isDisconnect {
c.info.willMsg = nil
c.cancelFunc()
}
msg := &Message{ msg := &Message{
client: c, client: c,
packet: packet, packet: packet,
@@ -197,63 +149,6 @@ func (c *client) readLoop() {
} }
// extractPacketFields function reads a control packet and extracts only the fields
// that needs to pass on UTF-8 validation
func extractPacketFields(msgPacket packets.ControlPacket) []string {
var fields []string
// Get packet type
switch msgPacket.(type) {
case *packets.ConnackPacket:
case *packets.ConnectPacket:
case *packets.PublishPacket:
packet := msgPacket.(*packets.PublishPacket)
fields = append(fields, packet.TopicName)
break
case *packets.SubscribePacket:
case *packets.SubackPacket:
case *packets.UnsubscribePacket:
packet := msgPacket.(*packets.UnsubscribePacket)
fields = append(fields, packet.Topics...)
break
}
return fields
}
// validatePacketFields function checks if any of control packets fields has ill-formed
// UTF-8 string
func validatePacketFields(msgPacket packets.ControlPacket) (validFields bool) {
// Extract just fields that needs validation
fields := extractPacketFields(msgPacket)
for _, field := range fields {
// Perform the basic UTF-8 validation
if !utf8.ValidString(field) {
validFields = false
return
}
// A UTF-8 encoded string MUST NOT include an encoding of the null
// character U+0000
// If a receiver (Server or Client) receives a Control Packet containing U+0000
// it MUST close the Network Connection
// http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf page 14
if bytes.ContainsAny([]byte(field), "\u0000") {
validFields = false
return
}
}
// All fields have been validated successfully
validFields = true
return
}
func ProcessMessage(msg *Message) { func ProcessMessage(msg *Message) {
c := msg.client c := msg.client
ca := msg.packet ca := msg.packet
@@ -265,77 +160,16 @@ func ProcessMessage(msg *Message) {
log.Debug("Recv message:", zap.String("message type", reflect.TypeOf(msg.packet).String()[9:]), zap.String("ClientID", c.info.clientID)) log.Debug("Recv message:", zap.String("message type", reflect.TypeOf(msg.packet).String()[9:]), zap.String("ClientID", c.info.clientID))
} }
// Perform field validation
if !validatePacketFields(ca) {
// http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf
// Page 14
//
// If a Server or Client receives a Control Packet
// containing ill-formed UTF-8 it MUST close the Network Connection
_ = c.conn.Close()
// Update client status
//c.status = Disconnected
log.Error("Client disconnected due to malformed packet", zap.String("ClientID", c.info.clientID))
return
}
switch ca.(type) { switch ca.(type) {
case *packets.ConnackPacket: case *packets.ConnackPacket:
case *packets.ConnectPacket: case *packets.ConnectPacket:
case *packets.PublishPacket: case *packets.PublishPacket:
packet := ca.(*packets.PublishPacket) packet := ca.(*packets.PublishPacket)
c.ProcessPublish(packet) c.ProcessPublish(packet)
case *packets.PubackPacket: case *packets.PubackPacket:
packet := ca.(*packets.PubackPacket)
c.inflightMu.Lock()
if _, found := c.inflight[packet.MessageID]; found {
delete(c.inflight, packet.MessageID)
} else {
log.Error("Duplicated PUBACK PacketId", zap.Uint16("MessageID", packet.MessageID))
}
c.inflightMu.Unlock()
case *packets.PubrecPacket: case *packets.PubrecPacket:
packet := ca.(*packets.PubrecPacket)
c.inflightMu.RLock()
ielem, found := c.inflight[packet.MessageID]
c.inflightMu.RUnlock()
if found {
if ielem.status == Publish {
ielem.status = Pubrel
ielem.timestamp = time.Now().Unix()
} else if ielem.status == Pubrel {
log.Error("Duplicated PUBREC PacketId", zap.Uint16("MessageID", packet.MessageID))
}
} else {
log.Error("The PUBREC PacketId is not found.", zap.Uint16("MessageID", packet.MessageID))
}
pubrel := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket)
pubrel.MessageID = packet.MessageID
if err := c.WriterPacket(pubrel); err != nil {
log.Error("send pubrel error, ", zap.Error(err), zap.String("ClientID", c.info.clientID))
return
}
case *packets.PubrelPacket: case *packets.PubrelPacket:
packet := ca.(*packets.PubrelPacket)
_ = c.pubRel(packet.MessageID)
pubcomp := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket)
pubcomp.MessageID = packet.MessageID
if err := c.WriterPacket(pubcomp); err != nil {
log.Error("send pubcomp error, ", zap.Error(err), zap.String("ClientID", c.info.clientID))
return
}
case *packets.PubcompPacket: case *packets.PubcompPacket:
packet := ca.(*packets.PubcompPacket)
c.inflightMu.Lock()
delete(c.inflight, packet.MessageID)
c.inflightMu.Unlock()
case *packets.SubscribePacket: case *packets.SubscribePacket:
packet := ca.(*packets.SubscribePacket) packet := ca.(*packets.SubscribePacket)
c.ProcessSubscribe(packet) c.ProcessSubscribe(packet)
@@ -413,8 +247,8 @@ func (c *client) processClientPublish(packet *packets.PublishPacket) {
return return
} }
//publish to bridge mq //publish kafka
cost := c.broker.Publish(&bridge.Elements{ c.broker.Publish(&bridge.Elements{
ClientID: c.info.clientID, ClientID: c.info.clientID,
Username: c.info.username, Username: c.info.username,
Action: bridge.Publish, Action: bridge.Publish,
@@ -423,10 +257,6 @@ func (c *client) processClientPublish(packet *packets.PublishPacket) {
Topic: topic, Topic: topic,
}) })
if cost {
return
}
switch packet.Qos { switch packet.Qos {
case QosAtMostOnce: case QosAtMostOnce:
c.ProcessPublishMessage(packet) c.ProcessPublishMessage(packet)
@@ -439,17 +269,6 @@ func (c *client) processClientPublish(packet *packets.PublishPacket) {
} }
c.ProcessPublishMessage(packet) c.ProcessPublishMessage(packet)
case QosExactlyOnce: case QosExactlyOnce:
if err := c.registerPublishPacketId(packet.MessageID); err != nil {
return
} else {
pubrec := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket)
pubrec.MessageID = packet.MessageID
if err := c.WriterPacket(pubrec); err != nil {
log.Error("send pubrec error, ", zap.Error(err), zap.String("ClientID", c.info.clientID))
return
}
c.ProcessPublishMessage(packet)
}
return return
default: default:
log.Error("publish with unknown qos", zap.String("ClientID", c.info.clientID)) log.Error("publish with unknown qos", zap.String("ClientID", c.info.clientID))
@@ -478,6 +297,7 @@ func (c *client) ProcessPublishMessage(packet *packets.PublishPacket) {
return return
} }
// fmt.Println("psubs num: ", len(c.subs))
if len(c.subs) == 0 { if len(c.subs) == 0 {
return return
} }
@@ -514,8 +334,6 @@ func (c *client) ProcessSubscribe(packet *packets.SubscribePacket) {
case CLIENT: case CLIENT:
c.processClientSubscribe(packet) c.processClientSubscribe(packet)
case ROUTER: case ROUTER:
fallthrough
case REMOTE:
c.processRouterSubscribe(packet) c.processRouterSubscribe(packet)
} }
} }
@@ -529,15 +347,14 @@ func (c *client) processClientSubscribe(packet *packets.SubscribePacket) {
if b == nil { if b == nil {
return return
} }
topics := packet.Topics
subTopics := packet.Topics
qoss := packet.Qoss qoss := packet.Qoss
suback := packets.NewControlPacket(packets.Suback).(*packets.SubackPacket) suback := packets.NewControlPacket(packets.Suback).(*packets.SubackPacket)
suback.MessageID = packet.MessageID suback.MessageID = packet.MessageID
var retcodes []byte var retcodes []byte
for i, topic := range subTopics { for i, topic := range topics {
t := topic t := topic
//check topic auth for client //check topic auth for client
if !b.CheckTopicAuth(SUB, c.info.clientID, c.info.username, c.info.remoteIP, topic) { if !b.CheckTopicAuth(SUB, c.info.clientID, c.info.username, c.info.remoteIP, topic) {
@@ -567,13 +384,6 @@ func (c *client) processClientSubscribe(packet *packets.SubscribePacket) {
topic = substr[2] topic = substr[2]
} }
c.subMapMu.Lock()
if oldSub, exist := c.subMap[t]; exist {
_ = c.topicsMgr.Unsubscribe([]byte(oldSub.topic), oldSub)
delete(c.subMap, t)
}
c.subMapMu.Unlock()
sub := &subscription{ sub := &subscription{
topic: topic, topic: topic,
qos: qoss[i], qos: qoss[i],
@@ -589,13 +399,12 @@ func (c *client) processClientSubscribe(packet *packets.SubscribePacket) {
continue continue
} }
c.subMapMu.Lock()
c.subMap[t] = sub c.subMap[t] = sub
c.subMapMu.Unlock()
_ = c.session.AddTopic(t, qoss[i]) c.session.AddTopic(t, qoss[i])
retcodes = append(retcodes, rqos) retcodes = append(retcodes, rqos)
_ = c.topicsMgr.Retained([]byte(topic), &c.rmsgs) c.topicsMgr.Retained([]byte(topic), &c.rmsgs)
} }
suback.ReturnCodes = retcodes suback.ReturnCodes = retcodes
@@ -627,15 +436,14 @@ func (c *client) processRouterSubscribe(packet *packets.SubscribePacket) {
if b == nil { if b == nil {
return return
} }
topics := packet.Topics
subTopics := packet.Topics
qoss := packet.Qoss qoss := packet.Qoss
suback := packets.NewControlPacket(packets.Suback).(*packets.SubackPacket) suback := packets.NewControlPacket(packets.Suback).(*packets.SubackPacket)
suback.MessageID = packet.MessageID suback.MessageID = packet.MessageID
var retcodes []byte var retcodes []byte
for i, topic := range subTopics { for i, topic := range topics {
t := topic t := topic
groupName := "" groupName := ""
share := false share := false
@@ -665,13 +473,8 @@ func (c *client) processRouterSubscribe(packet *packets.SubscribePacket) {
continue continue
} }
c.subMapMu.Lock()
c.subMap[t] = sub c.subMap[t] = sub
c.subMapMu.Unlock()
c.routeSubMapMu.Lock()
addSubMap(c.routeSubMap, topic) addSubMap(c.routeSubMap, topic)
c.routeSubMapMu.Unlock()
retcodes = append(retcodes, rqos) retcodes = append(retcodes, rqos)
} }
@@ -701,24 +504,20 @@ func (c *client) processRouterUnSubscribe(packet *packets.UnsubscribePacket) {
if b == nil { if b == nil {
return return
} }
topics := packet.Topics
unSubTopics := packet.Topics for _, topic := range topics {
sub, exist := c.subMap[topic]
for _, topic := range unSubTopics { if exist {
c.subMapMu.Lock() retainNum := delSubMap(c.routeSubMap, topic)
if sub, exist := c.subMap[topic]; exist { if retainNum > 0 {
c.routeSubMapMu.Lock()
if retainNum := delSubMap(c.routeSubMap, topic); retainNum > 0 {
c.routeSubMapMu.Unlock()
c.subMapMu.Unlock()
continue continue
} }
c.routeSubMapMu.Unlock()
_ = c.topicsMgr.Unsubscribe([]byte(sub.topic), sub) c.topicsMgr.Unsubscribe([]byte(sub.topic), sub)
delete(c.subMap, topic) delete(c.subMap, topic)
} }
c.subMapMu.Unlock()
} }
unsuback := packets.NewControlPacket(packets.Unsuback).(*packets.UnsubackPacket) unsuback := packets.NewControlPacket(packets.Unsuback).(*packets.UnsubackPacket)
@@ -739,10 +538,9 @@ func (c *client) processClientUnSubscribe(packet *packets.UnsubscribePacket) {
if b == nil { if b == nil {
return return
} }
topics := packet.Topics
unSubTopics := packet.Topics for _, topic := range topics {
for _, topic := range unSubTopics {
{ {
//publish kafka //publish kafka
@@ -756,14 +554,12 @@ func (c *client) processClientUnSubscribe(packet *packets.UnsubscribePacket) {
} }
c.subMapMu.Lock()
sub, exist := c.subMap[topic] sub, exist := c.subMap[topic]
if exist { if exist {
_ = c.topicsMgr.Unsubscribe([]byte(sub.topic), sub) c.topicsMgr.Unsubscribe([]byte(sub.topic), sub)
_ = c.session.RemoveTopic(topic) c.session.RemoveTopic(topic)
delete(c.subMap, topic) delete(c.subMap, topic)
} }
c.subMapMu.Unlock()
} }
@@ -811,62 +607,44 @@ func (c *client) Close() {
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
}) })
if c.mu.Lock(); c.conn != nil { if c.conn != nil {
_ = c.conn.Close() c.conn.Close()
c.conn = nil c.conn = nil
c.mu.Unlock()
} }
if b == nil { subs := c.subMap
return
}
b.removeClient(c) if b != nil {
b.removeClient(c)
c.subMapMu.RLock() for _, sub := range subs {
defer c.subMapMu.RUnlock() err := b.topicsMgr.Unsubscribe([]byte(sub.topic), sub)
if err != nil {
unSubTopics := make([]string, 0) log.Error("unsubscribe error, ", zap.Error(err), zap.String("ClientID", c.info.clientID))
for topic, sub := range c.subMap { }
unSubTopics = append(unSubTopics, topic)
// guard against race condition where a client gets Close() but wasn't initialized yet fully
if sub == nil || b.topicsMgr == nil {
continue
} }
if err := b.topicsMgr.Unsubscribe([]byte(sub.topic), sub); err != nil { if c.typ == CLIENT {
log.Error("unsubscribe error, ", zap.Error(err), zap.String("ClientID", c.info.clientID)) b.BroadcastUnSubscribe(subs)
//offline notification
b.OnlineOfflineNotification(c.info.clientID, false)
}
if c.info.willMsg != nil {
b.PublishMessage(c.info.willMsg)
}
if c.typ == CLUSTER {
b.ConnectToDiscovery()
}
//do reconnect
if c.typ == REMOTE {
go b.connectRouter(c.route.remoteID, c.route.remoteUrl)
} }
} }
if c.typ == CLIENT {
b.BroadcastUnSubscribe(unSubTopics)
//offline notification
b.OnlineOfflineNotification(c.info.clientID, false)
}
if c.info.willMsg != nil {
b.PublishMessage(c.info.willMsg)
}
if c.typ == CLUSTER {
b.ConnectToDiscovery()
}
//do reconnect
if c.typ == REMOTE {
go b.connectRouter(c.route.remoteID, c.route.remoteUrl)
}
} }
func (c *client) WriterPacket(packet packets.ControlPacket) error { func (c *client) WriterPacket(packet packets.ControlPacket) error {
defer func() {
if err := recover(); err != nil {
log.Error("recover error, ", zap.Any("recover", r))
}
}()
if c.status == Disconnected { if c.status == Disconnected {
return nil return nil
} }
@@ -880,61 +658,7 @@ func (c *client) WriterPacket(packet packets.ControlPacket) error {
} }
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() err := packet.Write(c.conn)
return packet.Write(c.conn) c.mu.Unlock()
} return err
func (c *client) registerPublishPacketId(packetId uint16) error {
if c.isAwaitingFull() {
log.Error("Dropped qos2 packet for too many awaiting_rel", zap.Uint16("id", packetId))
return errors.New("DROPPED_QOS2_PACKET_FOR_TOO_MANY_AWAITING_REL")
}
c.awaitingRelMu.Lock()
defer c.awaitingRelMu.Unlock()
if _, found := c.awaitingRel[packetId]; found {
return errors.New("RC_PACKET_IDENTIFIER_IN_USE")
}
c.awaitingRel[packetId] = time.Now().Unix()
time.AfterFunc(time.Duration(awaitRelTimeout)*time.Second, c.expireAwaitingRel)
return nil
}
func (c *client) isAwaitingFull() bool {
c.awaitingRelMu.RLock()
defer c.awaitingRelMu.RUnlock()
if c.maxAwaitingRel == 0 {
return false
}
if len(c.awaitingRel) < c.maxAwaitingRel {
return false
}
return true
}
func (c *client) expireAwaitingRel() {
c.awaitingRelMu.Lock()
defer c.awaitingRelMu.Unlock()
if len(c.awaitingRel) == 0 {
return
}
now := time.Now().Unix()
for packetId, Timestamp := range c.awaitingRel {
if now-Timestamp >= awaitRelTimeout {
log.Error("Dropped qos2 packet for await_rel_timeout", zap.Uint16("id", packetId))
delete(c.awaitingRel, packetId)
}
}
}
func (c *client) pubRel(packetId uint16) error {
c.awaitingRelMu.Lock()
defer c.awaitingRelMu.Unlock()
if _, found := c.awaitingRel[packetId]; found {
delete(c.awaitingRel, packetId)
} else {
log.Error("The PUBREL PacketId is not found", zap.Uint16("id", packetId))
return errors.New("RC_PACKET_IDENTIFIER_NOT_FOUND")
}
return nil
} }

View File

@@ -1,14 +1,17 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import ( import (
"encoding/json"
"reflect" "reflect"
"time" "time"
jsoniter "github.com/json-iterator/go" "github.com/tidwall/gjson"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/eclipse/paho.mqtt.golang/packets" "github.com/eclipse/paho.mqtt.golang/packets"
uuid "github.com/google/uuid" uuid "github.com/satori/go.uuid"
) )
const ( const (
@@ -113,11 +116,7 @@ func delSubMap(m map[string]uint64, topic string) uint64 {
} }
func GenUniqueId() string { func GenUniqueId() string {
id, err := uuid.NewRandom() return uuid.NewV4().String()
if err != nil {
log.Error("uuid.NewRandom() returned an error: " + err.Error())
}
return id.String()
} }
func wrapPublishPacket(packet *packets.PublishPacket) *packets.PublishPacket { func wrapPublishPacket(packet *packets.PublishPacket) *packets.PublishPacket {
@@ -133,108 +132,26 @@ func wrapPublishPacket(packet *packets.PublishPacket) *packets.PublishPacket {
func unWrapPublishPacket(packet *packets.PublishPacket) *packets.PublishPacket { func unWrapPublishPacket(packet *packets.PublishPacket) *packets.PublishPacket {
p := packet.Copy() p := packet.Copy()
if payload := jsoniter.Get(p.Payload, "payload").ToString(); payload != "" { if gjson.GetBytes(p.Payload, "payload").Exists() {
p.Payload = []byte(payload) p.Payload = []byte(gjson.GetBytes(p.Payload, "payload").String())
} }
return p return p
} }
func publish(sub *subscription, packet *packets.PublishPacket) { func publish(sub *subscription, packet *packets.PublishPacket) {
switch packet.Qos { // var p *packets.PublishPacket
case QosAtMostOnce: // if sub.client.info.username != "root" {
err := sub.client.WriterPacket(packet) // p = unWrapPublishPacket(packet)
if err != nil { // } else {
log.Error("process message for psub error, ", zap.Error(err)) // p = wrapPublishPacket(packet)
} // }
case QosAtLeastOnce, QosExactlyOnce: // err := sub.client.WriterPacket(p)
sub.client.inflightMu.Lock() // if err != nil {
sub.client.inflight[packet.MessageID] = &inflightElem{status: Publish, packet: packet, timestamp: time.Now().Unix()} // log.Error("process message for psub error, ", zap.Error(err))
sub.client.inflightMu.Unlock() // }
err := sub.client.WriterPacket(packet)
if err != nil { err := sub.client.WriterPacket(packet)
log.Error("process message for psub error, ", zap.Error(err)) if err != nil {
} log.Error("process message for psub error, ", zap.Error(err))
sub.client.ensureRetryTimer()
default:
log.Error("publish with unknown qos", zap.String("ClientID", sub.client.info.clientID))
return
} }
} }
// timer for retry delivery
func (c *client) ensureRetryTimer(interval ...int64) {
c.retryTimerLock.Lock()
defer c.retryTimerLock.Unlock()
if c.retryTimer != nil {
return
}
if len(interval) > 1 {
return
}
timerInterval := retryInterval
if len(interval) == 1 {
timerInterval = interval[0]
}
c.retryTimer = time.AfterFunc(time.Duration(timerInterval)*time.Second, c.retryDelivery)
return
}
func (c *client) resetRetryTimer() {
// lock mutex before reading retryTimer
c.retryTimerLock.Lock()
defer c.retryTimerLock.Unlock()
if c.retryTimer == nil {
return
}
// reset timer
c.retryTimer = nil
}
func (c *client) retryDelivery() {
c.resetRetryTimer()
c.inflightMu.RLock()
ilen := len(c.inflight)
if c.mu.Lock(); c.conn == nil || ilen == 0 { //Reset timer when client offline OR inflight is empty
c.inflightMu.RUnlock()
c.mu.Unlock()
return
}
// copy the to be retried elements out of the map to only hold the lock for a short time and use the new slice later to iterate
// through them
toRetryEle := make([]*inflightElem, 0, ilen)
for _, infEle := range c.inflight {
toRetryEle = append(toRetryEle, infEle)
}
c.inflightMu.RUnlock()
now := time.Now().Unix()
for _, infEle := range toRetryEle {
age := now - infEle.timestamp
if age >= retryInterval {
if infEle.status == Publish {
c.WriterPacket(infEle.packet)
infEle.timestamp = now
} else if infEle.status == Pubrel {
pubrel := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket)
pubrel.MessageID = infEle.packet.MessageID
c.WriterPacket(pubrel)
infEle.timestamp = now
}
} else {
if age < 0 {
age = 0
}
c.ensureRetryTimer(retryInterval - age)
}
}
c.ensureRetryTimer()
}

View File

@@ -1,8 +1,11 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/json"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
@@ -10,14 +13,9 @@ import (
"os" "os"
"github.com/fhmq/hmq/logger" "github.com/fhmq/hmq/logger"
"github.com/fhmq/hmq/plugins/auth"
"github.com/fhmq/hmq/plugins/bridge"
jsoniter "github.com/json-iterator/go"
"go.uber.org/zap" "go.uber.org/zap"
) )
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type Config struct { type Config struct {
Worker int `json:"workerNum"` Worker int `json:"workerNum"`
HTTPPort string `json:"httpPort"` HTTPPort string `json:"httpPort"`
@@ -36,11 +34,6 @@ type Config struct {
} }
type Plugins struct { type Plugins struct {
Auth auth.Auth
Bridge bridge.BridgeMQ
}
type NamedPlugins struct {
Auth string Auth string
Bridge string Bridge string
} }
@@ -161,17 +154,6 @@ func LoadConfig(filename string) (*Config, error) {
return &config, nil return &config, nil
} }
func (p *Plugins) UnmarshalJSON(b []byte) error {
var named NamedPlugins
err := json.Unmarshal(b, &named)
if err != nil {
return err
}
p.Auth = auth.NewAuth(named.Auth)
p.Bridge = bridge.NewBridgeMQ(named.Bridge)
return nil
}
func (config *Config) check() error { func (config *Config) check() error {
if config.Worker == 0 { if config.Worker == 0 {
@@ -236,7 +218,7 @@ func NewTLSConfig(tlsInfo TLSInfo) (*tls.Config, error) {
return nil, err return nil, err
} }
pool := x509.NewCertPool() pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM(rootPEM) ok := pool.AppendCertsFromPEM([]byte(rootPEM))
if !ok { if !ok {
return nil, fmt.Errorf("failed to parse root ca certificate") return nil, fmt.Errorf("failed to parse root ca certificate")
} }

View File

@@ -11,8 +11,8 @@ func InitHTTPMoniter(b *Broker) {
clientid := c.Param("clientid") clientid := c.Param("clientid")
cli, ok := b.clients.Load(clientid) cli, ok := b.clients.Load(clientid)
if ok { if ok {
conn, success := cli.(*client) conn, succss := cli.(*client)
if success { if succss {
conn.Close() conn.Close()
} }
} }

View File

@@ -1,3 +1,5 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import ( import (
@@ -15,7 +17,7 @@ func (c *client) SendInfo() {
} }
url := c.info.localIP + ":" + c.broker.config.Cluster.Port url := c.info.localIP + ":" + c.broker.config.Cluster.Port
infoMsg := NewInfo(c.broker.id, url) infoMsg := NewInfo(c.broker.id, url, false)
err := c.WriterPacket(infoMsg) err := c.WriterPacket(infoMsg)
if err != nil { if err != nil {
log.Error("send info message error, ", zap.Error(err)) log.Error("send info message error, ", zap.Error(err))
@@ -46,8 +48,6 @@ func (c *client) SendConnect() {
return return
} }
m := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket) m := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket)
m.ProtocolName = "MQIsdp"
m.ProtocolVersion = 3
m.CleanSession = true m.CleanSession = true
m.ClientIdentifier = c.info.clientID m.ClientIdentifier = c.info.clientID
@@ -60,12 +60,13 @@ func (c *client) SendConnect() {
log.Info("send connect success") log.Info("send connect success")
} }
func NewInfo(sid, url string) *packets.PublishPacket { func NewInfo(sid, url string, isforword bool) *packets.PublishPacket {
pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket)
pub.Qos = 0 pub.Qos = 0
pub.TopicName = BrokerInfoTopic pub.TopicName = BrokerInfoTopic
pub.Retain = false pub.Retain = false
info := fmt.Sprintf(`{"brokerID":"%s","brokerUrl":"%s"}`, sid, url) info := fmt.Sprintf(`{"brokerID":"%s","brokerUrl":"%s"}`, sid, url)
// log.Info("new info", string(info))
pub.Payload = []byte(info) pub.Payload = []byte(info)
return pub return pub
} }

View File

@@ -55,7 +55,7 @@ func (this *Session) Init(msg *packets.ConnectPacket) error {
this.topics = make(map[string]byte, 1) this.topics = make(map[string]byte, 1)
this.id = msg.ClientIdentifier this.id = string(msg.ClientIdentifier)
this.initted = true this.initted = true

View File

@@ -78,7 +78,7 @@ func (this *memTopics) Unsubscribe(topic []byte, sub interface{}) error {
return this.sroot.sremove(topic, sub) return this.sroot.sremove(topic, sub)
} }
// Subscribers Returned values will be invalidated by the next Subscribers call // Returned values will be invalidated by the next Subscribers call
func (this *memTopics) Subscribers(topic []byte, qos byte, subs *[]interface{}, qoss *[]byte) error { func (this *memTopics) Subscribers(topic []byte, qos byte, subs *[]interface{}, qoss *[]byte) error {
if !ValidQos(qos) { if !ValidQos(qos) {
return fmt.Errorf("Invalid QoS %d", qos) return fmt.Errorf("Invalid QoS %d", qos)
@@ -104,7 +104,7 @@ func (this *memTopics) Retain(msg *packets.PublishPacket) error {
return this.rroot.rremove([]byte(msg.TopicName)) return this.rroot.rremove([]byte(msg.TopicName))
} }
return this.rroot.rinsertOrUpdate([]byte(msg.TopicName), msg) return this.rroot.rinsert([]byte(msg.TopicName), msg)
} }
func (this *memTopics) Retained(topic []byte, msgs *[]*packets.PublishPacket) error { func (this *memTopics) Retained(topic []byte, msgs *[]*packets.PublishPacket) error {
@@ -286,11 +286,13 @@ func newRNode() *rnode {
} }
} }
func (this *rnode) rinsertOrUpdate(topic []byte, msg *packets.PublishPacket) error { func (this *rnode) rinsert(topic []byte, msg *packets.PublishPacket) error {
// If there's no more topic levels, that means we are at the matching rnode. // If there's no more topic levels, that means we are at the matching rnode.
if len(topic) == 0 { if len(topic) == 0 {
// Reuse the message if possible // Reuse the message if possible
this.msg = msg if this.msg == nil {
this.msg = msg
}
return nil return nil
} }
@@ -313,7 +315,7 @@ func (this *rnode) rinsertOrUpdate(topic []byte, msg *packets.PublishPacket) err
this.rnodes[level] = n this.rnodes[level] = n
} }
return n.rinsertOrUpdate(rem, msg) return n.rinsert(rem, msg)
} }
// Remove the retained message for the supplied topic // Remove the retained message for the supplied topic

View File

@@ -1,3 +1,5 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
*/
package broker package broker
import "github.com/eclipse/paho.mqtt.golang/packets" import "github.com/eclipse/paho.mqtt.golang/packets"

View File

@@ -2,11 +2,13 @@
"workerNum": 4096, "workerNum": 4096,
"port": "1883", "port": "1883",
"host": "0.0.0.0", "host": "0.0.0.0",
"debug": true,
"cluster": { "cluster": {
"host": "0.0.0.0", "host": "0.0.0.0",
"port": "1993" "port": "1993"
}, },
"httpPort": "8080", "httpPort": "8080",
"router": "127.0.0.1:9888",
"tlsPort": "8883", "tlsPort": "8883",
"tlsHost": "0.0.0.0", "tlsHost": "0.0.0.0",
"wsPort": "1888", "wsPort": "1888",
@@ -19,7 +21,7 @@
"keyFile": "ssl/server/key.pem" "keyFile": "ssl/server/key.pem"
}, },
"plugins": { "plugins": {
"auth": "mock", "auth": "authhttp",
"bridge": "csvlog" "bridge": "kafka"
} }
} }

View File

@@ -14,24 +14,24 @@ spec:
spec: spec:
containers: containers:
- name: mqtt-broker - name: mqtt-broker
image: hmq:v0.1.0 image: uhub.service.ucloud.cn/uiot_core_hub/hmq:v0.1.0
ports: ports:
- containerPort: 1883 - containerPort: 1883
- containerPort: 8080 - containerPort: 8080
volumeMounts: volumeMounts:
- name: mqtt-broker - name: mqtt-broker1
mountPath: /conf mountPath: /conf
subPath: hmq.config subPath: hmq.config
- name: mqtt-broker - name: mqtt-broker1
mountPath: /plugins/kafka/kafka.json mountPath: /plugins/kafka/kafka.json
subPath: kafka.json subPath: kafka.json
- name: mqtt-broker - name: mqtt-broker1
mountPath: /plugins/authttp/http.json mountPath: /plugins/authttp/http.json
subPath: kafka.json subPath: kafka.json
volumes: volumes:
- name: mqtt-broker - name: mqtt-broker1
configMap: configMap:
name: mqtt-broker name: mqtt-broker1
items: items:
- key: hmq.config - key: hmq.config
path: hmq.config path: hmq.config

View File

@@ -7,7 +7,7 @@ spec:
app: mqtt-broker app: mqtt-broker
ports: ports:
- protocol: TCP - protocol: TCP
port: 1883 port: 8080
targetPort: 1883 targetPort: 8080
type: ClusterIP type: ClusterIP
sessionAffinity: ClientIP sessionAffinity: ClientIP

73
go.mod
View File

@@ -1,57 +1,30 @@
module github.com/fhmq/hmq module github.com/fhmq/hmq
go 1.18 go 1.12
require ( require (
github.com/Shopify/sarama v1.34.1 github.com/Shopify/sarama v1.23.0
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 // indirect
github.com/bitly/go-simplejson v0.5.0 github.com/bitly/go-simplejson v0.5.0
github.com/cespare/xxhash/v2 v2.1.2
github.com/eapache/queue v1.1.0
github.com/eclipse/paho.mqtt.golang v1.4.1
github.com/gin-gonic/gin v1.8.1
github.com/google/uuid v1.3.0
github.com/json-iterator/go v1.1.12
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/stretchr/testify v1.7.2
go.uber.org/zap v1.21.0
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
)
require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/eapache/go-resiliency v1.2.0 // indirect github.com/gin-gonic/gin v1.4.0
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/golang/protobuf v1.3.2 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/go-playground/validator/v10 v10.10.1 // indirect github.com/pkg/errors v0.8.1 // indirect
github.com/goccy/go-json v0.9.7 // indirect github.com/satori/go.uuid v1.2.0
github.com/golang/snappy v0.0.4 // indirect github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/shirou/gopsutil v2.18.12+incompatible
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/stretchr/testify v1.3.0
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/tidwall/gjson v1.3.0
github.com/jcmturner/aescts/v2 v2.0.0 // indirect go.uber.org/atomic v1.4.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect go.uber.org/multierr v1.1.0 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect go.uber.org/zap v1.10.0
github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
github.com/klauspost/compress v1.15.6 // indirect golang.org/x/sys v0.0.0-20190730183949-1393eb018365 // indirect
github.com/leodido/go-urn v1.2.1 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

661
go.sum
View File

@@ -1,610 +1,125 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58lvEQXs0UpQJCo5SoGAcg+mbSTIg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/Shopify/sarama v1.23.0 h1:slvlbm7bxyp7sKQbUwha5BQdZTqurhRoI+zbKorVigQ=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= github.com/Shopify/sarama v1.23.0/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Shopify/sarama v1.34.1 h1:pVCQO7BMAK3s1jWhgi5v1W6lwZ6Veiekfc2vsgRS06Y=
github.com/Shopify/sarama v1.34.1/go.mod h1:NZSNswsnStpq8TUdFaqnpXm2Do6KRzTIjdBdVlL1YRM=
github.com/Shopify/toxiproxy/v2 v2.4.0 h1:O1e4Jfvr/hefNTNu+8VtdEG5lSeamJRo4aKhMOKNM64=
github.com/Shopify/toxiproxy/v2 v2.4.0/go.mod h1:3ilnjng821bkozDRxNoo64oI/DKqM+rOyJzb564+bvg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/eclipse/paho.mqtt.golang v1.4.1 h1:tUSpviiL5G3P9SZZJPC4ZULZJsxQKXxfENpMvdbAXAI= github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
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.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/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
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.3/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd748aV0cYL+I6k44gFJTCVvpU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/errors v0.9.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tidwall/gjson v1.3.0 h1:kfpsw1W3trbg4Xm6doUtqSl9+LhLB6qJ9PkltVAQZYs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/gjson v1.3.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-20181114220301-adae6a3d119a/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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/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-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20181116152217-5ac8a444bdc5/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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20190730183949-1393eb018365 h1:SaXEMXhWzMJThc05vu6uh61Q245r4KaWMrsTedk0FDc=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc44010=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

19
main.go
View File

@@ -1,30 +1,35 @@
/* Copyright (c) 2018, joy.zhou <chowyu08@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
*/
package main package main
import ( import (
"log"
"os" "os"
"os/signal" "os/signal"
"runtime"
"github.com/fhmq/hmq/broker" "github.com/fhmq/hmq/broker"
"github.com/fhmq/hmq/logger"
"go.uber.org/zap"
) )
var log = logger.Get()
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
config, err := broker.ConfigureConfig(os.Args[1:]) config, err := broker.ConfigureConfig(os.Args[1:])
if err != nil { if err != nil {
log.Fatal("configure broker config error", zap.Error(err)) log.Fatal("configure broker config error: ", err)
} }
b, err := broker.NewBroker(config) b, err := broker.NewBroker(config)
if err != nil { if err != nil {
log.Fatal("New Broker error: ", zap.Error(err)) log.Fatal("New Broker error: ", err)
} }
b.Start() b.Start()
s := waitForSignal() s := waitForSignal()
log.Info("signal received, broker closed.", zap.Any("signal", s)) log.Println("signal received, broker closed.", s)
} }
func waitForSignal() os.Signal { func waitForSignal() os.Signal {

View File

@@ -19,5 +19,5 @@ func (a *aclAuth) CheckConnect(clientID, username, password string) bool {
} }
func (a *aclAuth) CheckACL(action, clientID, username, ip, topic string) bool { func (a *aclAuth) CheckACL(action, clientID, username, ip, topic string) bool {
return checkTopicAuth(a.config, action, ip, username, clientID, topic) return checkTopicAuth(a.config, action, username, ip, clientID, topic)
} }

View File

@@ -1,23 +0,0 @@
//+build test
package acl
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOrigAcls(t *testing.T) {
pwd, _ := os.Getwd()
os.Chdir("../../../")
aclOrig := Init()
os.Chdir(pwd)
// rule: allow ip 127.0.0.1 2 $SYS/#
origAllowed := aclOrig.CheckACL(PUB, "dummyClientID", "dummyUser", "127.0.0.1", "$SYS/something")
assert.True(t, origAllowed)
origAllowed = aclOrig.CheckACL(SUB, "dummyClientID", "dummyUser", "127.0.0.1", "$SYS/something")
assert.False(t, origAllowed)
}

View File

@@ -37,14 +37,14 @@ func AclConfigLoad(file string) (*ACLConfig, error) {
File: file, File: file,
Info: make([]*AuthInfo, 0, 4), Info: make([]*AuthInfo, 0, 4),
} }
err := aclconifg.Parse() err := aclconifg.Prase()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return aclconifg, err return aclconifg, err
} }
func (c *ACLConfig) Parse() error { func (c *ACLConfig) Prase() error {
f, err := os.Open(c.File) f, err := os.Open(c.File)
defer f.Close() defer f.Close()
if err != nil { if err != nil {

View File

@@ -56,7 +56,7 @@ func Init() *authHTTP {
return &authHTTP{client: httpClient} return &authHTTP{client: httpClient}
} }
// CheckConnect check mqtt connect //CheckAuth check mqtt connect
func (a *authHTTP) CheckConnect(clientID, username, password string) bool { func (a *authHTTP) CheckConnect(clientID, username, password string) bool {
action := "connect" action := "connect"
{ {

View File

@@ -1,50 +0,0 @@
# CSVLog Plugin For HMQ
This is a bridge implementation for HMQ that allows messages to be logged to a CSV file at runtime.
It can be used for debugging/monitoring purposes, for integration with other systems/platforms, or as an audit trail of messages.
The plugin allows you to define 0, 1, or more filters which determine which messages get bridged. Where no filters are defined the plugin bridges every message. Where one or more filters exist, the plugin applies the filter/s and only brdiges messages that match the filter spec.
The plugin allows you provide a filename for the output file, and also supports three special filenames {LOG},{STDOUT}, and {NULL}. {LOG} results in messages being bridged to the log, {STDOUT} bridges them to Std out, and {NULL} simply skips and returns without an error.
## Configuration
The configiration settings for CSVLog are defined by the struct csvBridgeConfig.
```
type csvBridgeConfig struct {
FileName string `json:"fileName"`
LogFileMaxSizeMB int64 `json:"logFileMaxSizeMB"`
LogFileMaxFiles int64 `json:"logFileMaxFiles"`
WriteIntervalSecs int64 `json:"writeIntervalSecs"`
CommandTopic string `json:"commandTopic"`
Filters []string `json:"filters"`
}
```
| Setting | Description |
| ----------- | ----------- |
| FileName | A complete filename for the output file, or {LOG} to send bridged messages to the log, {STDOUT} to send bridged messages to STDOUT, or {NULL} to not bridge anything at all |
| LogFileMaxSizeMB | The size in megabytes at which the log file is rotated |
| LogFileMaxFiles | The maximum number of rotated logfiles to retain before they're deleted |
| WriteIntervalSecs | The delay before flushing any pending writes to the file |
| CommandTopic | The name of a topic to which commands relating to CSVLog will be sent eg "bridge/CSVLOG/command" |
| Filters | An array of filter specifications which are used to determine which messages are bridged, if there are no filters specified the filter is assumed to be "#" which bridges everything. Filters are specified the same way that topic acls are described|
## Filters
Filters use the same syntax as for ACL permissions.
So a filter can name a specific topic..
"animals/cats" will bridge messages sent to the "animals/cats" topic.
A filter can use the + or # wildcards so
"animals/cats/+" will bridge messages sent to "animals/cats/breeds", "animals/cats/colours" but not "animals/cats/breeds/longhair"
"animals/cats/#" will bridge messages sent to "animals/cats/breeds", "animals/cats/colours", "animals/cats/breeds/longhair", etc
## Commands
Currently two commands can be sent to the CSVLog bridge:
ROTATEFILE - Triggers an immediate rotation of the log file
REALOADCONFIG - Triggers a reload of the CSVLog config file

View File

@@ -32,21 +32,17 @@ type Elements struct {
const ( const (
//Kafka plugin name //Kafka plugin name
Kafka = "kafka" Kafka = "kafka"
CSVLog = "csvlog"
) )
type BridgeMQ interface { type BridgeMQ interface {
// Publish return true to cost the message Publish(e *Elements) error
Publish(e *Elements) (bool, error)
} }
func NewBridgeMQ(name string) BridgeMQ { func NewBridgeMQ(name string) BridgeMQ {
switch name { switch name {
case Kafka: case Kafka:
return InitKafka() return InitKafka()
case CSVLog:
return InitCSVLog()
default: default:
return &mockMQ{} return &mockMQ{}
} }

View File

@@ -1,414 +0,0 @@
package bridge
/*
Copyright (c) 2021, Gary Barnett @thinkovation. Released under the Apache 2 License
CSVLog is a bridge plugin for HMQ that implements CSV logging of messages. See CSVLog.md for more information
*/
import (
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
"go.uber.org/zap"
)
type csvBridgeConfig struct {
FileName string `json:"fileName"`
LogFileMaxSizeMB int64 `json:"logFileMaxSizeMB"`
LogFileMaxFiles int64 `json:"logFileMaxFiles"`
WriteIntervalSecs int64 `json:"writeIntervalSecs"`
CommandTopic string `json:"commandTopic"`
Filters []string `json:"filters"`
}
type csvLog struct {
config csvBridgeConfig
buffer []string
msgchan chan (*Elements)
sync.RWMutex
}
// rotateLog performs a log rotation - copying the current logfile to the base file name plus a timestamp
func (c *csvLog) rotateLog(withPrune bool) error {
c.Lock()
filename := c.config.FileName
c.Unlock()
basename := strings.TrimSuffix(filename, filepath.Ext(filename))
newpath := basename + time.Now().Format("-20060102T150405") + filepath.Ext(filename)
renameError := os.Rename(filename, newpath)
if renameError != nil {
return renameError
}
outfile, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
outfile.Close()
// Whenever we rotate a logfile we prune
if withPrune {
c.logFilePrune()
}
return nil
}
// writeToLog takes an array of elements and writes them to the logfile (or to log or stdout) spefified in
// the configuration
func (c *csvLog) writeToLog(els []Elements) error {
c.RLock()
fname := c.config.FileName
c.RUnlock()
if fname == "" {
fname = "CSVLOG.CSV"
}
if fname == "{LOG}" {
for _, value := range els {
t := time.Unix(value.Timestamp, 0)
log.Info(t.Format("2006-01-02T15:04:05") + " " + value.ClientID + " " + value.Username + " " + value.Action + " " + value.Topic + " " + value.Payload)
}
return nil
}
if fname == "{STDOUT}" {
for _, value := range els {
t := time.Unix(value.Timestamp, 0)
fmt.Println(t.Format("2006-01-02T15:04:05") + " " + value.ClientID + " " + value.Username + " " + value.Action + " " + value.Topic + " " + value.Payload)
}
return nil
}
var mbsize int64
fileStat, fileStatErr := os.Stat(fname)
if fileStatErr != nil {
log.Warn("Could not get CSVLog info. Received Err " + fileStatErr.Error())
mbsize = 0
} else {
mbsize = fileStat.Size() / 1024 / 1024
}
if mbsize > c.config.LogFileMaxSizeMB && c.config.LogFileMaxSizeMB != 0 {
rotateErr := c.rotateLog(true)
if rotateErr != nil {
log.Warn("Unable to rotate outputfile")
}
}
outfile, outfileOpenError := os.OpenFile(fname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
defer outfile.Close()
if outfileOpenError != nil {
log.Warn("Could not open CSV Log file to write")
return errors.New("Could not write to CSV Log File")
}
writer := csv.NewWriter(outfile)
defer writer.Flush()
for _, value := range els {
t := time.Unix(value.Timestamp, 0)
var outrow = []string{t.Format("2006-01-02T15:04:05"), value.ClientID, value.Username, value.Action, value.Topic, value.Payload}
writeOutRowError := writer.Write(outrow)
if writeOutRowError != nil {
log.Warn("Could not write msg to CSV Log")
}
}
return nil
}
// Worker should be invoked as a goroutine - It listens on the csvlog message channel for incoming messages
// for performance we batch messages into an outqueue and write them in bulk when a timer expires
func (c *csvLog) Worker() {
log.Info("Running CSVLog worker")
var outqueue []Elements
for true {
c.RLock()
waitInterval := c.config.WriteIntervalSecs
c.RUnlock()
timer := time.NewTimer(time.Second * time.Duration(waitInterval))
select {
case p := <-c.msgchan:
c.RLock()
oktopublish := false
// Check to see if any filters are defined. If there are none we assume we're logging everything
if len(c.config.Filters) != 0 {
// We pick up a Read lock here to parse the c.config.Filters string array
// as it's a read lock, and write locks will be rare
// it feels as if this will be fine.
// If there is contention, it _might_ make sense to quickly lock c, get
// the filters and release the lock, then process the filters with no locks
// but I think it's unlikely
for _, filt := range c.config.Filters {
if topicMatch(p.Topic, filt) {
oktopublish = true
break
}
}
} else {
oktopublish = true
}
if oktopublish {
var el Elements
el.Action = p.Action
el.ClientID = p.ClientID
el.Payload = p.Payload
el.Size = p.Size
el.Timestamp = p.Timestamp
el.Topic = p.Topic
el.Username = p.Username
outqueue = append(outqueue, el)
}
c.RUnlock()
break
case <-timer.C:
if len(outqueue) > 0 {
writeResult := c.writeToLog(outqueue)
if writeResult != nil {
log.Warn("Trouble writing to CSV Log")
}
outqueue = nil
}
break
}
}
}
// LoadCSVLogConfig loads the configuration file - it currently looks in
// "./plugins/csvlog/csvlogconfig.json" (following the example of the default location of the kafka plugin config file)
// if it doesn't find it there it looks in two further places - the current directory and
// an "assets" folder under the current directory (This is for compatibility with a couple of deployed)
// implementations.
func LoadCSVLogConfig() csvBridgeConfig {
// Check to see if the CSVLOGCONFFILE environment variable is set and if so
// check that it does actually point to a file
csvLogConfigFile := os.Getenv("CSVLOGCONFFILE")
if csvLogConfigFile != "" {
if _, err := os.Stat(csvLogConfigFile); os.IsNotExist(err) {
csvLogConfigFile = ""
}
}
// If csvLogConfigFile is blank look in the plugins directory,
// then the current directory for the csvLogConfigFile. If it's still not found we use a default config
// If the file does not exist, we use default parameters
if csvLogConfigFile == "" {
csvLogConfigFile = "./plugins/csvlog/csvlogconfig.json"
}
if _, err := os.Stat(csvLogConfigFile); os.IsNotExist(err) {
if _, err := os.Stat("csvlogconfig.json"); os.IsNotExist(err) {
csvLogConfigFile = ""
} else {
csvLogConfigFile = "csvlogconfig.json"
}
}
var configUnmarshalErr error
var config csvBridgeConfig
if csvLogConfigFile != "" {
log.Info("Trying to load config file from " + csvLogConfigFile)
content, err := ioutil.ReadFile(csvLogConfigFile)
if err != nil {
log.Info("Read config file error: ", zap.Error(err))
}
configUnmarshalErr = json.Unmarshal(content, &config)
}
if configUnmarshalErr != nil || config.FileName == "" {
log.Warn("Unable to load csvlog config file, so using default settings")
config.FileName = "/var/log/csvlog.log"
config.CommandTopic = "CSVLOG/command"
config.WriteIntervalSecs = 10
config.LogFileMaxSizeMB = 1
config.LogFileMaxFiles = 4
}
return config
}
// InitCSVLog initialises a CSVLOG plugin
// It does this by loading a config file if one can be found. The default filename follows the same
// convention as the kafka plugin - ie it's in "./plugins/csvlog/csvlogconfig.json" but an
// environment var - CSVLOGCONFFILE - can be set to provide a different location.
//
// Once the config is set the worker is started
func InitCSVLog() *csvLog {
log.Info("Trying to init CSVLOG")
c := &csvLog{config: LoadCSVLogConfig()}
c.msgchan = make(chan *Elements, 200)
//Start the csvlog worker
go c.Worker()
return c
}
// topicMatch accepts a topic name and a filter string, it then evaluates the
// topic against the filter string and returns true if there is a match.
//
// The CSV bridge can be configured with 0, 1 or more filters - Where there are no
// filters specified, every message will be re-published. Where there are filters, any message
// that passes any of the filter tests will be re-published.
func topicMatch(topic string, filter string) bool {
if topic == filter || filter == "#" {
return true
}
topicComponents := strings.Split(topic, "/")
filterComponents := strings.Split(filter, "/")
currentpos := 0
filterComponentsLength := len(filterComponents)
currentFilterComponent := ""
if filterComponentsLength > 0 {
currentFilterComponent = filterComponents[currentpos]
}
for _, topicVal := range topicComponents {
if currentFilterComponent == "" {
return false
}
if currentFilterComponent == "#" {
return true
}
if currentFilterComponent != "+" && currentFilterComponent != topicVal {
return false
}
currentpos++
if filterComponentsLength > currentpos {
currentFilterComponent = filterComponents[currentpos]
} else {
currentFilterComponent = ""
}
}
return true
}
// logFilePrune checks the number of rotated logfiles and prunes them
func (c *csvLog) logFilePrune() error {
// List the rotated files
c.RLock()
filename := c.config.FileName
maxfiles := c.config.LogFileMaxFiles
c.RUnlock()
if maxfiles == 0 {
return nil
}
fileExt := filepath.Ext(filename)
fileDir := filepath.Dir(filename)
baseFileName := strings.TrimSuffix(filepath.Base(filename), fileExt)
files, err := ioutil.ReadDir(fileDir)
if err != nil {
return err
}
var foundFiles []string
for _, file := range files {
if strings.HasPrefix(file.Name(), baseFileName+"-") {
foundFiles = append(foundFiles, file.Name())
}
}
if len(foundFiles) >= int(maxfiles) {
fmt.Println("Found ", len(foundFiles), " files")
sort.Strings(foundFiles)
for i := 0; i < len(foundFiles)-int(maxfiles); i++ {
fileDeleteError := os.Remove(fileDir + "//" + foundFiles[i])
log.Info("Pruning logfile " + fileDir + "//" + foundFiles[i])
if fileDeleteError != nil {
log.Warn("Could not delete file " + fileDir + "//" + foundFiles[i])
}
}
}
return nil
}
// Publish implements the bridge interface - it accepts an Element then checks to see if that element is a
// message published to the admin topic for the plugin
//
func (c *csvLog) Publish(e *Elements) (bool, error) {
// A short-lived lock on c allows us to
// get the Command topic then release the lock
// This then allows us to process the command - which may
// take its a write lock on c (to update values) and then
// return here where we'll pick up a
// read lock to iterate over the c.config.filters
// We're trying to minimise the time spent in this function
// and to limit the overall time spent in any write locks.
c.RLock()
//CSVLOG allows you to configure a CommandTopic which is a topic to which commands affecting the behaviour of CSVLog can be sent
//The simplest would be a message with a payload of "RELOAD" which will reload the configuration allowing configuration changes to be
//made at runtime without restarting the broker
CommandTopic := c.config.CommandTopic
OutFile := c.config.FileName
c.RUnlock()
// If the outfile is set to "{NULL}" we don't do anything with the message - we just return nil
// This feature is here to allow CSVLOG to be enabled/disabled at runtime
if OutFile == "{NULL}" {
return false, nil
}
if e.Topic == CommandTopic {
log.Info("CSVLOG Command Received")
// Process Command
// These are going to be rare ocurrences, so in this implementation
// we will process the command here - but if we _really_ want to
// squeeze delays out, we could have a worker sitting on a
// command channel processing any commands.
if e.Payload == "RELOADCONFIG" {
newConfig := LoadCSVLogConfig()
c.Lock()
c.config = newConfig
c.Unlock()
}
if e.Payload == "ROTATEFILE" {
c.rotateLog(true)
}
if e.Payload == "ROTATEFILENOPRUNE" {
c.rotateLog(false)
}
// We could return without doing anything more here, but
// for now we move ahead with the filter processing on the
// basis that unless we either filter for "all" (with #) or
// filter for the CommandTopic, they won't be logged - but we
// may have a reason for wanting to track commands too
}
// Push the message into the channel and return
// the channel is buffered and is read by a goroutine so this should block for the shortest possible time
c.msgchan <- e
return false, nil
}

View File

@@ -1,36 +0,0 @@
package bridge
import (
"fmt"
"testing"
)
//Test_topicMatch is here to double check the topic matching logic
func Test_topicMatch(t *testing.T) {
tests := []struct {
name string
topic string
filter string
want bool
}{
// Some sample test cases
{name: "Simple", topic: "test", filter: "test", want: true},
{name: "Simple", topic: "test/cat", filter: "test/+", want: true},
{name: "Simple", topic: "test/cat/breed", filter: "test/+", want: false},
{name: "Simple", topic: "test/cat", filter: "test/#", want: true},
{name: "Simple", topic: "test/cat/banana", filter: "test/#", want: true},
{name: "Simple", topic: "test/cat/banana", filter: "test/+", want: false},
{name: "Simple", topic: "test/dog/banana", filter: "test/cat/+", want: false},
{name: "Simple", topic: "test/cat/banana", filter: "test/+/banana", want: true},
}
for _, tt := range tests {
fmt.Println(tt)
t.Run(tt.name, func(t *testing.T) {
if got := topicMatch(tt.topic, tt.filter); got != tt.want {
t.Errorf("topicMatch() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -11,7 +11,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
type kafkaConfig struct { type kafakConfig struct {
Addr []string `json:"addr"` Addr []string `json:"addr"`
ConnectTopic string `json:"onConnect"` ConnectTopic string `json:"onConnect"`
SubscribeTopic string `json:"onSubscribe"` SubscribeTopic string `json:"onSubscribe"`
@@ -22,24 +22,24 @@ type kafkaConfig struct {
} }
type kafka struct { type kafka struct {
kafkaConfig kafkaConfig kafakConfig kafakConfig
kafkaClient sarama.AsyncProducer kafkaClient sarama.AsyncProducer
} }
// InitKafka Init kafka client //Init init kafak client
func InitKafka() *kafka { func InitKafka() *kafka {
log.Info("start connect kafka....") log.Info("start connect kafka....")
content, err := ioutil.ReadFile("./plugins/kafka/kafka.json") content, err := ioutil.ReadFile("./plugins/mq/kafka/kafka.json")
if err != nil { if err != nil {
log.Fatal("Read config file error: ", zap.Error(err)) log.Fatal("Read config file error: ", zap.Error(err))
} }
// log.Info(string(content)) // log.Info(string(content))
var config kafkaConfig var config kafakConfig
err = json.Unmarshal(content, &config) err = json.Unmarshal(content, &config)
if err != nil { if err != nil {
log.Fatal("Unmarshal config file error: ", zap.Error(err)) log.Fatal("Unmarshal config file error: ", zap.Error(err))
} }
c := &kafka{kafkaConfig: config} c := &kafka{kafakConfig: config}
c.connect() c.connect()
return c return c
} }
@@ -48,7 +48,7 @@ func InitKafka() *kafka {
func (k *kafka) connect() { func (k *kafka) connect() {
conf := sarama.NewConfig() conf := sarama.NewConfig()
conf.Version = sarama.V1_1_1_0 conf.Version = sarama.V1_1_1_0
kafkaClient, err := sarama.NewAsyncProducer(k.kafkaConfig.Addr, conf) kafkaClient, err := sarama.NewAsyncProducer(k.kafakConfig.Addr, conf)
if err != nil { if err != nil {
log.Fatal("create kafka async producer failed: ", zap.Error(err)) log.Fatal("create kafka async producer failed: ", zap.Error(err))
} }
@@ -63,8 +63,8 @@ func (k *kafka) connect() {
} }
//Publish publish to kafka //Publish publish to kafka
func (k *kafka) Publish(e *Elements) (bool, error) { func (k *kafka) Publish(e *Elements) error {
config := k.kafkaConfig config := k.kafakConfig
key := e.ClientID key := e.ClientID
topics := make(map[string]bool) topics := make(map[string]bool)
switch e.Action { switch e.Action {
@@ -96,10 +96,10 @@ func (k *kafka) Publish(e *Elements) (bool, error) {
topics[config.DisconnectTopic] = true topics[config.DisconnectTopic] = true
} }
default: default:
return false, errors.New("error action: " + e.Action) return errors.New("error action: " + e.Action)
} }
return false, k.publish(topics, key, e) return k.publish(topics, key, e)
} }

View File

@@ -2,6 +2,6 @@ package bridge
type mockMQ struct{} type mockMQ struct{}
func (m *mockMQ) Publish(e *Elements) (bool, error) { func (m *mockMQ) Publish(e *Elements) error {
return false, nil return nil
} }

View File

@@ -1,7 +1,7 @@
package pool package pool
import ( import (
"github.com/cespare/xxhash/v2" "github.com/segmentio/fasthash/fnv1a"
) )
type WorkerPool struct { type WorkerPool struct {
@@ -29,7 +29,7 @@ func New(maxWorkers int) *WorkerPool {
} }
func (p *WorkerPool) Submit(uid string, task func()) { func (p *WorkerPool) Submit(uid string, task func()) {
idx := xxhash.Sum64([]byte(uid)) % uint64(p.maxWorkers) idx := fnv1a.HashString64(uid) % uint64(p.maxWorkers)
if task != nil { if task != nil {
p.taskQueue[idx] <- task p.taskQueue[idx] <- task
} }

166
pool/pool.go Normal file
View File

@@ -0,0 +1,166 @@
package pool
// import "time"
// const (
// // This value is the size of the queue that workers register their
// // availability to the dispatcher. There may be hundreds of workers, but
// // only a small channel is needed to register some of the workers.
// readyQueueSize = 64
// // If worker pool receives no new work for this period of time, then stop
// // a worker goroutine.
// idleTimeoutSec = 5
// )
// type WorkerPool struct {
// maxWorkers int
// timeout time.Duration
// taskQueue chan func()
// readyWorkers chan chan func()
// stoppedChan chan struct{}
// }
// func New(maxWorkers int) *WorkerPool {
// // There must be at least one worker.
// if maxWorkers < 1 {
// maxWorkers = 1
// }
// // taskQueue is unbuffered since items are always removed immediately.
// pool := &WorkerPool{
// taskQueue: make(chan func()),
// maxWorkers: maxWorkers,
// readyWorkers: make(chan chan func(), readyQueueSize),
// timeout: time.Second * idleTimeoutSec,
// stoppedChan: make(chan struct{}),
// }
// // Start the task dispatcher.
// go pool.dispatch()
// return pool
// }
// func (p *WorkerPool) Stop() {
// if p.Stopped() {
// return
// }
// close(p.taskQueue)
// <-p.stoppedChan
// }
// func (p *WorkerPool) Stopped() bool {
// select {
// case <-p.stoppedChan:
// return true
// default:
// }
// return false
// }
// func (p *WorkerPool) Submit(task func()) {
// if task != nil {
// p.taskQueue <- task
// }
// }
// func (p *WorkerPool) SubmitWait(task func()) {
// if task == nil {
// return
// }
// doneChan := make(chan struct{})
// p.taskQueue <- func() {
// task()
// close(doneChan)
// }
// <-doneChan
// }
// func (p *WorkerPool) dispatch() {
// defer close(p.stoppedChan)
// timeout := time.NewTimer(p.timeout)
// var workerCount int
// var task func()
// var ok bool
// var workerTaskChan chan func()
// startReady := make(chan chan func())
// Loop:
// for {
// timeout.Reset(p.timeout)
// select {
// case task, ok = <-p.taskQueue:
// if !ok {
// break Loop
// }
// // Got a task to do.
// select {
// case workerTaskChan = <-p.readyWorkers:
// // A worker is ready, so give task to worker.
// workerTaskChan <- task
// default:
// // No workers ready.
// // Create a new worker, if not at max.
// if workerCount < p.maxWorkers {
// workerCount++
// go func(t func()) {
// startWorker(startReady, p.readyWorkers)
// // Submit the task when the new worker.
// taskChan := <-startReady
// taskChan <- t
// }(task)
// } else {
// // Start a goroutine to submit the task when an existing
// // worker is ready.
// go func(t func()) {
// taskChan := <-p.readyWorkers
// taskChan <- t
// }(task)
// }
// }
// case <-timeout.C:
// // Timed out waiting for work to arrive. Kill a ready worker.
// if workerCount > 0 {
// select {
// case workerTaskChan = <-p.readyWorkers:
// // A worker is ready, so kill.
// close(workerTaskChan)
// workerCount--
// default:
// // No work, but no ready workers. All workers are busy.
// }
// }
// }
// }
// // Stop all remaining workers as they become ready.
// for workerCount > 0 {
// workerTaskChan = <-p.readyWorkers
// close(workerTaskChan)
// workerCount--
// }
// }
// func startWorker(startReady, readyWorkers chan chan func()) {
// go func() {
// taskChan := make(chan func())
// var task func()
// var ok bool
// // Register availability on starReady channel.
// startReady <- taskChan
// for {
// // Read task from dispatcher.
// task, ok = <-taskChan
// if !ok {
// // Dispatcher has told worker to stop.
// break
// }
// // Execute the task.
// task()
// // Register availability on readyWorkers channel.
// readyWorkers <- taskChan
// }
// }()
// }