refactor: rm mqtt broker & rm db file
parent
2c16f21ff1
commit
b6b8094b83
|
@ -14,3 +14,6 @@ hummingbird
|
|||
## binary
|
||||
cmd/hummingbird-core/hummingbird-core
|
||||
cmd/mqtt-broker/mqtt-broker
|
||||
|
||||
kuiper
|
||||
db-data/leveldb-core-data
|
|
@ -1,42 +0,0 @@
|
|||
# ----------------------------------------------------------------------------------
|
||||
# Copyright 2018 Dell Technologies, Inc.
|
||||
# Copyright 2018 Cavium
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
ARG BUILDER_BASE=golang:latest
|
||||
FROM ${BUILDER_BASE} AS builder
|
||||
|
||||
WORKDIR /edge
|
||||
|
||||
# gitlab
|
||||
COPY . .
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build make cmd/mqtt-broker/mqtt-broker
|
||||
|
||||
#Next image - Copy built Go binary into new workspace
|
||||
FROM alpine:3.16
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apk apk add --update --no-cache dumb-init
|
||||
|
||||
EXPOSE 58090
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=builder /edge/cmd/mqtt-broker/mqtt-broker /bin/
|
||||
COPY --from=builder /edge/cmd/mqtt-broker/res/configuration.yml.dist /etc/emqtt-broker/res/configuration.yml
|
||||
|
||||
#RUN mkdir -p /logs/mqtt-broker
|
||||
|
||||
CMD ["/bin/sh", "-c", "/bin/mqtt-broker start -c=/etc/emqtt-broker/res/configuration.yml"]
|
|
@ -1,39 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 Dell Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package initcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/winc-link/hummingbird/cmd/mqtt-broker/mqttd"
|
||||
"github.com/winc-link/hummingbird/cmd/mqtt-broker/mqttd/command"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func Init(rootCmd *cobra.Command) {
|
||||
configDir, err := mqttd.GetDefaultConfigDir()
|
||||
must(err)
|
||||
command.ConfigFile = path.Join(configDir, "configuration.yml")
|
||||
rootCmd.PersistentFlags().StringVarP(&command.ConfigFile, "config", "c", command.ConfigFile, "The configuration file path")
|
||||
rootCmd.AddCommand(command.NewStartCmd())
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 Dell Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/winc-link/hummingbird/cmd/mqtt-broker/initcmd"
|
||||
|
||||
_ "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence"
|
||||
_ "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/topicalias/fifo"
|
||||
|
||||
_ "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/plugin/admin"
|
||||
_ "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/plugin/aplugin"
|
||||
_ "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/plugin/auth"
|
||||
_ "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/plugin/federation"
|
||||
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "mqttd",
|
||||
Long: "This is a MQTT broker that fully implements MQTT V5.0 and V3.1.1 protocol",
|
||||
Version: "",
|
||||
}
|
||||
enablePprof bool
|
||||
pprofAddr = "127.0.0.1:60600"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if enablePprof {
|
||||
go func() {
|
||||
http.ListenAndServe(pprofAddr, nil)
|
||||
}()
|
||||
}
|
||||
initcmd.Init(rootCmd)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprint(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/pidfile"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ConfigFile string
|
||||
logger *zap.Logger
|
||||
)
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func installSignal(srv server.Server) {
|
||||
// reload
|
||||
reloadSignalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(reloadSignalCh, syscall.SIGHUP)
|
||||
|
||||
// stop
|
||||
stopSignalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(stopSignalCh, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-reloadSignalCh:
|
||||
var c config.Config
|
||||
var err error
|
||||
c, err = config.ParseConfig(ConfigFile)
|
||||
if err != nil {
|
||||
logger.Error("reload error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
srv.ApplyConfig(c)
|
||||
logger.Info("gmqtt reloaded")
|
||||
case <-stopSignalCh:
|
||||
err := srv.Stop(context.Background())
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
//fmt.Fprint(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetListeners(c config.Config) (tcpListeners []net.Listener, websockets []*server.WsServer, err error) {
|
||||
for _, v := range c.Listeners {
|
||||
var ln net.Listener
|
||||
if v.Websocket != nil {
|
||||
ws := &server.WsServer{
|
||||
Server: &http.Server{Addr: v.Address},
|
||||
Path: v.Websocket.Path,
|
||||
}
|
||||
if v.TLSOptions != nil {
|
||||
ws.KeyFile = v.Key
|
||||
ws.CertFile = v.Cert
|
||||
}
|
||||
websockets = append(websockets, ws)
|
||||
continue
|
||||
}
|
||||
if v.TLSOptions != nil {
|
||||
var cert tls.Certificate
|
||||
cert, err = tls.LoadX509KeyPair(v.Cert, v.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ln, err = tls.Listen("tcp", v.Address, &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
})
|
||||
} else {
|
||||
ln, err = net.Listen("tcp", v.Address)
|
||||
}
|
||||
tcpListeners = append(tcpListeners, ln)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewStartCmd creates a *cobra.Command object for start command.
|
||||
func NewStartCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start gmqtt broker",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var err error
|
||||
must(err)
|
||||
c, err := config.ParseConfig(ConfigFile)
|
||||
if os.IsNotExist(err) {
|
||||
must(err)
|
||||
} else {
|
||||
must(err)
|
||||
}
|
||||
if c.PidFile != "" {
|
||||
pid, err := pidfile.New(c.PidFile)
|
||||
if err != nil {
|
||||
must(fmt.Errorf("open pid file failed: %s", err))
|
||||
}
|
||||
defer pid.Remove()
|
||||
}
|
||||
|
||||
level, l, err := c.GetLogger(c.Log)
|
||||
must(err)
|
||||
logger = l
|
||||
|
||||
//db := mqttbroker.NewDatabase(c)
|
||||
//err = db.InitDBClient(l)
|
||||
//must(err)
|
||||
|
||||
tcpListeners, websockets, err := GetListeners(c)
|
||||
must(err)
|
||||
|
||||
s := server.New(
|
||||
server.WithConfig(c),
|
||||
server.WithTCPListener(tcpListeners...),
|
||||
server.WithWebsocketServer(websockets...),
|
||||
server.WithLogger(&server.DefaultLogger{
|
||||
Level: level,
|
||||
Logger: l,
|
||||
}),
|
||||
)
|
||||
|
||||
err = s.Init()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
go installSignal(s)
|
||||
err = s.Run()
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package mqttd
|
||||
|
||||
var (
|
||||
DefaultConfigDir = "./res/"
|
||||
)
|
||||
|
||||
func GetDefaultConfigDir() (string, error) {
|
||||
return DefaultConfigDir, nil
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var tmpl = `//go:generate sh -c "cd ../../ && go run plugin_generate.go"
|
||||
// generated by plugin_generate.go; DO NOT EDIT
|
||||
|
||||
package mqttd
|
||||
|
||||
import (
|
||||
{{- range $index, $element := .}}
|
||||
_ "{{$element}}"
|
||||
{{- end}}
|
||||
)
|
||||
`
|
||||
|
||||
const (
|
||||
pluginFile = "./mqttd/plugins.go"
|
||||
pluginCfg = "plugin_imports.yml"
|
||||
importPath = "gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/plugin"
|
||||
)
|
||||
|
||||
type ymlCfg struct {
|
||||
Packages []string `yaml:"packages"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
b, err := ioutil.ReadFile(pluginCfg)
|
||||
if err != nil {
|
||||
log.Fatalf("ReadFile error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg ymlCfg
|
||||
err = yaml.Unmarshal(b, &cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Unmarshal error: %s", err)
|
||||
return
|
||||
}
|
||||
t, err := template.New("plugin_gen").Parse(tmpl)
|
||||
if err != nil {
|
||||
log.Fatalf("Parse template error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range cfg.Packages {
|
||||
if !strings.Contains(v, "/") {
|
||||
cfg.Packages[k] = importPath + "/" + v
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatalf("read error: %s", err)
|
||||
return
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
err = t.Execute(buf, cfg.Packages)
|
||||
if err != nil {
|
||||
log.Fatalf("excute template error: %s", err)
|
||||
return
|
||||
}
|
||||
rs, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("format error: %s", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(pluginFile, rs, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("writeFile error: %s", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
packages:
|
||||
- admin
|
||||
# - federation
|
||||
- aplugin
|
||||
# for external plugin, use full import path
|
||||
# - gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/plugin/prometheus
|
|
@ -1,22 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDszCCApugAwIBAgIJAJXKBu6eNV6YMA0GCSqGSIb3DQEBCwUAMHAxCzAJBgNV
|
||||
BAYTAkNOMQswCQYDVQQIDAJaSjELMAkGA1UEBwwCSFoxCzAJBgNVBAoMAlRZMQsw
|
||||
CQYDVQQLDAJUWTEOMAwGA1UEAwwFdGVkZ2UxHTAbBgkqhkiG9w0BCQEWDnRlZGdl
|
||||
QHR1eWEuY29tMB4XDTIyMDEyNDA2NTQ0M1oXDTMyMDEyMjA2NTQ0M1owcDELMAkG
|
||||
A1UEBhMCQ04xCzAJBgNVBAgMAlpKMQswCQYDVQQHDAJIWjELMAkGA1UECgwCVFkx
|
||||
CzAJBgNVBAsMAlRZMQ4wDAYDVQQDDAV0ZWRnZTEdMBsGCSqGSIb3DQEJARYOdGVk
|
||||
Z2VAdHV5YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/IZfF
|
||||
++ytZVIDbt5Ypz/55e0HTrq9jrpVOZAKSBbmSUryjpo8NfZoDp5QVZi4kSo1G0xV
|
||||
Wf9C+5h13TFM2pDm9W9q4v8e3cB3Z+qK8nHn66xyQYnTihg8D9vyJHIQ2nirCVqW
|
||||
HL2wYdakE0MojbVsQPWufYh84tWXyyUIo2W2ycoXmSfpWhb4LDEf4tcmDBNp2ydG
|
||||
ef7MNbrS3t/h/iOzqjj7s+styiLyKjxE0oh1VfOOp8e9HPnh2EvaQwwTq91KRf+v
|
||||
rl4DPZt93oMd9i28HuxBsWsE6eDRfYmF96ZoIXEh4ga9XWR8geuRCsTREQo7tqUX
|
||||
gXFUXCe2Uo6R0uh9AgMBAAGjUDBOMB0GA1UdDgQWBBQayDeoKN44f/FV+Z6rv1vT
|
||||
bsITvTAfBgNVHSMEGDAWgBQayDeoKN44f/FV+Z6rv1vTbsITvTAMBgNVHRMEBTAD
|
||||
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB0G/AM7zU2USZj3C32zzzM6LQNx465x1i2
|
||||
XgSw9ECM7M3ct6x859L6vKXWUm6+OQO7jm9xRRyDQIrCSQT66MCei5C+nqzyPIZA
|
||||
zL5cV7bRXA39KBwThyZZqWl4bttp98UZnEbX6yICVEcjsnaIA2D0vh1zZar4Ilyq
|
||||
mBl4NA13HTIcQ0s4Efuhdf5RdPw3ha2cjf74aNNj2WijAY4rKEVF1Buw/PrvJ7WR
|
||||
hrQlNrv214e3hbjV99oiII8OLDT0oJApUbSr6ktjF26bAu929b3QDADEK9QpBYE1
|
||||
brg3KD51xgD+HGKd3PVLqr60y7OQKKMHd8TrQK/ibQVgFdbE6/AG
|
||||
-----END CERTIFICATE-----
|
|
@ -1,159 +0,0 @@
|
|||
# Path to pid file.
|
||||
# If not set, there will be no pid file.
|
||||
# pid_file: /var/run/mqttd.pid
|
||||
|
||||
listeners:
|
||||
# bind address
|
||||
- address: ":58090" # 58090
|
||||
|
||||
# - address: ":21883" # 21883
|
||||
# tls:
|
||||
# cacert: "/etc/mqtt-broker/ca.crt"
|
||||
# cert: "/etc/mqtt-broker/server.pem"
|
||||
# key: "/etc/mqtt-broker/server.key"
|
||||
#
|
||||
# cacert: "cmd/mqtt-broker/res/ca.crt"
|
||||
# cert: "cmd/mqtt-broker/res/server.pem"
|
||||
# key: "cmd/mqtt-broker/res/server.key"
|
||||
#
|
||||
# - address: ":28883" # 28883
|
||||
# # websocket setting
|
||||
# websocket:
|
||||
# path: "/"
|
||||
|
||||
api:
|
||||
grpc:
|
||||
# The gRPC server listen address. Supports unix socket and tcp socket.
|
||||
- address: "tcp://127.0.0.1:57090" # 57090
|
||||
http:
|
||||
# The HTTP server listen address. This is a reverse-proxy server in front of gRPC server.
|
||||
- address: "tcp://127.0.0.1:57091" # 57091
|
||||
map: "tcp://127.0.0.1:57090" # The backend gRPC server endpoint,
|
||||
|
||||
mqtt:
|
||||
# The maximum session expiry interval in seconds.
|
||||
session_expiry: 2h
|
||||
# The interval time for session expiry checker to check whether there are expired sessions.
|
||||
session_expiry_check_timer: 20s
|
||||
# The maximum lifetime of the message in seconds.
|
||||
# If a message in the queue is not sent in message_expiry time, it will be dropped, which means it will not be sent to the subscriber.
|
||||
message_expiry: 2h
|
||||
# The lifetime of the "inflight" message in seconds.
|
||||
# If a "inflight" message is not acknowledged by a client in inflight_expiry time, it will be removed when the message queue is full.
|
||||
inflight_expiry: 30s
|
||||
# The maximum packet size that the server is willing to accept from the client.
|
||||
max_packet_size: 268435456
|
||||
# The maximum number of QoS 1 and QoS 2 publications that the server is willing to process concurrently for the client.
|
||||
server_receive_maximum: 100
|
||||
# The maximum keep alive time in seconds allows by the server.
|
||||
# If the client requests a keepalive time bigger than MaxKeepalive,the server will use MaxKeepAlive as the keepalive time.
|
||||
# In this case, if the client version is v5, the server will set MaxKeepalive into CONNACK to inform the client.
|
||||
# But if the client version is 3.x, the server has no way to inform the client that the keepalive time has been changed.
|
||||
max_keepalive: 300
|
||||
# The highest value that the server will accept as a Topic Alias sent by the client.
|
||||
# No-op if the client version is MQTTv3.x .
|
||||
topic_alias_maximum: 10
|
||||
# Whether the server supports Subscription Identifiers.
|
||||
# No-op if the client version is MQTTv3.x .
|
||||
subscription_identifier_available: true
|
||||
# Whether the server supports Wildcard Subscriptions.
|
||||
wildcard_subscription_available: true
|
||||
# Whether the server supports Shared Subscriptions.
|
||||
shared_subscription_available: true
|
||||
# The highest QOS level permitted for a Publish.
|
||||
maximum_qos: 2
|
||||
# Whether the server supports retained messages.
|
||||
retain_available: true
|
||||
# The maximum queue length of the outgoing messages.
|
||||
# If the queue is full, some message will be dropped.
|
||||
# The message dropping strategy is described in the document of the persistence/queue.Store interface.
|
||||
max_queued_messages: 1000
|
||||
# The limits of inflight message length of the outgoing messages.
|
||||
# Inflight message is also stored in the message queue, so it must be less than or equal to max_queued_messages.
|
||||
# Inflight message is the QoS 1 or QoS 2 message that has been sent out to a client but not been acknowledged yet.
|
||||
max_inflight: 100
|
||||
# Whether to store QoS 0 message for a offline session.
|
||||
queue_qos0_messages: true
|
||||
# The delivery mode. The possible value can be "overlap" or "onlyonce".
|
||||
# It is possible for a client’s subscriptions to overlap so that a published message might match multiple filters.
|
||||
# When set to "overlap" , the server will deliver one message for each matching subscription and respecting the subscription’s QoS in each case.
|
||||
# When set to "onlyonce", the server will deliver the message to the client respecting the maximum QoS of all the matching subscriptions.
|
||||
delivery_mode: onlyonce
|
||||
# Whether to allow a client to connect with empty client id.
|
||||
allow_zero_length_clientid: true
|
||||
|
||||
persistence:
|
||||
type: memory # memory | redis
|
||||
# The redis configuration only take effect when type == redis.
|
||||
redis:
|
||||
# redis server address
|
||||
addr: "127.0.0.1:56379"
|
||||
# the maximum number of idle connections in the redis connection pool.
|
||||
max_idle: 1000
|
||||
# the maximum number of connections allocated by the redis connection pool at a given time.
|
||||
# If zero, there is no limit on the number of connections in the pool.
|
||||
max_active: 0
|
||||
# the connection idle timeout, connection will be closed after remaining idle for this duration. If the value is zero, then idle connections are not closed.
|
||||
idle_timeout: 240s
|
||||
password: "qqwihyzjb8l2sx0c"
|
||||
# the number of the redis database.
|
||||
database: 0
|
||||
|
||||
# The topic alias manager setting. The topic alias feature is introduced by MQTT V5.
|
||||
# This setting is used to control how the broker manage topic alias.
|
||||
topic_alias_manager:
|
||||
# Currently, only FIFO strategy is supported.
|
||||
type: fifo
|
||||
|
||||
plugins:
|
||||
aplugin:
|
||||
# Password hash type. (plain | md5 | sha256 | bcrypt)
|
||||
# Default to MD5.
|
||||
hash: md5
|
||||
# The file to store password. If it is a relative path, it locates in the same directory as the config file.
|
||||
# (e.g: ./gmqtt_password => /etc/gmqtt/gmqtt_password.yml)
|
||||
# Defaults to ./gmqtt_password.yml
|
||||
# password_file:
|
||||
federation:
|
||||
# node_name is the unique identifier for the node in the federation. Defaults to hostname.
|
||||
# node_name:
|
||||
# fed_addr is the gRPC server listening address for the federation internal communication. Defaults to :8901
|
||||
fed_addr: :8901
|
||||
# advertise_fed_addr is used to change the federation gRPC server address that we advertise to other nodes in the cluster.
|
||||
# Defaults to "fed_addr".However, in some cases, there may be a routable address that cannot be bound.
|
||||
# If the port is missing, the default federation port (8901) will be used.
|
||||
advertise_fed_addr: :8901
|
||||
# gossip_addr is the address that the gossip will listen on, It is used for both UDP and TCP gossip. Defaults to :8902
|
||||
gossip_addr: :8902
|
||||
# advertise_gossip_addr is used to change the gossip server address that we advertise to other nodes in the cluster.
|
||||
# Defaults to "GossipAddr" or the private IP address of the node if the IP in "GossipAddr" is 0.0.0.0.
|
||||
# If the port is missing, the default gossip port (8902) will be used.
|
||||
advertise_gossip_addr: :8902
|
||||
|
||||
# retry_join is the address of other nodes to join upon starting up.
|
||||
# If port is missing, the default gossip port (8902) will be used.
|
||||
#retry_join:
|
||||
# - 127.0.0.1:8902
|
||||
|
||||
# rejoin_after_leave will be pass to "RejoinAfterLeave" in serf configuration.
|
||||
# It controls our interaction with the snapshot file.
|
||||
# When set to false (default), a leave causes a Serf to not rejoin the cluster until an explicit join is received.
|
||||
# If this is set to true, we ignore the leave, and rejoin the cluster on start.
|
||||
rejoin_after_leave: false
|
||||
# snapshot_path will be pass to "SnapshotPath" in serf configuration.
|
||||
# When Serf is started with a snapshot,it will attempt to join all the previously known nodes until one
|
||||
# succeeds and will also avoid replaying old user events.
|
||||
snapshot_path:
|
||||
|
||||
# plugin loading orders
|
||||
plugin_order:
|
||||
# Uncomment auth to enable authentication.
|
||||
- aplugin
|
||||
#- admin
|
||||
#- federation
|
||||
log:
|
||||
level: debug # debug | info | warn | error
|
||||
file_path: "./mqtt-broker/mqtt-broker.log"
|
||||
# whether to dump MQTT packet in debug level
|
||||
dump_packet: false
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
# Path to pid file.
|
||||
# If not set, there will be no pid file.
|
||||
# pid_file: /var/run/mqttd.pid
|
||||
|
||||
listeners:
|
||||
# bind address
|
||||
- address: ":58090" # 58090
|
||||
|
||||
# - address: ":21883" # 21883
|
||||
# tls:
|
||||
# cacert: "/etc/mqtt-broker/ca.crt"
|
||||
# cert: "/etc/mqtt-broker/server.pem"
|
||||
# key: "/etc/mqtt-broker/server.key"
|
||||
#
|
||||
# cacert: "cmd/mqtt-broker/res/ca.crt"
|
||||
# cert: "cmd/mqtt-broker/res/server.pem"
|
||||
# key: "cmd/mqtt-broker/res/server.key"
|
||||
#
|
||||
# - address: ":28883" # 28883
|
||||
# # websocket setting
|
||||
# websocket:
|
||||
# path: "/"
|
||||
|
||||
api:
|
||||
grpc:
|
||||
# The gRPC server listen address. Supports unix socket and tcp socket.
|
||||
- address: "tcp://127.0.0.1:57090" # 57090
|
||||
http:
|
||||
# The HTTP server listen address. This is a reverse-proxy server in front of gRPC server.
|
||||
- address: "tcp://127.0.0.1:57091" # 57091
|
||||
map: "tcp://127.0.0.1:57090" # The backend gRPC server endpoint,
|
||||
|
||||
mqtt:
|
||||
# The maximum session expiry interval in seconds.
|
||||
session_expiry: 2h
|
||||
# The interval time for session expiry checker to check whether there are expired sessions.
|
||||
session_expiry_check_timer: 20s
|
||||
# The maximum lifetime of the message in seconds.
|
||||
# If a message in the queue is not sent in message_expiry time, it will be dropped, which means it will not be sent to the subscriber.
|
||||
message_expiry: 2h
|
||||
# The lifetime of the "inflight" message in seconds.
|
||||
# If a "inflight" message is not acknowledged by a client in inflight_expiry time, it will be removed when the message queue is full.
|
||||
inflight_expiry: 30s
|
||||
# The maximum packet size that the server is willing to accept from the client.
|
||||
max_packet_size: 268435456
|
||||
# The maximum number of QoS 1 and QoS 2 publications that the server is willing to process concurrently for the client.
|
||||
server_receive_maximum: 100
|
||||
# The maximum keep alive time in seconds allows by the server.
|
||||
# If the client requests a keepalive time bigger than MaxKeepalive,the server will use MaxKeepAlive as the keepalive time.
|
||||
# In this case, if the client version is v5, the server will set MaxKeepalive into CONNACK to inform the client.
|
||||
# But if the client version is 3.x, the server has no way to inform the client that the keepalive time has been changed.
|
||||
max_keepalive: 300
|
||||
# The highest value that the server will accept as a Topic Alias sent by the client.
|
||||
# No-op if the client version is MQTTv3.x .
|
||||
topic_alias_maximum: 10
|
||||
# Whether the server supports Subscription Identifiers.
|
||||
# No-op if the client version is MQTTv3.x .
|
||||
subscription_identifier_available: true
|
||||
# Whether the server supports Wildcard Subscriptions.
|
||||
wildcard_subscription_available: true
|
||||
# Whether the server supports Shared Subscriptions.
|
||||
shared_subscription_available: true
|
||||
# The highest QOS level permitted for a Publish.
|
||||
maximum_qos: 2
|
||||
# Whether the server supports retained messages.
|
||||
retain_available: true
|
||||
# The maximum queue length of the outgoing messages.
|
||||
# If the queue is full, some message will be dropped.
|
||||
# The message dropping strategy is described in the document of the persistence/queue.Store interface.
|
||||
max_queued_messages: 1000
|
||||
# The limits of inflight message length of the outgoing messages.
|
||||
# Inflight message is also stored in the message queue, so it must be less than or equal to max_queued_messages.
|
||||
# Inflight message is the QoS 1 or QoS 2 message that has been sent out to a client but not been acknowledged yet.
|
||||
max_inflight: 100
|
||||
# Whether to store QoS 0 message for a offline session.
|
||||
queue_qos0_messages: true
|
||||
# The delivery mode. The possible value can be "overlap" or "onlyonce".
|
||||
# It is possible for a client’s subscriptions to overlap so that a published message might match multiple filters.
|
||||
# When set to "overlap" , the server will deliver one message for each matching subscription and respecting the subscription’s QoS in each case.
|
||||
# When set to "onlyonce", the server will deliver the message to the client respecting the maximum QoS of all the matching subscriptions.
|
||||
delivery_mode: onlyonce
|
||||
# Whether to allow a client to connect with empty client id.
|
||||
allow_zero_length_clientid: true
|
||||
|
||||
persistence:
|
||||
type: memory # memory | redis
|
||||
# The redis configuration only take effect when type == redis.
|
||||
redis:
|
||||
# redis server address
|
||||
addr: "127.0.0.1:56379"
|
||||
# the maximum number of idle connections in the redis connection pool.
|
||||
max_idle: 1000
|
||||
# the maximum number of connections allocated by the redis connection pool at a given time.
|
||||
# If zero, there is no limit on the number of connections in the pool.
|
||||
max_active: 0
|
||||
# the connection idle timeout, connection will be closed after remaining idle for this duration. If the value is zero, then idle connections are not closed.
|
||||
idle_timeout: 240s
|
||||
password: "qqwihyzjb8l2sx0c"
|
||||
# the number of the redis database.
|
||||
database: 0
|
||||
|
||||
# The topic alias manager setting. The topic alias feature is introduced by MQTT V5.
|
||||
# This setting is used to control how the broker manage topic alias.
|
||||
topic_alias_manager:
|
||||
# Currently, only FIFO strategy is supported.
|
||||
type: fifo
|
||||
|
||||
plugins:
|
||||
aplugin:
|
||||
# Password hash type. (plain | md5 | sha256 | bcrypt)
|
||||
# Default to MD5.
|
||||
hash: md5
|
||||
# The file to store password. If it is a relative path, it locates in the same directory as the config file.
|
||||
# (e.g: ./gmqtt_password => /etc/gmqtt/gmqtt_password.yml)
|
||||
# Defaults to ./gmqtt_password.yml
|
||||
# password_file:
|
||||
federation:
|
||||
# node_name is the unique identifier for the node in the federation. Defaults to hostname.
|
||||
# node_name:
|
||||
# fed_addr is the gRPC server listening address for the federation internal communication. Defaults to :8901
|
||||
fed_addr: :8901
|
||||
# advertise_fed_addr is used to change the federation gRPC server address that we advertise to other nodes in the cluster.
|
||||
# Defaults to "fed_addr".However, in some cases, there may be a routable address that cannot be bound.
|
||||
# If the port is missing, the default federation port (8901) will be used.
|
||||
advertise_fed_addr: :8901
|
||||
# gossip_addr is the address that the gossip will listen on, It is used for both UDP and TCP gossip. Defaults to :8902
|
||||
gossip_addr: :8902
|
||||
# advertise_gossip_addr is used to change the gossip server address that we advertise to other nodes in the cluster.
|
||||
# Defaults to "GossipAddr" or the private IP address of the node if the IP in "GossipAddr" is 0.0.0.0.
|
||||
# If the port is missing, the default gossip port (8902) will be used.
|
||||
advertise_gossip_addr: :8902
|
||||
|
||||
# retry_join is the address of other nodes to join upon starting up.
|
||||
# If port is missing, the default gossip port (8902) will be used.
|
||||
#retry_join:
|
||||
# - 127.0.0.1:8902
|
||||
|
||||
# rejoin_after_leave will be pass to "RejoinAfterLeave" in serf configuration.
|
||||
# It controls our interaction with the snapshot file.
|
||||
# When set to false (default), a leave causes a Serf to not rejoin the cluster until an explicit join is received.
|
||||
# If this is set to true, we ignore the leave, and rejoin the cluster on start.
|
||||
rejoin_after_leave: false
|
||||
# snapshot_path will be pass to "SnapshotPath" in serf configuration.
|
||||
# When Serf is started with a snapshot,it will attempt to join all the previously known nodes until one
|
||||
# succeeds and will also avoid replaying old user events.
|
||||
snapshot_path:
|
||||
|
||||
# plugin loading orders
|
||||
plugin_order:
|
||||
# Uncomment auth to enable authentication.
|
||||
- aplugin
|
||||
#- admin
|
||||
#- federation
|
||||
log:
|
||||
level: debug # debug | info | warn | error
|
||||
file_path: "/logs/mqtt-broker/mqtt-broker.log"
|
||||
# whether to dump MQTT packet in debug level
|
||||
dump_packet: false
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# This is a sample plain password file for the auth plugin.
|
||||
- username: root
|
||||
password: root
|
|
@ -1,14 +0,0 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICFTCCAX4CAQAwcDELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAlpKMQswCQYDVQQH
|
||||
DAJIWjELMAkGA1UECgwCVFkxCzAJBgNVBAsMAlRZMQ4wDAYDVQQDDAV0ZWRnZTEd
|
||||
MBsGCSqGSIb3DQEJARYOdGVkZ2VAdHV5YS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
|
||||
gY0AMIGJAoGBAMe1bzSZLfvqrBeBOgxAdaDqh8fWudqeb0wqC+ZSZg4uH+WEG4Hu
|
||||
rMbt4B+b1U98ctpA/aOEgnZiV1z79w8Rm9ENvfCUOKJ8uJyVf2usAdR/HkudDOhU
|
||||
KlnvXaCd5t99gi8pyBmYkaXf82ya7CN97f/Y35zNIcWTJhJmYmd3N6FRAgMBAAGg
|
||||
ZTARBgkqhkiG9w0BCQIxBAwCdHkwFQYJKoZIhvcNAQkHMQgMBmJleW9uZDA5Bgkq
|
||||
hkiG9w0BCQ4xLDAqMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBAGA1UdEQQJMAeC
|
||||
BXRlZGdlMA0GCSqGSIb3DQEBCwUAA4GBADSeemIkKCvrfOz+m0AxQN/9L8SEqdHB
|
||||
l8YBaHSHgdxq6666ENPz5o2uPNnu6qYaBZUMOZ5223Sx2MJPNDAxemFnOw7YbnCV
|
||||
jIPwI3O9KIFDZ+tmhEIVHSlqRFphYNIWAVVFBsdNkse1gLTLLBLKfbsCZeoD4Dz2
|
||||
mc3JPZjeo4Mq
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -1,16 +0,0 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMe1bzSZLfvqrBeB
|
||||
OgxAdaDqh8fWudqeb0wqC+ZSZg4uH+WEG4HurMbt4B+b1U98ctpA/aOEgnZiV1z7
|
||||
9w8Rm9ENvfCUOKJ8uJyVf2usAdR/HkudDOhUKlnvXaCd5t99gi8pyBmYkaXf82ya
|
||||
7CN97f/Y35zNIcWTJhJmYmd3N6FRAgMBAAECgYBVCLMGIWcMCdsm0vZlexja4KHZ
|
||||
/Fr8dFONiaWxd0pPJWKddofD5l2ZAnZY3yCPjLzWo6+b7XMjdzIdvIdw2h2OwGpE
|
||||
kPQST1lkN5VlPIG67jwmIyJVw1LBAqknmqRFLjJ8NcJRttNjYjEkpetMOq1rM3Di
|
||||
90mY3lBLT2g5lZa0pQJBAOr0lEPC3WJMq1N04wzqM6h6y8FUKhGZDawl1HGne9bs
|
||||
4IDzVEhiCT9VvN3eoX+bk6av1/uZOxHY78j81q8KzY8CQQDZmKpPOZFeBK8dIOt7
|
||||
L4XB1NMVAkOy4UFZ1I9lpn9OVSQEPrnV0oyMnKIIMCzGy4nnFmrY/u4LKpuYSoVO
|
||||
lvMfAkAHwhOzORf+SvHNS6rDnmgeRA++Tn0lH5yn9ofRSOp56lBvcZly2mnbwYT+
|
||||
/n7uq8BwXJYRJLoimLsyM8cS+JRZAkAp40Glzqc1OiGbseKi7BsLnTSlLrJplQNH
|
||||
j6urHcoUAj/UsV6E0utLhjuK5/s2qaf6XE5lR237qFAbmPzgjB5xAkAP7H0cfdzs
|
||||
X9gCe4RqQgIuJzK6Y59GkVeWVT8lScL9FyWm2JmeGh907HgnTXt6t8Hhk23JxD3x
|
||||
KNcIk5xzRaOO
|
||||
-----END PRIVATE KEY-----
|
|
@ -1,19 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAMMZRyEj7GMLMA0GCSqGSIb3DQEBCwUAMHAxCzAJBgNV
|
||||
BAYTAkNOMQswCQYDVQQIDAJaSjELMAkGA1UEBwwCSFoxCzAJBgNVBAoMAlRZMQsw
|
||||
CQYDVQQLDAJUWTEOMAwGA1UEAwwFdGVkZ2UxHTAbBgkqhkiG9w0BCQEWDnRlZGdl
|
||||
QHR1eWEuY29tMB4XDTIyMDEyNDA3MDAxNVoXDTMyMDEyMjA3MDAxNVowcDELMAkG
|
||||
A1UEBhMCQ04xCzAJBgNVBAgMAlpKMQswCQYDVQQHDAJIWjELMAkGA1UECgwCVFkx
|
||||
CzAJBgNVBAsMAlRZMQ4wDAYDVQQDDAV0ZWRnZTEdMBsGCSqGSIb3DQEJARYOdGVk
|
||||
Z2VAdHV5YS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMe1bzSZLfvq
|
||||
rBeBOgxAdaDqh8fWudqeb0wqC+ZSZg4uH+WEG4HurMbt4B+b1U98ctpA/aOEgnZi
|
||||
V1z79w8Rm9ENvfCUOKJ8uJyVf2usAdR/HkudDOhUKlnvXaCd5t99gi8pyBmYkaXf
|
||||
82ya7CN97f/Y35zNIcWTJhJmYmd3N6FRAgMBAAGjLDAqMAkGA1UdEwQCMAAwCwYD
|
||||
VR0PBAQDAgXgMBAGA1UdEQQJMAeCBXRlZGdlMA0GCSqGSIb3DQEBCwUAA4IBAQA5
|
||||
htWsbfo7XedP2DBbVRXWFhEw7RPFfmyFMgzQq3aifnNB93xpDRwauXH5k6TEsiIO
|
||||
OKjQit9aiSA28sTad6k6S09SwJokeQ9l14T3vVVMdDVJCw1Hq/mEhgoGgpYM+om0
|
||||
t/gl7e4FHL0AH6vcAyO70Q4uVRGpnm6Ehp8MxW0f/uip6TLxSj3lTitkCytMSGMK
|
||||
WhvTLy8gsD9sSkiZUL/jknVkSp5An3roayWZLZucPV0E2rINchRcMcrrY1UkeYu1
|
||||
HB94dGg2U7R7Qj0eJBdxJN0uCY5n02pBXabJXRtwvOReHsW6Qoo50MhWEd2sazCA
|
||||
HwMRWr6g8aAun7QJfySG
|
||||
-----END CERTIFICATE-----
|
32
go.mod
32
go.mod
|
@ -15,17 +15,9 @@ require (
|
|||
github.com/go-gormigrate/gormigrate/v2 v2.0.0
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/golang/mock v1.5.0
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/gomodule/redigo v1.8.9
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
github.com/hashicorp/go-sockaddr v1.0.0
|
||||
github.com/hashicorp/logutils v1.0.0
|
||||
github.com/hashicorp/serf v0.8.2
|
||||
github.com/hpcloud/tail v1.0.0
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
|
@ -38,7 +30,6 @@ require (
|
|||
github.com/pkg/errors v0.9.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
||||
|
@ -57,11 +48,9 @@ require (
|
|||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/text v0.11.0
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c
|
||||
google.golang.org/grpc v1.49.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.0.1
|
||||
gorm.io/driver/sqlite v1.3.6
|
||||
|
@ -71,9 +60,6 @@ require (
|
|||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
@ -94,17 +80,10 @@ require (
|
|||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
|
||||
github.com/hashicorp/go-msgpack v0.5.3 // indirect
|
||||
github.com/hashicorp/go-multierror v1.0.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||
github.com/hashicorp/memberlist v0.1.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
|
@ -117,8 +96,6 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/miekg/dns v1.0.14 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
|
@ -130,14 +107,9 @@ require (
|
|||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.3 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
|
@ -151,7 +123,9 @@ require (
|
|||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.3.0 // indirect
|
||||
)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 DrmagicE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,77 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// API is the configuration for API server.
|
||||
// The API server use gRPC-gateway to provide both gRPC and HTTP endpoints.
|
||||
type API struct {
|
||||
// GRPC is the gRPC endpoint configuration.
|
||||
GRPC []*Endpoint `yaml:"grpc"`
|
||||
// HTTP is the HTTP endpoint configuration.
|
||||
HTTP []*Endpoint `yaml:"http"`
|
||||
}
|
||||
|
||||
// Endpoint represents a gRPC or HTTP server endpoint.
|
||||
type Endpoint struct {
|
||||
// Address is the bind address of the endpoint.
|
||||
// Format: [tcp|unix://][<host>]:<port>
|
||||
// e.g :
|
||||
// * unix:///var/run/mqttd.sock
|
||||
// * tcp://127.0.0.1:8080
|
||||
// * :8081 (equal to tcp://:8081)
|
||||
Address string `yaml:"address"`
|
||||
// Map maps the HTTP endpoint to gRPC endpoint.
|
||||
// Must be set if the endpoint is representing a HTTP endpoint.
|
||||
Map string `yaml:"map"`
|
||||
// TLS is the tls configuration.
|
||||
TLS *TLSOptions `yaml:"tls"`
|
||||
}
|
||||
|
||||
var DefaultAPI API
|
||||
|
||||
func (a API) validateAddress(address string, fieldName string) error {
|
||||
if address == "" {
|
||||
return fmt.Errorf("%s cannot be empty", fieldName)
|
||||
}
|
||||
epParts := strings.SplitN(address, "://", 2)
|
||||
if len(epParts) == 1 && epParts[0] != "" {
|
||||
epParts = []string{"tcp", epParts[0]}
|
||||
}
|
||||
if len(epParts) != 0 {
|
||||
switch epParts[0] {
|
||||
case "tcp":
|
||||
_, _, err := net.SplitHostPort(epParts[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s: %s", fieldName, err.Error())
|
||||
}
|
||||
case "unix":
|
||||
default:
|
||||
return fmt.Errorf("invalid %s schema: %s", fieldName, epParts[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a API) Validate() error {
|
||||
for _, v := range a.GRPC {
|
||||
err := a.validateAddress(v.Address, "endpoint")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, v := range a.HTTP {
|
||||
err := a.validateAddress(v.Address, "endpoint")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = a.validateAddress(v.Map, "map")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPI_Validate(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
tt := []struct {
|
||||
cfg API
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
cfg: API{
|
||||
GRPC: []*Endpoint{
|
||||
{
|
||||
Address: "udp://127.0.0.1",
|
||||
},
|
||||
},
|
||||
HTTP: []*Endpoint{
|
||||
{},
|
||||
},
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
cfg: API{
|
||||
GRPC: []*Endpoint{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:1234",
|
||||
},
|
||||
},
|
||||
HTTP: []*Endpoint{
|
||||
{
|
||||
Address: "udp://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
cfg: API{
|
||||
GRPC: []*Endpoint{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:1234",
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
cfg: API{
|
||||
GRPC: []*Endpoint{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:1234",
|
||||
},
|
||||
},
|
||||
HTTP: []*Endpoint{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:1235",
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
cfg: API{
|
||||
GRPC: []*Endpoint{
|
||||
{
|
||||
Address: "unix:///var/run/mqttd.sock",
|
||||
},
|
||||
},
|
||||
HTTP: []*Endpoint{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:1235",
|
||||
Map: "unix:///var/run/mqttd.sock",
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
for _, v := range tt {
|
||||
err := v.cfg.Validate()
|
||||
if v.valid {
|
||||
a.NoError(err)
|
||||
} else {
|
||||
a.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
pkgconfig "github.com/winc-link/hummingbird/internal/pkg/config"
|
||||
)
|
||||
|
||||
const EdgeMqttBroker = "mqtt-broker"
|
||||
|
||||
var (
|
||||
defaultPluginConfig = make(map[string]Configuration)
|
||||
configFileFullPath string
|
||||
config Config
|
||||
)
|
||||
|
||||
// Configuration is the interface that enable the implementation to parse config from the global config file.
|
||||
// Plugin admin and prometheus are two examples.
|
||||
type Configuration interface {
|
||||
// Validate validates the configuration.
|
||||
// If returns error, the broker will not start.
|
||||
Validate() error
|
||||
// Unmarshaler defined how to unmarshal YAML into the config structure.
|
||||
yaml.Unmarshaler
|
||||
}
|
||||
|
||||
// RegisterDefaultPluginConfig registers the default configuration for the given plugin.
|
||||
func RegisterDefaultPluginConfig(name string, config Configuration) {
|
||||
if _, ok := defaultPluginConfig[name]; ok {
|
||||
panic(fmt.Sprintf("duplicated default config for %s plugin", name))
|
||||
}
|
||||
defaultPluginConfig[name] = config
|
||||
|
||||
}
|
||||
|
||||
// DefaultConfig return the default configuration.
|
||||
// If config file is not provided, mqttd will start with DefaultConfig.
|
||||
func DefaultConfig() Config {
|
||||
c := Config{
|
||||
Listeners: DefaultListeners,
|
||||
MQTT: DefaultMQTTConfig,
|
||||
API: DefaultAPI,
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
FilePath: "/var/tedge/logs/mqtt-broker.log",
|
||||
},
|
||||
Plugins: make(pluginConfig),
|
||||
PluginOrder: []string{"aplugin"},
|
||||
Persistence: DefaultPersistenceConfig,
|
||||
TopicAliasManager: DefaultTopicAliasManager,
|
||||
}
|
||||
|
||||
for name, v := range defaultPluginConfig {
|
||||
c.Plugins[name] = v
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
var DefaultListeners = []*ListenerConfig{
|
||||
{
|
||||
Address: "0.0.0.0:58090",
|
||||
TLSOptions: nil,
|
||||
Websocket: nil,
|
||||
},
|
||||
{
|
||||
Address: "0.0.0.0:58091",
|
||||
Websocket: &WebsocketOptions{
|
||||
Path: "/",
|
||||
},
|
||||
}, {
|
||||
Address: "0.0.0.0:21883",
|
||||
TLSOptions: &TLSOptions{
|
||||
CACert: "/etc/tedge-mqtt-broker/ca.crt",
|
||||
Cert: "/etc/tedge-mqtt-broker/server.pem",
|
||||
Key: "/etc/tedge-mqtt-broker/server.key",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// LogConfig is use to configure the log behaviors.
|
||||
type LogConfig struct {
|
||||
// Level is the log level. Possible values: debug, info, warn, error
|
||||
Level string `yaml:"level"`
|
||||
FilePath string `yaml:"file_path"`
|
||||
// DumpPacket indicates whether to dump MQTT packet in debug level.
|
||||
DumpPacket bool `yaml:"dump_packet"`
|
||||
}
|
||||
|
||||
func (l LogConfig) Validate() error {
|
||||
level := strings.ToLower(l.Level)
|
||||
if level != "debug" && level != "info" && level != "warn" && level != "error" {
|
||||
return fmt.Errorf("invalid log level: %s", l.Level)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pluginConfig stores the plugin default configuration, key by the plugin name.
|
||||
// If the plugin has default configuration, it should call RegisterDefaultPluginConfig in it's init function to register.
|
||||
type pluginConfig map[string]Configuration
|
||||
|
||||
func (p pluginConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
for _, v := range p {
|
||||
err := unmarshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is the configration for mqttd.
|
||||
type Config struct {
|
||||
Listeners []*ListenerConfig `yaml:"listeners"`
|
||||
API API `yaml:"api"`
|
||||
MQTT MQTT `yaml:"mqttclient,omitempty"`
|
||||
GRPC GRPC `yaml:"gRPC"`
|
||||
Log LogConfig `yaml:"log"`
|
||||
PidFile string `yaml:"pid_file"`
|
||||
ConfigDir string `yaml:"config_dir"`
|
||||
Plugins pluginConfig `yaml:"plugins"`
|
||||
// PluginOrder is a slice that contains the name of the plugin which will be loaded.
|
||||
// Giving a correct order to the slice is significant,
|
||||
// because it represents the loading order which affect the behavior of the broker.
|
||||
PluginOrder []string `yaml:"plugin_order"`
|
||||
Persistence Persistence `yaml:"persistence"`
|
||||
TopicAliasManager TopicAliasManager `yaml:"topic_alias_manager"`
|
||||
Database pkgconfig.Database `yaml:"data_base"`
|
||||
}
|
||||
|
||||
type GRPC struct {
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
}
|
||||
|
||||
type TLSOptions struct {
|
||||
// CACert is the trust CA certificate file.
|
||||
CACert string `yaml:"cacert"`
|
||||
// Cert is the path to certificate file.
|
||||
Cert string `yaml:"cert"`
|
||||
// Key is the path to key file.
|
||||
Key string `yaml:"key"`
|
||||
// Verify indicates whether to verify client cert.
|
||||
Verify bool `yaml:"verify"`
|
||||
}
|
||||
|
||||
type ListenerConfig struct {
|
||||
Address string `yaml:"address"`
|
||||
*TLSOptions `yaml:"tls"`
|
||||
Websocket *WebsocketOptions `yaml:"websocket"`
|
||||
}
|
||||
|
||||
type WebsocketOptions struct {
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type config Config
|
||||
raw := config(DefaultConfig())
|
||||
if err := unmarshal(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
emptyMQTT := MQTT{}
|
||||
if raw.MQTT == emptyMQTT {
|
||||
raw.MQTT = DefaultMQTTConfig
|
||||
}
|
||||
if len(raw.Plugins) == 0 {
|
||||
raw.Plugins = make(pluginConfig)
|
||||
for name, v := range defaultPluginConfig {
|
||||
raw.Plugins[name] = v
|
||||
}
|
||||
} else {
|
||||
for name, v := range raw.Plugins {
|
||||
if v == nil {
|
||||
raw.Plugins[name] = defaultPluginConfig[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
*c = Config(raw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) Validate() (err error) {
|
||||
err = c.Log.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.API.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.MQTT.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Persistence.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, conf := range c.Plugins {
|
||||
err := conf.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseConfig(filePath string) (Config, error) {
|
||||
if filePath == "" {
|
||||
return DefaultConfig(), nil
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
fmt.Println("unspecificed configuration file, use default config")
|
||||
return DefaultConfig(), nil
|
||||
}
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config = DefaultConfig()
|
||||
err = yaml.Unmarshal(b, &config)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config.ConfigDir = path.Dir(filePath)
|
||||
err = config.Validate()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
configFileFullPath = filePath
|
||||
return config, err
|
||||
}
|
||||
|
||||
func UpdateLogLevel(level string) {
|
||||
config.Log.Level = level
|
||||
}
|
||||
|
||||
func GetLogLevel() string {
|
||||
return config.Log.Level
|
||||
}
|
||||
|
||||
func WriteToFile() error {
|
||||
return config.writeToFile()
|
||||
}
|
||||
|
||||
func (c Config) writeToFile() error {
|
||||
var (
|
||||
err error
|
||||
buff bytes.Buffer
|
||||
)
|
||||
e := yaml.NewEncoder(&buff)
|
||||
if err = e.Encode(c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(configFileFullPath+".tmp", buff.Bytes(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(configFileFullPath)
|
||||
return os.Rename(configFileFullPath+".tmp", configFileFullPath)
|
||||
}
|
||||
|
||||
func (c Config) GetLogger(config LogConfig) (*zap.AtomicLevel, *zap.Logger, error) {
|
||||
var logLevel zapcore.Level
|
||||
err := logLevel.UnmarshalText([]byte(config.Level))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var level = zap.NewAtomicLevelAt(logLevel)
|
||||
if config.FilePath == "" {
|
||||
cfg := zap.NewDevelopmentConfig()
|
||||
cfg.Level = level
|
||||
cfg.EncoderConfig.ConsoleSeparator = " "
|
||||
cfg.EncoderConfig.LineEnding = zapcore.DefaultLineEnding
|
||||
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
|
||||
cfg.EncoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
|
||||
cfg.EncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
|
||||
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
logger, err := cfg.Build(zap.AddStacktrace(zapcore.PanicLevel))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &level, logger.Named(EdgeMqttBroker), nil
|
||||
}
|
||||
|
||||
writeSyncer := getLogWriter(config)
|
||||
encoder := getEncoder()
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level.Level())
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.PanicLevel))
|
||||
|
||||
return &level, logger.Named(EdgeMqttBroker), nil
|
||||
}
|
||||
|
||||
func getEncoder() zapcore.Encoder {
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.LineEnding = zapcore.DefaultLineEnding
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
|
||||
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
|
||||
encoderConfig.ConsoleSeparator = " "
|
||||
return zapcore.NewConsoleEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
func getLogWriter(cfg LogConfig) zapcore.WriteSyncer {
|
||||
lumberJackLogger := &lumberjack.Logger{
|
||||
Filename: cfg.FilePath,
|
||||
MaxSize: 10,
|
||||
MaxBackups: 3,
|
||||
MaxAge: 7,
|
||||
Compress: false,
|
||||
}
|
||||
return zapcore.AddSync(lumberJackLogger)
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Code generated by config. DO NOT EDIT.
|
||||
// Source: config/config.go
|
||||
|
||||
// Package config is a generated GoMock package.
|
||||
package config
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockConfiguration is a mock of Configuration interface
|
||||
type MockConfiguration struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockConfigurationMockRecorder
|
||||
}
|
||||
|
||||
// MockConfigurationMockRecorder is the mock recorder for MockConfiguration
|
||||
type MockConfigurationMockRecorder struct {
|
||||
mock *MockConfiguration
|
||||
}
|
||||
|
||||
// NewMockConfiguration creates a new mock instance
|
||||
func NewMockConfiguration(ctrl *gomock.Controller) *MockConfiguration {
|
||||
mock := &MockConfiguration{ctrl: ctrl}
|
||||
mock.recorder = &MockConfigurationMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockConfiguration) EXPECT() *MockConfigurationMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Validate mocks base method
|
||||
func (m *MockConfiguration) Validate() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Validate")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Validate indicates an expected call of Validate
|
||||
func (mr *MockConfigurationMockRecorder) Validate() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockConfiguration)(nil).Validate))
|
||||
}
|
||||
|
||||
// UnmarshalYAML mocks base method
|
||||
func (m *MockConfiguration) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnmarshalYAML", unmarshal)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnmarshalYAML indicates an expected call of UnmarshalYAML
|
||||
func (mr *MockConfigurationMockRecorder) UnmarshalYAML(unmarshal interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalYAML", reflect.TypeOf((*MockConfiguration)(nil).UnmarshalYAML), unmarshal)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseConfig(t *testing.T) {
|
||||
var tt = []struct {
|
||||
caseName string
|
||||
fileName string
|
||||
hasErr bool
|
||||
expected Config
|
||||
}{
|
||||
{
|
||||
caseName: "defaultConfig",
|
||||
fileName: "",
|
||||
hasErr: false,
|
||||
expected: DefaultConfig(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range tt {
|
||||
t.Run(v.caseName, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c, err := ParseConfig(v.fileName)
|
||||
if v.hasErr {
|
||||
a.NotNil(err)
|
||||
} else {
|
||||
a.Nil(err)
|
||||
}
|
||||
a.Equal(v.expected, c)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
const (
|
||||
Overlap = "overlap"
|
||||
OnlyOnce = "onlyonce"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultMQTTConfig
|
||||
DefaultMQTTConfig = MQTT{
|
||||
SessionExpiry: 2 * time.Hour,
|
||||
SessionExpiryCheckInterval: 20 * time.Second,
|
||||
MessageExpiry: 2 * time.Hour,
|
||||
InflightExpiry: 30 * time.Second,
|
||||
MaxPacketSize: packets.MaximumSize,
|
||||
ReceiveMax: 100,
|
||||
MaxKeepAlive: 300,
|
||||
TopicAliasMax: 10,
|
||||
SubscriptionIDAvailable: true,
|
||||
SharedSubAvailable: true,
|
||||
WildcardAvailable: true,
|
||||
RetainAvailable: true,
|
||||
MaxQueuedMsg: 1000,
|
||||
MaxInflight: 100,
|
||||
MaximumQoS: 2,
|
||||
QueueQos0Msg: true,
|
||||
DeliveryMode: OnlyOnce,
|
||||
AllowZeroLenClientID: true,
|
||||
}
|
||||
)
|
||||
|
||||
type MQTT struct {
|
||||
// SessionExpiry is the maximum session expiry interval in seconds.
|
||||
SessionExpiry time.Duration `yaml:"session_expiry"`
|
||||
// SessionExpiryCheckInterval is the interval time for session expiry checker to check whether there
|
||||
// are expired sessions.
|
||||
SessionExpiryCheckInterval time.Duration `yaml:"session_expiry_check_interval"`
|
||||
// MessageExpiry is the maximum lifetime of the message in seconds.
|
||||
// If a message in the queue is not sent in MessageExpiry time, it will be removed, which means it will not be sent to the subscriber.
|
||||
MessageExpiry time.Duration `yaml:"message_expiry"`
|
||||
// InflightExpiry is the lifetime of the "inflight" message in seconds.
|
||||
// If a "inflight" message is not acknowledged by a client in InflightExpiry time, it will be removed when the message queue is full.
|
||||
InflightExpiry time.Duration `yaml:"inflight_expiry"`
|
||||
// MaxPacketSize is the maximum packet size that the server is willing to accept from the client
|
||||
MaxPacketSize uint32 `yaml:"max_packet_size"`
|
||||
// ReceiveMax limits the number of QoS 1 and QoS 2 publications that the server is willing to process concurrently for the client.
|
||||
ReceiveMax uint16 `yaml:"server_receive_maximum"`
|
||||
// MaxKeepAlive is the maximum keep alive time in seconds allows by the server.
|
||||
// If the client requests a keepalive time bigger than MaxKeepalive,
|
||||
// the server will use MaxKeepAlive as the keepalive time.
|
||||
// In this case, if the client version is v5, the server will set MaxKeepalive into CONNACK to inform the client.
|
||||
// But if the client version is 3.x, the server has no way to inform the client that the keepalive time has been changed.
|
||||
MaxKeepAlive uint16 `yaml:"max_keepalive"`
|
||||
// TopicAliasMax indicates the highest value that the server will accept as a Topic Alias sent by the client.
|
||||
// No-op if the client version is MQTTv3.x
|
||||
TopicAliasMax uint16 `yaml:"topic_alias_maximum"`
|
||||
// SubscriptionIDAvailable indicates whether the server supports Subscription Identifiers.
|
||||
// No-op if the client version is MQTTv3.x .
|
||||
SubscriptionIDAvailable bool `yaml:"subscription_identifier_available"`
|
||||
// SharedSubAvailable indicates whether the server supports Shared Subscriptions.
|
||||
SharedSubAvailable bool `yaml:"shared_subscription_available"`
|
||||
// WildcardSubAvailable indicates whether the server supports Wildcard Subscriptions.
|
||||
WildcardAvailable bool `yaml:"wildcard_subscription_available"`
|
||||
// RetainAvailable indicates whether the server supports retained messages.
|
||||
RetainAvailable bool `yaml:"retain_available"`
|
||||
// MaxQueuedMsg is the maximum queue length of the outgoing messages.
|
||||
// If the queue is full, some message will be dropped.
|
||||
// The message dropping strategy is described in the document of the persistence/queue.Store interface.
|
||||
MaxQueuedMsg int `yaml:"max_queued_messages"`
|
||||
// MaxInflight limits inflight message length of the outgoing messages.
|
||||
// Inflight message is also stored in the message queue, so it must be less than or equal to MaxQueuedMsg.
|
||||
// Inflight message is the QoS 1 or QoS 2 message that has been sent out to a client but not been acknowledged yet.
|
||||
MaxInflight uint16 `yaml:"max_inflight"`
|
||||
// MaximumQoS is the highest QOS level permitted for a Publish.
|
||||
MaximumQoS uint8 `yaml:"maximum_qos"`
|
||||
// QueueQos0Msg indicates whether to store QoS 0 message for a offline session.
|
||||
QueueQos0Msg bool `yaml:"queue_qos0_messages"`
|
||||
// DeliveryMode is the delivery mode. The possible value can be "overlap" or "onlyonce".
|
||||
// It is possible for a client’s subscriptions to overlap so that a published message might match multiple filters.
|
||||
// When set to "overlap" , the server will deliver one message for each matching subscription and respecting the subscription’s QoS in each case.
|
||||
// When set to "onlyonce",the server will deliver the message to the client respecting the maximum QoS of all the matching subscriptions.
|
||||
DeliveryMode string `yaml:"delivery_mode"`
|
||||
// AllowZeroLenClientID indicates whether to allow a client to connect with empty client id.
|
||||
AllowZeroLenClientID bool `yaml:"allow_zero_length_clientid"`
|
||||
}
|
||||
|
||||
func (c MQTT) Validate() error {
|
||||
if c.MaximumQoS > packets.Qos2 {
|
||||
return fmt.Errorf("invalid maximum_qos: %d", c.MaximumQoS)
|
||||
}
|
||||
if c.MaxQueuedMsg <= 0 {
|
||||
return fmt.Errorf("invalid max_queued_messages : %d", c.MaxQueuedMsg)
|
||||
}
|
||||
if c.ReceiveMax == 0 {
|
||||
return fmt.Errorf("server_receive_maximum cannot be 0")
|
||||
}
|
||||
if c.MaxPacketSize == 0 {
|
||||
return fmt.Errorf("max_packet_size cannot be 0")
|
||||
}
|
||||
if c.MaxInflight == 0 {
|
||||
return fmt.Errorf("max_inflight cannot be 0")
|
||||
}
|
||||
if c.DeliveryMode != Overlap && c.DeliveryMode != OnlyOnce {
|
||||
return fmt.Errorf("invalid delivery_mode: %s", c.DeliveryMode)
|
||||
}
|
||||
|
||||
if c.MaxQueuedMsg < int(c.MaxInflight) {
|
||||
return fmt.Errorf("max_queued_message cannot be less than max_inflight")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PersistenceType = string
|
||||
|
||||
const (
|
||||
PersistenceTypeMemory PersistenceType = "memory"
|
||||
PersistenceTypeRedis PersistenceType = "redis"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultMaxActive = uint(0)
|
||||
defaultMaxIdle = uint(1000)
|
||||
// DefaultPersistenceConfig is the default value of Persistence
|
||||
DefaultPersistenceConfig = Persistence{
|
||||
Type: PersistenceTypeMemory,
|
||||
Redis: RedisPersistence{
|
||||
Addr: "127.0.0.1:6379",
|
||||
Password: "",
|
||||
Database: 0,
|
||||
MaxIdle: &defaultMaxIdle,
|
||||
MaxActive: &defaultMaxActive,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Persistence is the config of backend persistence.
|
||||
type Persistence struct {
|
||||
// Type is the persistence type.
|
||||
// If empty, use "memory" as default.
|
||||
Type PersistenceType `yaml:"type"`
|
||||
// Redis is the redis configuration and must be set when Type == "redis".
|
||||
Redis RedisPersistence `yaml:"redis"`
|
||||
}
|
||||
|
||||
// RedisPersistence is the configuration of redis persistence.
|
||||
type RedisPersistence struct {
|
||||
// Addr is the redis server address.
|
||||
// If empty, use "127.0.0.1:6379" as default.
|
||||
Addr string `yaml:"addr"`
|
||||
// Password is the redis password.
|
||||
Password string `yaml:"password"`
|
||||
// Database is the number of the redis database to be connected.
|
||||
Database uint `yaml:"database"`
|
||||
// MaxIdle is the maximum number of idle connections in the pool.
|
||||
// If nil, use 1000 as default.
|
||||
// This value will pass to redis.Pool.MaxIde.
|
||||
MaxIdle *uint `yaml:"max_idle"`
|
||||
// MaxActive is the maximum number of connections allocated by the pool at a given time.
|
||||
// If nil, use 0 as default.
|
||||
// If zero, there is no limit on the number of connections in the pool.
|
||||
// This value will pass to redis.Pool.MaxActive.
|
||||
MaxActive *uint `yaml:"max_active"`
|
||||
// Close connections after remaining idle for this duration. If the value
|
||||
// is zero, then idle connections are not closed. Applications should set
|
||||
// the timeout to a value less than the server's timeout.
|
||||
// Ff zero, use 240 * time.Second as default.
|
||||
// This value will pass to redis.Pool.IdleTimeout.
|
||||
IdleTimeout time.Duration `yaml:"idle_timeout"`
|
||||
}
|
||||
|
||||
func (p *Persistence) Validate() error {
|
||||
if p.Type != PersistenceTypeMemory && p.Type != PersistenceTypeRedis {
|
||||
return errors.New("invalid persistence type")
|
||||
}
|
||||
_, _, err := net.SplitHostPort(p.Redis.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Redis.Database < 0 {
|
||||
return errors.New("invalid redis database number")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
listeners:
|
||||
- address: ":58090"
|
||||
websocket:
|
||||
path: "/"
|
||||
- address: ":1234"
|
||||
|
||||
mqtt:
|
||||
session_expiry: 1m
|
||||
message_expiry: 1m
|
||||
max_packet_size: 200
|
||||
server_receive_maximum: 65535
|
||||
max_keepalive: 0 # unlimited
|
||||
topic_alias_maximum: 0 # 0 means not Supported
|
||||
subscription_identifier_available: true
|
||||
wildcard_subscription_available: true
|
||||
shared_subscription_available: true
|
||||
maximum_qos: 2
|
||||
retain_available: true
|
||||
max_queued_messages: 1000
|
||||
max_inflight: 32
|
||||
max_awaiting_rel: 100
|
||||
queue_qos0_messages: true
|
||||
delivery_mode: overlap # overlap or onlyonce
|
||||
allow_zero_length_clientid: true
|
||||
|
||||
log:
|
||||
level: debug # debug | info | warning | error
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
listeners:
|
||||
mqtt:
|
||||
log:
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package config
|
||||
|
||||
type TopicAliasType = string
|
||||
|
||||
const (
|
||||
TopicAliasMgrTypeFIFO TopicAliasType = "fifo"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTopicAliasManager is the default value of TopicAliasManager
|
||||
DefaultTopicAliasManager = TopicAliasManager{
|
||||
Type: TopicAliasMgrTypeFIFO,
|
||||
}
|
||||
)
|
||||
|
||||
// TopicAliasManager is the config of the topic alias manager.
|
||||
type TopicAliasManager struct {
|
||||
Type TopicAliasType
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 Dell Inc.
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*******************************************************************************/
|
||||
package mqttbroker
|
||||
|
||||
import (
|
||||
"github.com/winc-link/hummingbird/internal/dtos"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/infrastructure/sqlite"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/interfaces"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
DbClient interfaces.DBClient
|
||||
)
|
||||
|
||||
func GetDbClient() interfaces.DBClient {
|
||||
return DbClient
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
conf config.Config
|
||||
}
|
||||
|
||||
// NewDatabase is a factory method that returns an initialized Database receiver struct.
|
||||
func NewDatabase(conf config.Config) Database {
|
||||
return Database{
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
|
||||
// init the dbClient interfaces
|
||||
func (d Database) InitDBClient(
|
||||
lc *zap.Logger) error {
|
||||
dbClient, err := sqlite.NewClient(dtos.Configuration{
|
||||
Cluster: d.conf.Database.Cluster,
|
||||
Username: d.conf.Database.Username,
|
||||
Password: d.conf.Database.Password,
|
||||
DataSource: d.conf.Database.DataSource,
|
||||
DatabaseName: d.conf.Database.Name,
|
||||
}, lc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DbClient = dbClient
|
||||
return nil
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 Dell Inc.
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*******************************************************************************/
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"github.com/winc-link/hummingbird/internal/dtos"
|
||||
"github.com/winc-link/hummingbird/internal/models"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/errort"
|
||||
clientSQLite "github.com/winc-link/hummingbird/internal/tools/sqldb/sqlite"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Pool *gorm.DB
|
||||
client clientSQLite.ClientSQLite
|
||||
loggingClient *zap.Logger
|
||||
}
|
||||
|
||||
func NewClient(config dtos.Configuration, lc *zap.Logger) (c *Client, errEdgeX error) {
|
||||
client, err := clientSQLite.NewGormClient(config, nil)
|
||||
if err != nil {
|
||||
errEdgeX = errort.NewCommonEdgeX(errort.DefaultSystemError, "database failed to init", err)
|
||||
return
|
||||
}
|
||||
client.Pool = client.Pool.Debug()
|
||||
// 自动建表
|
||||
if err = client.InitTable(
|
||||
&models.MqttAuth{},
|
||||
); err != nil {
|
||||
errEdgeX = errort.NewCommonEdgeX(errort.DefaultSystemError, "database failed to init", err)
|
||||
return
|
||||
}
|
||||
c = &Client{
|
||||
client: client,
|
||||
loggingClient: lc,
|
||||
Pool: client.Pool,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) CloseSession() {
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) GetMqttAutInfo(clientId string) (models.MqttAuth, error) {
|
||||
return getMqttAutInfo(client, clientId)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 Dell Inc.
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*******************************************************************************/
|
||||
package sqlite
|
||||
|
||||
import "github.com/winc-link/hummingbird/internal/models"
|
||||
|
||||
func getMqttAutInfo(c *Client, clientId string) (models.MqttAuth, error) {
|
||||
var mqttAuth models.MqttAuth
|
||||
|
||||
if err := c.Pool.Where("client_id = ?", clientId).First(&mqttAuth).Error; err != nil {
|
||||
return models.MqttAuth{}, err
|
||||
}
|
||||
return mqttAuth, nil
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 Dell Inc.
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*******************************************************************************/
|
||||
package interfaces
|
||||
|
||||
import "github.com/winc-link/hummingbird/internal/models"
|
||||
|
||||
type DBClient interface {
|
||||
CloseSession()
|
||||
|
||||
GetMqttAutInfo(clientId string) (models.MqttAuth, error)
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
package mqttbroker
|
||||
|
||||
import (
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Dup bool
|
||||
QoS uint8
|
||||
Retained bool
|
||||
Topic string
|
||||
Payload []byte
|
||||
PacketID packets.PacketID
|
||||
// The following fields are introduced in v5 specification.
|
||||
// Excepting MessageExpiry, these fields will not take effect when it represents a v3.x publish packet.
|
||||
ContentType string
|
||||
CorrelationData []byte
|
||||
MessageExpiry uint32
|
||||
PayloadFormat packets.PayloadFormat
|
||||
ResponseTopic string
|
||||
SubscriptionIdentifier []uint32
|
||||
UserProperties []packets.UserProperty
|
||||
}
|
||||
|
||||
// Copy deep copies the Message and return the new one
|
||||
func (m *Message) Copy() *Message {
|
||||
newMsg := &Message{
|
||||
Dup: m.Dup,
|
||||
QoS: m.QoS,
|
||||
Retained: m.Retained,
|
||||
Topic: m.Topic,
|
||||
PacketID: m.PacketID,
|
||||
ContentType: m.ContentType,
|
||||
MessageExpiry: m.MessageExpiry,
|
||||
PayloadFormat: m.PayloadFormat,
|
||||
ResponseTopic: m.ResponseTopic,
|
||||
}
|
||||
newMsg.Payload = make([]byte, len(m.Payload))
|
||||
copy(newMsg.Payload, m.Payload)
|
||||
|
||||
if len(m.CorrelationData) != 0 {
|
||||
newMsg.CorrelationData = make([]byte, len(m.CorrelationData))
|
||||
copy(newMsg.CorrelationData, m.CorrelationData)
|
||||
}
|
||||
|
||||
if len(m.SubscriptionIdentifier) != 0 {
|
||||
newMsg.SubscriptionIdentifier = make([]uint32, len(m.SubscriptionIdentifier))
|
||||
copy(newMsg.SubscriptionIdentifier, m.SubscriptionIdentifier)
|
||||
}
|
||||
if len(m.UserProperties) != 0 {
|
||||
newMsg.UserProperties = make([]packets.UserProperty, len(m.UserProperties))
|
||||
for k := range newMsg.UserProperties {
|
||||
newMsg.UserProperties[k].K = make([]byte, len(m.UserProperties[k].K))
|
||||
copy(newMsg.UserProperties[k].K, m.UserProperties[k].K)
|
||||
|
||||
newMsg.UserProperties[k].V = make([]byte, len(m.UserProperties[k].V))
|
||||
copy(newMsg.UserProperties[k].V, m.UserProperties[k].V)
|
||||
}
|
||||
}
|
||||
return newMsg
|
||||
|
||||
}
|
||||
|
||||
func getVariablelenght(l int) int {
|
||||
if l <= 127 {
|
||||
return 1
|
||||
} else if l <= 16383 {
|
||||
return 2
|
||||
} else if l <= 2097151 {
|
||||
return 3
|
||||
} else if l <= 268435455 {
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// TotalBytes return the publish packets total bytes.
|
||||
func (m *Message) TotalBytes(version packets.Version) uint32 {
|
||||
remainLenght := len(m.Payload) + 2 + len(m.Topic)
|
||||
if m.QoS > packets.Qos0 {
|
||||
remainLenght += 2
|
||||
}
|
||||
if version == packets.Version5 {
|
||||
propertyLenght := 0
|
||||
if m.PayloadFormat == packets.PayloadFormatString {
|
||||
propertyLenght += 2
|
||||
}
|
||||
if l := len(m.ContentType); l != 0 {
|
||||
propertyLenght += 3 + l
|
||||
}
|
||||
if l := len(m.CorrelationData); l != 0 {
|
||||
propertyLenght += 3 + l
|
||||
}
|
||||
|
||||
for _, v := range m.SubscriptionIdentifier {
|
||||
propertyLenght++
|
||||
propertyLenght += getVariablelenght(int(v))
|
||||
}
|
||||
|
||||
if m.MessageExpiry != 0 {
|
||||
propertyLenght += 5
|
||||
}
|
||||
if l := len(m.ResponseTopic); l != 0 {
|
||||
propertyLenght += 3 + l
|
||||
}
|
||||
for _, v := range m.UserProperties {
|
||||
propertyLenght += 5 + len(v.K) + len(v.V)
|
||||
}
|
||||
remainLenght += propertyLenght + getVariablelenght(propertyLenght)
|
||||
}
|
||||
if remainLenght <= 127 {
|
||||
return 2 + uint32(remainLenght)
|
||||
} else if remainLenght <= 16383 {
|
||||
return 3 + uint32(remainLenght)
|
||||
} else if remainLenght <= 2097151 {
|
||||
return 4 + uint32(remainLenght)
|
||||
}
|
||||
return 5 + uint32(remainLenght)
|
||||
}
|
||||
|
||||
// MessageFromPublish create the Message instance from publish packets
|
||||
func MessageFromPublish(p *packets.Publish) *Message {
|
||||
m := &Message{
|
||||
Dup: p.Dup,
|
||||
QoS: p.Qos,
|
||||
Retained: p.Retain,
|
||||
Topic: string(p.TopicName),
|
||||
Payload: p.Payload,
|
||||
}
|
||||
if p.Version == packets.Version5 {
|
||||
if p.Properties.PayloadFormat != nil {
|
||||
m.PayloadFormat = *p.Properties.PayloadFormat
|
||||
}
|
||||
if l := len(p.Properties.ContentType); l != 0 {
|
||||
m.ContentType = string(p.Properties.ContentType)
|
||||
}
|
||||
if l := len(p.Properties.CorrelationData); l != 0 {
|
||||
m.CorrelationData = p.Properties.CorrelationData
|
||||
}
|
||||
if p.Properties.MessageExpiry != nil {
|
||||
m.MessageExpiry = *p.Properties.MessageExpiry
|
||||
}
|
||||
if l := len(p.Properties.ResponseTopic); l != 0 {
|
||||
m.ResponseTopic = string(p.Properties.ResponseTopic)
|
||||
}
|
||||
m.UserProperties = p.Properties.User
|
||||
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// MessageToPublish create the publish packet instance from *Message
|
||||
func MessageToPublish(msg *Message, version packets.Version) *packets.Publish {
|
||||
pub := &packets.Publish{
|
||||
Dup: msg.Dup,
|
||||
Qos: msg.QoS,
|
||||
PacketID: msg.PacketID,
|
||||
Retain: msg.Retained,
|
||||
TopicName: []byte(msg.Topic),
|
||||
Payload: msg.Payload,
|
||||
Version: version,
|
||||
}
|
||||
if version == packets.Version5 {
|
||||
var msgExpiry *uint32
|
||||
if e := msg.MessageExpiry; e != 0 {
|
||||
msgExpiry = &e
|
||||
}
|
||||
var contentType []byte
|
||||
if msg.ContentType != "" {
|
||||
contentType = []byte(msg.ContentType)
|
||||
}
|
||||
var responseTopic []byte
|
||||
if msg.ResponseTopic != "" {
|
||||
responseTopic = []byte(msg.ResponseTopic)
|
||||
}
|
||||
var payloadFormat *byte
|
||||
if e := msg.PayloadFormat; e == packets.PayloadFormatString {
|
||||
payloadFormat = &e
|
||||
}
|
||||
pub.Properties = &packets.Properties{
|
||||
CorrelationData: msg.CorrelationData,
|
||||
ContentType: contentType,
|
||||
MessageExpiry: msgExpiry,
|
||||
ResponseTopic: responseTopic,
|
||||
PayloadFormat: payloadFormat,
|
||||
User: msg.UserProperties,
|
||||
SubscriptionIdentifier: msg.SubscriptionIdentifier,
|
||||
}
|
||||
}
|
||||
return pub
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
mockgen -source=config/config.go -destination=./config/config_mock.go -package=config -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/config
|
||||
mockgen -source=persistence/queue/elem.go -destination=./persistence/queue/elem_mock.go -package=queue -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/queue
|
||||
mockgen -source=persistence/queue/queue.go -destination=./persistence/queue/queue_mock.go -package=queue -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/queue
|
||||
mockgen -source=persistence/session/session.go -destination=./persistence/session/session_mock.go -package=session -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/session
|
||||
mockgen -source=persistence/subscription/subscription.go -destination=./persistence/subscription/subscription_mock.go -package=subscription -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/subscription
|
||||
mockgen -source=persistence/unack/unack.go -destination=./persistence/unack/unack_mock.go -package=unack -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/unack
|
||||
mockgen -source=pkg/packets/packets.go -destination=./pkg/packets/packets_mock.go -package=packets -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/packets
|
||||
mockgen -source=plugin/auth/account_grpc.pb.go -destination=./plugin/auth/account_grpc.pb_mock.go -package=auth -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/auth
|
||||
mockgen -source=plugin/federation/federation.pb.go -destination=./plugin/federation/federation.pb_mock.go -package=federation -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/federation
|
||||
mockgen -source=plugin/federation/peer.go -destination=./plugin/federation/peer_mock.go -package=federation -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/federation
|
||||
mockgen -source=plugin/federation/membership.go -destination=./plugin/federation/membership_mock.go -package=federation -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/federation
|
||||
mockgen -source=retained/interface.go -destination=./retained/interface_mock.go -package=retained -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/retained
|
||||
mockgen -source=server/client.go -destination=./server/client_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
mockgen -source=server/persistence.go -destination=./server/persistence_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
mockgen -source=server/plugin.go -destination=./server/plugin_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
mockgen -source=server/server.go -destination=./server/server_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
mockgen -source=server/service.go -destination=./server/service_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
mockgen -source=server/stats.go -destination=./server/stats_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
mockgen -source=server/topic_alias.go -destination=./server/topic_alias_mock.go -package=server -self_package=gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/server
|
||||
|
||||
# reflection mode.
|
||||
# gRPC streaming mock issue: https://github.com/golang/mock/pull/163
|
||||
mockgen -package=federation -destination=/usr/local/gopath/src/gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/plugin/federation/federation_grpc.pb_mock.go gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/plugin/federation FederationClient,Federation_EventStreamClient
|
|
@ -1,73 +0,0 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func WriteUint16(w *bytes.Buffer, i uint16) {
|
||||
w.WriteByte(byte(i >> 8))
|
||||
w.WriteByte(byte(i))
|
||||
}
|
||||
|
||||
func WriteBool(w *bytes.Buffer, b bool) {
|
||||
if b {
|
||||
w.WriteByte(1)
|
||||
} else {
|
||||
w.WriteByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadBool(r *bytes.Buffer) (bool, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if b == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func WriteString(w *bytes.Buffer, s []byte) {
|
||||
WriteUint16(w, uint16(len(s)))
|
||||
w.Write(s)
|
||||
}
|
||||
func ReadString(r *bytes.Buffer) (b []byte, err error) {
|
||||
l := make([]byte, 2)
|
||||
_, err = io.ReadFull(r, l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(l))
|
||||
paylaod := make([]byte, length)
|
||||
|
||||
_, err = io.ReadFull(r, paylaod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return paylaod, nil
|
||||
}
|
||||
|
||||
func WriteUint32(w *bytes.Buffer, i uint32) {
|
||||
w.WriteByte(byte(i >> 24))
|
||||
w.WriteByte(byte(i >> 16))
|
||||
w.WriteByte(byte(i >> 8))
|
||||
w.WriteByte(byte(i))
|
||||
}
|
||||
|
||||
func ReadUint16(r *bytes.Buffer) (uint16, error) {
|
||||
if r.Len() < 2 {
|
||||
return 0, errors.New("invalid length")
|
||||
}
|
||||
return binary.BigEndian.Uint16(r.Next(2)), nil
|
||||
}
|
||||
|
||||
func ReadUint32(r *bytes.Buffer) (uint32, error) {
|
||||
if r.Len() < 4 {
|
||||
return 0, errors.New("invalid length")
|
||||
}
|
||||
return binary.BigEndian.Uint32(r.Next(4)), nil
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
)
|
||||
|
||||
// EncodeMessage encodes message into bytes and write it to the buffer
|
||||
func EncodeMessage(msg *gmqtt.Message, b *bytes.Buffer) {
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
WriteBool(b, msg.Dup)
|
||||
b.WriteByte(msg.QoS)
|
||||
WriteBool(b, msg.Retained)
|
||||
WriteString(b, []byte(msg.Topic))
|
||||
WriteString(b, []byte(msg.Payload))
|
||||
WriteUint16(b, msg.PacketID)
|
||||
|
||||
if len(msg.ContentType) != 0 {
|
||||
b.WriteByte(packets.PropContentType)
|
||||
WriteString(b, []byte(msg.ContentType))
|
||||
}
|
||||
if len(msg.CorrelationData) != 0 {
|
||||
b.WriteByte(packets.PropCorrelationData)
|
||||
WriteString(b, []byte(msg.CorrelationData))
|
||||
}
|
||||
if msg.MessageExpiry != 0 {
|
||||
b.WriteByte(packets.PropMessageExpiry)
|
||||
WriteUint32(b, msg.MessageExpiry)
|
||||
}
|
||||
b.WriteByte(packets.PropPayloadFormat)
|
||||
b.WriteByte(msg.PayloadFormat)
|
||||
|
||||
if len(msg.ResponseTopic) != 0 {
|
||||
b.WriteByte(packets.PropResponseTopic)
|
||||
WriteString(b, []byte(msg.ResponseTopic))
|
||||
}
|
||||
for _, v := range msg.SubscriptionIdentifier {
|
||||
b.WriteByte(packets.PropSubscriptionIdentifier)
|
||||
l, _ := packets.DecodeRemainLength(int(v))
|
||||
b.Write(l)
|
||||
}
|
||||
for _, v := range msg.UserProperties {
|
||||
b.WriteByte(packets.PropUser)
|
||||
WriteString(b, v.K)
|
||||
WriteString(b, v.V)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeMessage decodes message from buffer.
|
||||
func DecodeMessage(b *bytes.Buffer) (msg *gmqtt.Message, err error) {
|
||||
msg = &gmqtt.Message{}
|
||||
msg.Dup, err = ReadBool(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg.QoS, err = b.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg.Retained, err = ReadBool(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
topic, err := ReadString(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg.Topic = string(topic)
|
||||
msg.Payload, err = ReadString(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg.PacketID, err = ReadUint16(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
pt, err := b.ReadByte()
|
||||
if err == io.EOF {
|
||||
return msg, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch pt {
|
||||
case packets.PropContentType:
|
||||
v, err := ReadString(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.ContentType = string(v)
|
||||
case packets.PropCorrelationData:
|
||||
msg.CorrelationData, err = ReadString(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case packets.PropMessageExpiry:
|
||||
msg.MessageExpiry, err = ReadUint32(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case packets.PropPayloadFormat:
|
||||
msg.PayloadFormat, err = b.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case packets.PropResponseTopic:
|
||||
v, err := ReadString(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.ResponseTopic = string(v)
|
||||
case packets.PropSubscriptionIdentifier:
|
||||
si, err := packets.EncodeRemainLength(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.SubscriptionIdentifier = append(msg.SubscriptionIdentifier, uint32(si))
|
||||
case packets.PropUser:
|
||||
k, err := ReadString(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := ReadString(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.UserProperties = append(msg.UserProperties, packets.UserProperty{K: k, V: v})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeMessageFromBytes decodes message from bytes.
|
||||
func DecodeMessageFromBytes(b []byte) (msg *gmqtt.Message, err error) {
|
||||
if len(b) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return DecodeMessage(bytes.NewBuffer(b))
|
||||
}
|
||||
|
||||
func EncodeSession(sess *gmqtt.Session, b *bytes.Buffer) {
|
||||
WriteString(b, []byte(sess.ClientID))
|
||||
if sess.Will != nil {
|
||||
b.WriteByte(1)
|
||||
EncodeMessage(sess.Will, b)
|
||||
WriteUint32(b, sess.WillDelayInterval)
|
||||
} else {
|
||||
b.WriteByte(0)
|
||||
}
|
||||
time := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(time, uint64(sess.ConnectedAt.Unix()))
|
||||
WriteUint32(b, sess.ExpiryInterval)
|
||||
}
|
||||
|
||||
func DecodeSession(b *bytes.Buffer) (sess *gmqtt.Session, err error) {
|
||||
sess = &gmqtt.Session{}
|
||||
cid, err := ReadString(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sess.ClientID = string(cid)
|
||||
willPresent, err := b.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if willPresent == 1 {
|
||||
sess.Will, err = DecodeMessage(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sess.WillDelayInterval, err = ReadUint32(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
t := binary.BigEndian.Uint64(b.Next(8))
|
||||
sess.ConnectedAt = time.Unix(int64(t), 0)
|
||||
sess.ExpiryInterval, err = ReadUint32(b)
|
||||
return
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue"
|
||||
mem_queue "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue/mem"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session"
|
||||
mem_session "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session/mem"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
mem_sub "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription/mem"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack"
|
||||
mem_unack "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack/mem"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
func init() {
|
||||
server.RegisterPersistenceFactory("memory", NewMemory)
|
||||
}
|
||||
|
||||
func NewMemory(config config.Config) (server.Persistence, error) {
|
||||
return &memory{}, nil
|
||||
}
|
||||
|
||||
type memory struct {
|
||||
}
|
||||
|
||||
func (m *memory) NewUnackStore(config config.Config, clientID string) (unack.Store, error) {
|
||||
return mem_unack.New(mem_unack.Options{
|
||||
ClientID: clientID,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (m *memory) NewSessionStore(config config.Config) (session.Store, error) {
|
||||
return mem_session.New(), nil
|
||||
}
|
||||
|
||||
func (m *memory) Open() error {
|
||||
return nil
|
||||
}
|
||||
func (m *memory) NewQueueStore(config config.Config, defaultNotifier queue.Notifier, clientID string) (queue.Store, error) {
|
||||
return mem_queue.New(mem_queue.Options{
|
||||
MaxQueuedMsg: config.MQTT.MaxQueuedMsg,
|
||||
InflightExpiry: config.MQTT.InflightExpiry,
|
||||
ClientID: clientID,
|
||||
DefaultNotifier: defaultNotifier,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *memory) NewSubscriptionStore(config config.Config) (subscription.Store, error) {
|
||||
return mem_sub.NewStore(), nil
|
||||
}
|
||||
|
||||
func (m *memory) Close() error {
|
||||
return nil
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/encoding"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
type MessageWithID interface {
|
||||
ID() packets.PacketID
|
||||
SetID(id packets.PacketID)
|
||||
}
|
||||
|
||||
type Publish struct {
|
||||
*mqttbroker.Message
|
||||
}
|
||||
|
||||
func (p *Publish) ID() packets.PacketID {
|
||||
return p.PacketID
|
||||
}
|
||||
func (p *Publish) SetID(id packets.PacketID) {
|
||||
p.PacketID = id
|
||||
}
|
||||
|
||||
type Pubrel struct {
|
||||
PacketID packets.PacketID
|
||||
}
|
||||
|
||||
func (p *Pubrel) ID() packets.PacketID {
|
||||
return p.PacketID
|
||||
}
|
||||
func (p *Pubrel) SetID(id packets.PacketID) {
|
||||
p.PacketID = id
|
||||
}
|
||||
|
||||
// Elem represents the element store in the queue.
|
||||
type Elem struct {
|
||||
// At represents the entry time.
|
||||
At time.Time
|
||||
// Expiry represents the expiry time.
|
||||
// Empty means never expire.
|
||||
Expiry time.Time
|
||||
MessageWithID
|
||||
}
|
||||
|
||||
// Encode encodes the publish structure into bytes and write it to the buffer
|
||||
func (p *Publish) Encode(b *bytes.Buffer) {
|
||||
encoding.EncodeMessage(p.Message, b)
|
||||
}
|
||||
|
||||
func (p *Publish) Decode(b *bytes.Buffer) (err error) {
|
||||
msg, err := encoding.DecodeMessage(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Message = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encode the pubrel structure into bytes.
|
||||
func (p *Pubrel) Encode(b *bytes.Buffer) {
|
||||
encoding.WriteUint16(b, p.PacketID)
|
||||
}
|
||||
|
||||
func (p *Pubrel) Decode(b *bytes.Buffer) (err error) {
|
||||
p.PacketID, err = encoding.ReadUint16(b)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode encode the elem structure into bytes.
|
||||
// Format: 8 byte timestamp | 1 byte identifier| data
|
||||
func (e *Elem) Encode() []byte {
|
||||
b := bytes.NewBuffer(make([]byte, 0, 100))
|
||||
rs := make([]byte, 19)
|
||||
binary.BigEndian.PutUint64(rs[0:9], uint64(e.At.Unix()))
|
||||
binary.BigEndian.PutUint64(rs[9:18], uint64(e.Expiry.Unix()))
|
||||
switch m := e.MessageWithID.(type) {
|
||||
case *Publish:
|
||||
rs[18] = 0
|
||||
b.Write(rs)
|
||||
m.Encode(b)
|
||||
case *Pubrel:
|
||||
rs[18] = 1
|
||||
b.Write(rs)
|
||||
m.Encode(b)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func (e *Elem) Decode(b []byte) (err error) {
|
||||
if len(b) < 19 {
|
||||
return errors.New("invalid input length")
|
||||
}
|
||||
e.At = time.Unix(int64(binary.BigEndian.Uint64(b[0:9])), 0)
|
||||
e.Expiry = time.Unix(int64(binary.BigEndian.Uint64(b[9:19])), 0)
|
||||
switch b[18] {
|
||||
case 0: // publish
|
||||
p := &Publish{}
|
||||
buf := bytes.NewBuffer(b[19:])
|
||||
err = p.Decode(buf)
|
||||
e.MessageWithID = p
|
||||
case 1: // pubrel
|
||||
p := &Pubrel{}
|
||||
buf := bytes.NewBuffer(b[19:])
|
||||
err = p.Decode(buf)
|
||||
e.MessageWithID = p
|
||||
default:
|
||||
return errors.New("invalid identifier")
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: persistence/queue/elem.go
|
||||
|
||||
// Package queue is a generated GoMock package.
|
||||
package queue
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
packets "github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
// MockMessageWithID is a mock of MessageWithID interface
|
||||
type MockMessageWithID struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockMessageWithIDMockRecorder
|
||||
}
|
||||
|
||||
// MockMessageWithIDMockRecorder is the mock recorder for MockMessageWithID
|
||||
type MockMessageWithIDMockRecorder struct {
|
||||
mock *MockMessageWithID
|
||||
}
|
||||
|
||||
// NewMockMessageWithID creates a new mock instance
|
||||
func NewMockMessageWithID(ctrl *gomock.Controller) *MockMessageWithID {
|
||||
mock := &MockMessageWithID{ctrl: ctrl}
|
||||
mock.recorder = &MockMessageWithIDMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockMessageWithID) EXPECT() *MockMessageWithIDMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// ID mocks base method
|
||||
func (m *MockMessageWithID) ID() packets.PacketID {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ID")
|
||||
ret0, _ := ret[0].(packets.PacketID)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ID indicates an expected call of ID
|
||||
func (mr *MockMessageWithIDMockRecorder) ID() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockMessageWithID)(nil).ID))
|
||||
}
|
||||
|
||||
// SetID mocks base method
|
||||
func (m *MockMessageWithID) SetID(id packets.PacketID) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetID", id)
|
||||
}
|
||||
|
||||
// SetID indicates an expected call of SetID
|
||||
func (mr *MockMessageWithIDMockRecorder) SetID(id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetID", reflect.TypeOf((*MockMessageWithID)(nil).SetID), id)
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
func assertElemEqual(a *assert.Assertions, expected, actual *Elem) {
|
||||
expected.At = time.Unix(expected.At.Unix(), 0)
|
||||
expected.Expiry = time.Unix(expected.Expiry.Unix(), 0)
|
||||
actual.At = time.Unix(actual.At.Unix(), 0)
|
||||
actual.Expiry = time.Unix(actual.Expiry.Unix(), 0)
|
||||
a.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestElem_Encode_Publish(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
e := &Elem{
|
||||
At: time.Now(),
|
||||
MessageWithID: &Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 2,
|
||||
Retained: false,
|
||||
Topic: "/mytopic",
|
||||
Payload: []byte("payload"),
|
||||
PacketID: 2,
|
||||
ContentType: "type",
|
||||
CorrelationData: nil,
|
||||
MessageExpiry: 1,
|
||||
PayloadFormat: packets.PayloadFormatString,
|
||||
ResponseTopic: "",
|
||||
SubscriptionIdentifier: []uint32{1, 2},
|
||||
UserProperties: []packets.UserProperty{
|
||||
{
|
||||
K: []byte("1"),
|
||||
V: []byte("2"),
|
||||
}, {
|
||||
K: []byte("3"),
|
||||
V: []byte("4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rs := e.Encode()
|
||||
de := &Elem{}
|
||||
err := de.Decode(rs)
|
||||
a.Nil(err)
|
||||
assertElemEqual(a, e, de)
|
||||
}
|
||||
func TestElem_Encode_Pubrel(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
e := &Elem{
|
||||
At: time.Unix(time.Now().Unix(), 0),
|
||||
MessageWithID: &Pubrel{
|
||||
PacketID: 2,
|
||||
},
|
||||
}
|
||||
rs := e.Encode()
|
||||
de := &Elem{}
|
||||
err := de.Decode(rs)
|
||||
a.Nil(err)
|
||||
assertElemEqual(a, e, de)
|
||||
}
|
||||
|
||||
func Benchmark_Encode_Publish(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
e := &Elem{
|
||||
At: time.Unix(time.Now().Unix(), 0),
|
||||
MessageWithID: &Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 2,
|
||||
Retained: false,
|
||||
Topic: "/mytopic",
|
||||
Payload: []byte("payload"),
|
||||
PacketID: 2,
|
||||
ContentType: "type",
|
||||
CorrelationData: nil,
|
||||
MessageExpiry: 1,
|
||||
PayloadFormat: packets.PayloadFormatString,
|
||||
ResponseTopic: "",
|
||||
SubscriptionIdentifier: []uint32{1, 2},
|
||||
UserProperties: []packets.UserProperty{
|
||||
{
|
||||
K: []byte("1"),
|
||||
V: []byte("2"),
|
||||
}, {
|
||||
K: []byte("3"),
|
||||
V: []byte("4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
e.Encode()
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrClosed = errors.New("queue has been closed")
|
||||
ErrDropExceedsMaxPacketSize = errors.New("maximum packet size exceeded")
|
||||
ErrDropQueueFull = errors.New("the message queue is full")
|
||||
ErrDropExpired = errors.New("the message is expired")
|
||||
ErrDropExpiredInflight = errors.New("the inflight message is expired")
|
||||
)
|
||||
|
||||
// InternalError wraps the error of the backend storage.
|
||||
type InternalError struct {
|
||||
// Err is the error return by the backend storage.
|
||||
Err error
|
||||
}
|
||||
|
||||
func (i *InternalError) Error() string {
|
||||
return i.Error()
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
package mem
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var _ queue.Store = (*Queue)(nil)
|
||||
|
||||
type Options struct {
|
||||
MaxQueuedMsg int
|
||||
InflightExpiry time.Duration
|
||||
ClientID string
|
||||
DefaultNotifier queue.Notifier
|
||||
}
|
||||
|
||||
type Queue struct {
|
||||
cond *sync.Cond
|
||||
clientID string
|
||||
version packets.Version
|
||||
opts *Options
|
||||
readBytesLimit uint32
|
||||
l *list.List
|
||||
// current is the next element to read.
|
||||
current *list.Element
|
||||
inflightDrained bool
|
||||
closed bool
|
||||
// max is the maximum queue length
|
||||
max int
|
||||
log *zap.Logger
|
||||
inflightExpiry time.Duration
|
||||
notifier queue.Notifier
|
||||
}
|
||||
|
||||
func New(opts Options) (*Queue, error) {
|
||||
return &Queue{
|
||||
clientID: opts.ClientID,
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
l: list.New(),
|
||||
max: opts.MaxQueuedMsg,
|
||||
inflightExpiry: opts.InflightExpiry,
|
||||
notifier: opts.DefaultNotifier,
|
||||
log: server.LoggerWithField(zap.String("queue", "memory")),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *Queue) Close() error {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
q.closed = true
|
||||
q.cond.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) Init(opts *queue.InitOptions) error {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
q.closed = false
|
||||
q.inflightDrained = false
|
||||
if opts.CleanStart {
|
||||
q.l = list.New()
|
||||
}
|
||||
q.readBytesLimit = opts.ReadBytesLimit
|
||||
q.version = opts.Version
|
||||
q.current = q.l.Front()
|
||||
q.notifier = opts.Notifier
|
||||
q.cond.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Queue) Clean() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) Add(elem *queue.Elem) (err error) {
|
||||
now := time.Now()
|
||||
var dropErr error
|
||||
var dropElem *list.Element
|
||||
var drop bool
|
||||
q.cond.L.Lock()
|
||||
defer func() {
|
||||
q.cond.L.Unlock()
|
||||
q.cond.Signal()
|
||||
}()
|
||||
defer func() {
|
||||
if drop {
|
||||
if dropErr == queue.ErrDropExpiredInflight {
|
||||
q.notifier.NotifyInflightAdded(-1)
|
||||
}
|
||||
if dropElem == nil {
|
||||
q.notifier.NotifyDropped(elem, dropErr)
|
||||
return
|
||||
}
|
||||
if dropElem == q.current {
|
||||
q.current = q.current.Next()
|
||||
}
|
||||
q.l.Remove(dropElem)
|
||||
q.notifier.NotifyDropped(dropElem.Value.(*queue.Elem), dropErr)
|
||||
} else {
|
||||
q.notifier.NotifyMsgQueueAdded(1)
|
||||
}
|
||||
e := q.l.PushBack(elem)
|
||||
if q.current == nil {
|
||||
q.current = e
|
||||
}
|
||||
}()
|
||||
if q.l.Len() >= q.max {
|
||||
// set default drop error
|
||||
dropErr = queue.ErrDropQueueFull
|
||||
drop = true
|
||||
|
||||
// drop expired inflight message
|
||||
if v := q.l.Front(); v != q.current &&
|
||||
v != nil &&
|
||||
queue.ElemExpiry(now, v.Value.(*queue.Elem)) {
|
||||
dropElem = v
|
||||
dropErr = queue.ErrDropExpiredInflight
|
||||
return
|
||||
}
|
||||
|
||||
// drop the current elem if there is no more non-inflight messages.
|
||||
if q.inflightDrained && q.current == nil {
|
||||
return
|
||||
}
|
||||
for e := q.current; e != nil; e = e.Next() {
|
||||
pub := e.Value.(*queue.Elem).MessageWithID.(*queue.Publish)
|
||||
// drop expired non-inflight message
|
||||
if pub.ID() == 0 &&
|
||||
queue.ElemExpiry(now, e.Value.(*queue.Elem)) {
|
||||
dropElem = e
|
||||
dropErr = queue.ErrDropExpired
|
||||
return
|
||||
}
|
||||
// drop qos0 message in the queue
|
||||
if pub.ID() == 0 && pub.QoS == packets.Qos0 && dropElem == nil {
|
||||
dropElem = e
|
||||
}
|
||||
}
|
||||
if dropElem != nil {
|
||||
return
|
||||
}
|
||||
if elem.MessageWithID.(*queue.Publish).QoS == packets.Qos0 {
|
||||
return
|
||||
}
|
||||
|
||||
if q.inflightDrained {
|
||||
// drop the front message
|
||||
dropElem = q.current
|
||||
return
|
||||
}
|
||||
// the messages in the queue are all inflight messages, drop the current elem
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) Replace(elem *queue.Elem) (replaced bool, err error) {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
unread := q.current
|
||||
for e := q.l.Front(); e != nil && e != unread; e = e.Next() {
|
||||
if e.Value.(*queue.Elem).ID() == elem.ID() {
|
||||
e.Value = elem
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (q *Queue) Read(pids []packets.PacketID) (rs []*queue.Elem, err error) {
|
||||
now := time.Now()
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
if !q.inflightDrained {
|
||||
panic("must call ReadInflight to drain all inflight messages before Read")
|
||||
}
|
||||
for (q.l.Len() == 0 || q.current == nil) && !q.closed {
|
||||
q.cond.Wait()
|
||||
}
|
||||
if q.closed {
|
||||
return nil, queue.ErrClosed
|
||||
}
|
||||
length := q.l.Len()
|
||||
if len(pids) < length {
|
||||
length = len(pids)
|
||||
}
|
||||
var msgQueueDelta, inflightDelta int
|
||||
var pflag int
|
||||
for i := 0; i < length && q.current != nil; i++ {
|
||||
v := q.current
|
||||
// remove expired message
|
||||
if queue.ElemExpiry(now, v.Value.(*queue.Elem)) {
|
||||
q.current = q.current.Next()
|
||||
q.notifier.NotifyDropped(v.Value.(*queue.Elem), queue.ErrDropExpired)
|
||||
q.l.Remove(v)
|
||||
msgQueueDelta--
|
||||
continue
|
||||
}
|
||||
// remove message which exceeds maximum packet size
|
||||
pub := v.Value.(*queue.Elem).MessageWithID.(*queue.Publish)
|
||||
if size := pub.TotalBytes(q.version); size > q.readBytesLimit {
|
||||
q.current = q.current.Next()
|
||||
q.notifier.NotifyDropped(v.Value.(*queue.Elem), queue.ErrDropExceedsMaxPacketSize)
|
||||
q.l.Remove(v)
|
||||
msgQueueDelta--
|
||||
continue
|
||||
}
|
||||
|
||||
// remove qos 0 message after read
|
||||
if pub.QoS == 0 {
|
||||
q.current = q.current.Next()
|
||||
q.l.Remove(v)
|
||||
msgQueueDelta--
|
||||
} else {
|
||||
pub.SetID(pids[pflag])
|
||||
// When the message becomes inflight message, update the expiry time.
|
||||
if q.inflightExpiry != 0 {
|
||||
v.Value.(*queue.Elem).Expiry = now.Add(q.inflightExpiry)
|
||||
}
|
||||
pflag++
|
||||
inflightDelta++
|
||||
q.current = q.current.Next()
|
||||
}
|
||||
rs = append(rs, v.Value.(*queue.Elem))
|
||||
}
|
||||
q.notifier.NotifyMsgQueueAdded(msgQueueDelta)
|
||||
q.notifier.NotifyInflightAdded(inflightDelta)
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (q *Queue) ReadInflight(maxSize uint) (rs []*queue.Elem, err error) {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
length := q.l.Len()
|
||||
if length == 0 || q.current == nil {
|
||||
q.inflightDrained = true
|
||||
return nil, nil
|
||||
}
|
||||
if int(maxSize) < length {
|
||||
length = int(maxSize)
|
||||
}
|
||||
for i := 0; i < length && q.current != nil; i++ {
|
||||
if e := q.current.Value.(*queue.Elem); e.ID() != 0 {
|
||||
if q.inflightExpiry != 0 {
|
||||
e.Expiry = time.Now().Add(q.inflightExpiry)
|
||||
}
|
||||
rs = append(rs, e)
|
||||
q.current = q.current.Next()
|
||||
} else {
|
||||
q.inflightDrained = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (q *Queue) Remove(pid packets.PacketID) error {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
// Must not remove unread messages.
|
||||
unread := q.current
|
||||
for e := q.l.Front(); e != nil && e != unread; e = e.Next() {
|
||||
if e.Value.(*queue.Elem).ID() == pid {
|
||||
q.l.Remove(e)
|
||||
q.notifier.NotifyMsgQueueAdded(-1)
|
||||
q.notifier.NotifyInflightAdded(-1)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
// InitOptions is used to pass some required client information to the queue.Init()
|
||||
type InitOptions struct {
|
||||
// CleanStart is the cleanStart field in the connect packet.
|
||||
CleanStart bool
|
||||
// Version is the client MQTT protocol version.
|
||||
Version packets.Version
|
||||
// ReadBytesLimit indicates the maximum publish size that is allow to read.
|
||||
ReadBytesLimit uint32
|
||||
Notifier Notifier
|
||||
}
|
||||
|
||||
// Store represents a queue store for one client.
|
||||
type Store interface {
|
||||
// Close will be called when the client disconnect.
|
||||
// This method must unblock the Read method.
|
||||
Close() error
|
||||
// Init will be called when the client connect.
|
||||
// If opts.CleanStart set to true, the implementation should remove any associated data in backend store.
|
||||
// If it sets to false, the implementation should be able to retrieve the associated data from backend store.
|
||||
// The opts.version indicates the protocol version of the connected client, it is mainly used to calculate the publish packet size.
|
||||
Init(opts *InitOptions) error
|
||||
Clean() error
|
||||
// Add inserts a elem to the queue.
|
||||
// When the len of queue is reaching the maximum setting, the implementation should drop messages according the following priorities:
|
||||
// 1. Drop the expired inflight message.
|
||||
// 2. Drop the current elem if there is no more non-inflight messages.
|
||||
// 3. Drop expired non-inflight message.
|
||||
// 4. Drop qos0 message.
|
||||
// 5. Drop the front message.
|
||||
// See queue.mem for more details.
|
||||
Add(elem *Elem) error
|
||||
// Replace replaces the PUBLISH with the PUBREL with the same packet id.
|
||||
Replace(elem *Elem) (replaced bool, err error)
|
||||
|
||||
// Read reads a batch of new message (non-inflight) from the store. The qos0 messages will be removed after read.
|
||||
// The size of the batch will be less than or equal to the size of the given packet id list.
|
||||
// The implementation must remove and do not return any :
|
||||
// 1. expired messages
|
||||
// 2. publish message which exceeds the InitOptions.ReadBytesLimit
|
||||
// while reading.
|
||||
// The caller must call ReadInflight first to read all inflight message before calling this method.
|
||||
// Calling this method will be blocked until there are any new messages can be read or the store has been closed.
|
||||
// If the store has been closed, returns nil, ErrClosed.
|
||||
Read(pids []packets.PacketID) ([]*Elem, error)
|
||||
|
||||
// ReadInflight reads at most maxSize inflight messages.
|
||||
// The caller must call this method to read all inflight messages before calling Read method.
|
||||
// Returning 0 length elems means all inflight messages have been read.
|
||||
ReadInflight(maxSize uint) (elems []*Elem, err error)
|
||||
|
||||
// Remove removes the elem for a given id.
|
||||
Remove(pid packets.PacketID) error
|
||||
}
|
||||
|
||||
type Notifier interface {
|
||||
// NotifyDropped will be called when the element in the queue is dropped.
|
||||
// The err indicates the reason of why it is dropped.
|
||||
// The MessageWithID field in elem param can be queue.Pubrel or queue.Publish.
|
||||
NotifyDropped(elem *Elem, err error)
|
||||
NotifyInflightAdded(delta int)
|
||||
NotifyMsgQueueAdded(delta int)
|
||||
}
|
||||
|
||||
// ElemExpiry return whether the elem is expired
|
||||
func ElemExpiry(now time.Time, elem *Elem) bool {
|
||||
if !elem.Expiry.IsZero() {
|
||||
return now.After(elem.Expiry)
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: persistence/queue/queue.go
|
||||
|
||||
// Package queue is a generated GoMock package.
|
||||
package queue
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
packets "github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
// MockStore is a mock of Store interface
|
||||
type MockStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStoreMockRecorder
|
||||
}
|
||||
|
||||
// MockStoreMockRecorder is the mock recorder for MockStore
|
||||
type MockStoreMockRecorder struct {
|
||||
mock *MockStore
|
||||
}
|
||||
|
||||
// NewMockStore creates a new mock instance
|
||||
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||
mock := &MockStore{ctrl: ctrl}
|
||||
mock.recorder = &MockStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method
|
||||
func (m *MockStore) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close
|
||||
func (mr *MockStoreMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStore)(nil).Close))
|
||||
}
|
||||
|
||||
// Init mocks base method
|
||||
func (m *MockStore) Init(opts *InitOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", opts)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init
|
||||
func (mr *MockStoreMockRecorder) Init(opts interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockStore)(nil).Init), opts)
|
||||
}
|
||||
|
||||
// Clean mocks base method
|
||||
func (m *MockStore) Clean() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Clean")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Clean indicates an expected call of Clean
|
||||
func (mr *MockStoreMockRecorder) Clean() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockStore)(nil).Clean))
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockStore) Add(elem *Elem) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Add", elem)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add
|
||||
func (mr *MockStoreMockRecorder) Add(elem interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockStore)(nil).Add), elem)
|
||||
}
|
||||
|
||||
// Replace mocks base method
|
||||
func (m *MockStore) Replace(elem *Elem) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Replace", elem)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Replace indicates an expected call of Replace
|
||||
func (mr *MockStoreMockRecorder) Replace(elem interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Replace", reflect.TypeOf((*MockStore)(nil).Replace), elem)
|
||||
}
|
||||
|
||||
// Read mocks base method
|
||||
func (m *MockStore) Read(pids []packets.PacketID) ([]*Elem, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Read", pids)
|
||||
ret0, _ := ret[0].([]*Elem)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Read indicates an expected call of Read
|
||||
func (mr *MockStoreMockRecorder) Read(pids interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockStore)(nil).Read), pids)
|
||||
}
|
||||
|
||||
// ReadInflight mocks base method
|
||||
func (m *MockStore) ReadInflight(maxSize uint) ([]*Elem, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ReadInflight", maxSize)
|
||||
ret0, _ := ret[0].([]*Elem)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ReadInflight indicates an expected call of ReadInflight
|
||||
func (mr *MockStoreMockRecorder) ReadInflight(maxSize interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadInflight", reflect.TypeOf((*MockStore)(nil).ReadInflight), maxSize)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockStore) Remove(pid packets.PacketID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Remove", pid)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockStoreMockRecorder) Remove(pid interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStore)(nil).Remove), pid)
|
||||
}
|
||||
|
||||
// MockNotifier is a mock of Notifier interface
|
||||
type MockNotifier struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockNotifierMockRecorder
|
||||
}
|
||||
|
||||
// MockNotifierMockRecorder is the mock recorder for MockNotifier
|
||||
type MockNotifierMockRecorder struct {
|
||||
mock *MockNotifier
|
||||
}
|
||||
|
||||
// NewMockNotifier creates a new mock instance
|
||||
func NewMockNotifier(ctrl *gomock.Controller) *MockNotifier {
|
||||
mock := &MockNotifier{ctrl: ctrl}
|
||||
mock.recorder = &MockNotifierMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockNotifier) EXPECT() *MockNotifierMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// NotifyDropped mocks base method
|
||||
func (m *MockNotifier) NotifyDropped(elem *Elem, err error) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "NotifyDropped", elem, err)
|
||||
}
|
||||
|
||||
// NotifyDropped indicates an expected call of NotifyDropped
|
||||
func (mr *MockNotifierMockRecorder) NotifyDropped(elem, err interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotifyDropped", reflect.TypeOf((*MockNotifier)(nil).NotifyDropped), elem, err)
|
||||
}
|
||||
|
||||
// NotifyInflightAdded mocks base method
|
||||
func (m *MockNotifier) NotifyInflightAdded(delta int) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "NotifyInflightAdded", delta)
|
||||
}
|
||||
|
||||
// NotifyInflightAdded indicates an expected call of NotifyInflightAdded
|
||||
func (mr *MockNotifierMockRecorder) NotifyInflightAdded(delta interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotifyInflightAdded", reflect.TypeOf((*MockNotifier)(nil).NotifyInflightAdded), delta)
|
||||
}
|
||||
|
||||
// NotifyMsgQueueAdded mocks base method
|
||||
func (m *MockNotifier) NotifyMsgQueueAdded(delta int) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "NotifyMsgQueueAdded", delta)
|
||||
}
|
||||
|
||||
// NotifyMsgQueueAdded indicates an expected call of NotifyMsgQueueAdded
|
||||
func (mr *MockNotifierMockRecorder) NotifyMsgQueueAdded(delta interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotifyMsgQueueAdded", reflect.TypeOf((*MockNotifier)(nil).NotifyMsgQueueAdded), delta)
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
redigo "github.com/gomodule/redigo/redis"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/codes"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue"
|
||||
)
|
||||
|
||||
const (
|
||||
queuePrefix = "queue:"
|
||||
)
|
||||
|
||||
var _ queue.Store = (*Queue)(nil)
|
||||
|
||||
func getKey(clientID string) string {
|
||||
return queuePrefix + clientID
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
MaxQueuedMsg int
|
||||
ClientID string
|
||||
InflightExpiry time.Duration
|
||||
Pool *redigo.Pool
|
||||
DefaultNotifier queue.Notifier
|
||||
}
|
||||
|
||||
type Queue struct {
|
||||
cond *sync.Cond
|
||||
clientID string
|
||||
version packets.Version
|
||||
readBytesLimit uint32
|
||||
// max is the maximum queue length
|
||||
max int
|
||||
// len is the length of the list
|
||||
len int
|
||||
pool *redigo.Pool
|
||||
closed bool
|
||||
inflightDrained bool
|
||||
// current is the current read index of Queue list.
|
||||
current int
|
||||
readCache map[packets.PacketID][]byte
|
||||
err error
|
||||
log *zap.Logger
|
||||
inflightExpiry time.Duration
|
||||
notifier queue.Notifier
|
||||
}
|
||||
|
||||
func New(opts Options) (*Queue, error) {
|
||||
return &Queue{
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
clientID: opts.ClientID,
|
||||
max: opts.MaxQueuedMsg,
|
||||
len: 0,
|
||||
pool: opts.Pool,
|
||||
closed: false,
|
||||
inflightDrained: false,
|
||||
current: 0,
|
||||
inflightExpiry: opts.InflightExpiry,
|
||||
notifier: opts.DefaultNotifier,
|
||||
log: server.LoggerWithField(zap.String("queue", "redis")),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func wrapError(err error) *codes.Error {
|
||||
return &codes.Error{
|
||||
Code: codes.UnspecifiedError,
|
||||
ErrorDetails: codes.ErrorDetails{
|
||||
ReasonString: []byte(err.Error()),
|
||||
UserProperties: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queue) Close() error {
|
||||
q.cond.L.Lock()
|
||||
defer func() {
|
||||
q.cond.L.Unlock()
|
||||
q.cond.Signal()
|
||||
}()
|
||||
q.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) setLen(conn redigo.Conn) error {
|
||||
l, err := conn.Do("llen", getKey(q.clientID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.len = int(l.(int64))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) Init(opts *queue.InitOptions) error {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
conn := q.pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
if opts.CleanStart {
|
||||
_, err := conn.Do("del", getKey(q.clientID))
|
||||
if err != nil {
|
||||
return wrapError(err)
|
||||
}
|
||||
}
|
||||
err := q.setLen(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.version = opts.Version
|
||||
q.readBytesLimit = opts.ReadBytesLimit
|
||||
q.closed = false
|
||||
q.inflightDrained = false
|
||||
q.current = 0
|
||||
q.readCache = make(map[packets.PacketID][]byte)
|
||||
q.notifier = opts.Notifier
|
||||
q.cond.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) Clean() error {
|
||||
conn := q.pool.Get()
|
||||
defer conn.Close()
|
||||
_, err := conn.Do("del", getKey(q.clientID))
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *Queue) Add(elem *queue.Elem) (err error) {
|
||||
now := time.Now()
|
||||
conn := q.pool.Get()
|
||||
q.cond.L.Lock()
|
||||
var dropErr error
|
||||
var dropBytes []byte
|
||||
var dropElem *queue.Elem
|
||||
var drop bool
|
||||
defer func() {
|
||||
conn.Close()
|
||||
q.cond.L.Unlock()
|
||||
q.cond.Signal()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if drop {
|
||||
if dropErr == queue.ErrDropExpiredInflight {
|
||||
q.notifier.NotifyInflightAdded(-1)
|
||||
q.current--
|
||||
}
|
||||
if dropBytes == nil {
|
||||
q.notifier.NotifyDropped(elem, dropErr)
|
||||
return
|
||||
} else {
|
||||
err = conn.Send("lrem", getKey(q.clientID), 1, dropBytes)
|
||||
}
|
||||
q.notifier.NotifyDropped(dropElem, dropErr)
|
||||
} else {
|
||||
q.notifier.NotifyMsgQueueAdded(1)
|
||||
q.len++
|
||||
}
|
||||
_ = conn.Send("rpush", getKey(q.clientID), elem.Encode())
|
||||
err = conn.Flush()
|
||||
}()
|
||||
if q.len >= q.max {
|
||||
// set default drop error
|
||||
dropErr = queue.ErrDropQueueFull
|
||||
drop = true
|
||||
var rs []interface{}
|
||||
// drop expired inflight message
|
||||
rs, err = redigo.Values(conn.Do("lrange", getKey(q.clientID), 0, q.len))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var frontBytes []byte
|
||||
var frontElem *queue.Elem
|
||||
for i := 0; i < len(rs); i++ {
|
||||
b := rs[i].([]byte)
|
||||
e := &queue.Elem{}
|
||||
err = e.Decode(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// inflight message
|
||||
if i < q.current && queue.ElemExpiry(now, e) {
|
||||
dropBytes = b
|
||||
dropElem = e
|
||||
dropErr = queue.ErrDropExpiredInflight
|
||||
return
|
||||
}
|
||||
// non-inflight message
|
||||
if i >= q.current {
|
||||
if i == q.current {
|
||||
frontBytes = b
|
||||
frontElem = e
|
||||
}
|
||||
// drop qos0 message in the queue
|
||||
pub := e.MessageWithID.(*queue.Publish)
|
||||
// drop expired non-inflight message
|
||||
if pub.ID() == 0 && queue.ElemExpiry(now, e) {
|
||||
dropBytes = b
|
||||
dropElem = e
|
||||
dropErr = queue.ErrDropExpired
|
||||
return
|
||||
}
|
||||
if pub.ID() == 0 && pub.QoS == packets.Qos0 && dropElem == nil {
|
||||
dropBytes = b
|
||||
dropElem = e
|
||||
}
|
||||
}
|
||||
}
|
||||
// drop the current elem if there is no more non-inflight messages.
|
||||
if q.inflightDrained && q.current >= q.len {
|
||||
return
|
||||
}
|
||||
rs, err = redigo.Values(conn.Do("lrange", getKey(q.clientID), q.current, q.len))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dropElem != nil {
|
||||
return
|
||||
}
|
||||
if elem.MessageWithID.(*queue.Publish).QoS == packets.Qos0 {
|
||||
return
|
||||
}
|
||||
if frontElem != nil {
|
||||
// drop the front message
|
||||
dropBytes = frontBytes
|
||||
dropElem = frontElem
|
||||
}
|
||||
// the the messages in the queue are all inflight messages, drop the current elem
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) Replace(elem *queue.Elem) (replaced bool, err error) {
|
||||
conn := q.pool.Get()
|
||||
q.cond.L.Lock()
|
||||
defer func() {
|
||||
conn.Close()
|
||||
q.cond.L.Unlock()
|
||||
}()
|
||||
id := elem.ID()
|
||||
eb := elem.Encode()
|
||||
stop := q.current - 1
|
||||
if stop < 0 {
|
||||
stop = 0
|
||||
}
|
||||
rs, err := redigo.Values(conn.Do("lrange", getKey(q.clientID), 0, stop))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for k, v := range rs {
|
||||
b := v.([]byte)
|
||||
e := &queue.Elem{}
|
||||
err = e.Decode(b)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if e.ID() == elem.ID() {
|
||||
_, err = conn.Do("lset", getKey(q.clientID), k, eb)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
q.readCache[id] = eb
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (q *Queue) Read(pids []packets.PacketID) (elems []*queue.Elem, err error) {
|
||||
now := time.Now()
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
conn := q.pool.Get()
|
||||
defer conn.Close()
|
||||
if !q.inflightDrained {
|
||||
panic("must call ReadInflight to drain all inflight messages before Read")
|
||||
}
|
||||
for q.current >= q.len && !q.closed {
|
||||
q.cond.Wait()
|
||||
}
|
||||
if q.closed {
|
||||
return nil, queue.ErrClosed
|
||||
}
|
||||
rs, err := redigo.Values(conn.Do("lrange", getKey(q.clientID), q.current, q.current+len(pids)-1))
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
var msgQueueDelta, inflightDelta int
|
||||
var pflag int
|
||||
for i := 0; i < len(rs); i++ {
|
||||
b := rs[i].([]byte)
|
||||
e := &queue.Elem{}
|
||||
err := e.Decode(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// remove expired message
|
||||
if queue.ElemExpiry(now, e) {
|
||||
err = conn.Send("lrem", getKey(q.clientID), 1, b)
|
||||
q.len--
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.notifier.NotifyDropped(e, queue.ErrDropExpired)
|
||||
msgQueueDelta--
|
||||
continue
|
||||
}
|
||||
|
||||
// remove message which exceeds maximum packet size
|
||||
pub := e.MessageWithID.(*queue.Publish)
|
||||
if size := pub.TotalBytes(q.version); size > q.readBytesLimit {
|
||||
err = conn.Send("lrem", getKey(q.clientID), 1, b)
|
||||
q.len--
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.notifier.NotifyDropped(e, queue.ErrDropExceedsMaxPacketSize)
|
||||
msgQueueDelta--
|
||||
continue
|
||||
}
|
||||
|
||||
if e.MessageWithID.(*queue.Publish).QoS == 0 {
|
||||
err = conn.Send("lrem", getKey(q.clientID), 1, b)
|
||||
q.len--
|
||||
msgQueueDelta--
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
e.MessageWithID.SetID(pids[pflag])
|
||||
if q.inflightExpiry != 0 {
|
||||
e.Expiry = now.Add(q.inflightExpiry)
|
||||
}
|
||||
pflag++
|
||||
nb := e.Encode()
|
||||
|
||||
err = conn.Send("lset", getKey(q.clientID), q.current, nb)
|
||||
q.current++
|
||||
inflightDelta++
|
||||
q.readCache[e.MessageWithID.ID()] = nb
|
||||
}
|
||||
elems = append(elems, e)
|
||||
}
|
||||
err = conn.Flush()
|
||||
q.notifier.NotifyMsgQueueAdded(msgQueueDelta)
|
||||
q.notifier.NotifyInflightAdded(inflightDelta)
|
||||
return
|
||||
}
|
||||
|
||||
func (q *Queue) ReadInflight(maxSize uint) (elems []*queue.Elem, err error) {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
conn := q.pool.Get()
|
||||
defer conn.Close()
|
||||
rs, err := redigo.Values(conn.Do("lrange", getKey(q.clientID), q.current, q.current+int(maxSize)-1))
|
||||
if len(rs) == 0 {
|
||||
q.inflightDrained = true
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
beginIndex := q.current
|
||||
for index, v := range rs {
|
||||
b := v.([]byte)
|
||||
e := &queue.Elem{}
|
||||
err := e.Decode(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id := e.MessageWithID.ID()
|
||||
if id != 0 {
|
||||
if q.inflightExpiry != 0 {
|
||||
e.Expiry = time.Now().Add(q.inflightExpiry)
|
||||
b = e.Encode()
|
||||
_, err = conn.Do("lset", getKey(q.clientID), beginIndex+index, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
elems = append(elems, e)
|
||||
q.readCache[id] = b
|
||||
q.current++
|
||||
} else {
|
||||
q.inflightDrained = true
|
||||
return elems, nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (q *Queue) Remove(pid packets.PacketID) error {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
conn := q.pool.Get()
|
||||
defer conn.Close()
|
||||
if b, ok := q.readCache[pid]; ok {
|
||||
_, err := conn.Do("lrem", getKey(q.clientID), 1, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.notifier.NotifyMsgQueueAdded(-1)
|
||||
q.notifier.NotifyInflightAdded(-1)
|
||||
delete(q.readCache, pid)
|
||||
q.len--
|
||||
q.current--
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,672 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var (
|
||||
TestServerConfig = config.Config{
|
||||
MQTT: config.MQTT{
|
||||
MaxQueuedMsg: 5,
|
||||
InflightExpiry: 2 * time.Second,
|
||||
},
|
||||
}
|
||||
cid = "cid"
|
||||
TestClientID = cid
|
||||
TestNotifier = &testNotifier{}
|
||||
)
|
||||
|
||||
type testNotifier struct {
|
||||
dropElem []*queue.Elem
|
||||
dropErr error
|
||||
inflightLen int
|
||||
msgQueueLen int
|
||||
}
|
||||
|
||||
func (t *testNotifier) NotifyDropped(elem *queue.Elem, err error) {
|
||||
t.dropElem = append(t.dropElem, elem)
|
||||
t.dropErr = err
|
||||
}
|
||||
|
||||
func (t *testNotifier) NotifyInflightAdded(delta int) {
|
||||
t.inflightLen += delta
|
||||
if t.inflightLen < 0 {
|
||||
t.inflightLen = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testNotifier) NotifyMsgQueueAdded(delta int) {
|
||||
t.msgQueueLen += delta
|
||||
if t.msgQueueLen < 0 {
|
||||
t.msgQueueLen = 0
|
||||
}
|
||||
}
|
||||
|
||||
func initDrop() {
|
||||
TestNotifier.dropElem = nil
|
||||
TestNotifier.dropErr = nil
|
||||
}
|
||||
|
||||
func initNotifierLen() {
|
||||
TestNotifier.inflightLen = 0
|
||||
TestNotifier.msgQueueLen = 0
|
||||
}
|
||||
|
||||
func assertMsgEqual(a *assert.Assertions, expected, actual *queue.Elem) {
|
||||
expMsg := expected.MessageWithID.(*queue.Publish).Message
|
||||
actMsg := actual.MessageWithID.(*queue.Publish).Message
|
||||
a.Equal(expMsg.Topic, actMsg.Topic)
|
||||
a.Equal(expMsg.QoS, actMsg.QoS)
|
||||
a.Equal(expMsg.Payload, actMsg.Payload)
|
||||
a.Equal(expMsg.PacketID, actMsg.PacketID)
|
||||
}
|
||||
|
||||
func assertQueueLen(a *assert.Assertions, inflightLen, msgQueueLen int) {
|
||||
a.Equal(inflightLen, TestNotifier.inflightLen)
|
||||
a.Equal(msgQueueLen, TestNotifier.msgQueueLen)
|
||||
}
|
||||
|
||||
// 2 inflight message + 3 new message
|
||||
var initElems = []*queue.Elem{
|
||||
{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: packets.Qos1,
|
||||
Retained: false,
|
||||
Topic: "/topic1_qos1",
|
||||
Payload: []byte("qos1"),
|
||||
PacketID: 1,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: packets.Qos2,
|
||||
Retained: false,
|
||||
Topic: "/topic1_qos2",
|
||||
Payload: []byte("qos2"),
|
||||
PacketID: 2,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: packets.Qos1,
|
||||
Retained: false,
|
||||
Topic: "/topic1_qos1",
|
||||
Payload: []byte("qos1"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: packets.Qos0,
|
||||
Retained: false,
|
||||
Topic: "/topic1_qos0",
|
||||
Payload: []byte("qos0"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: packets.Qos2,
|
||||
Retained: false,
|
||||
Topic: "/topic1_qos2",
|
||||
Payload: []byte("qos2"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func initStore(store queue.Store) error {
|
||||
return store.Init(&queue.InitOptions{
|
||||
CleanStart: true,
|
||||
Version: packets.Version5,
|
||||
ReadBytesLimit: 100,
|
||||
Notifier: TestNotifier,
|
||||
})
|
||||
}
|
||||
|
||||
func add(store queue.Store) error {
|
||||
for _, v := range initElems {
|
||||
elem := *v
|
||||
elem.MessageWithID = &queue.Publish{
|
||||
Message: elem.MessageWithID.(*queue.Publish).Message.Copy(),
|
||||
}
|
||||
err := store.Add(&elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
TestNotifier.inflightLen = 2
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertDrop(a *assert.Assertions, elem *queue.Elem, err error) {
|
||||
a.Len(TestNotifier.dropElem, 1)
|
||||
switch elem.MessageWithID.(type) {
|
||||
case *queue.Publish:
|
||||
actual := TestNotifier.dropElem[0].MessageWithID.(*queue.Publish)
|
||||
pub := elem.MessageWithID.(*queue.Publish)
|
||||
a.Equal(pub.Message.Topic, actual.Topic)
|
||||
a.Equal(pub.Message.QoS, actual.QoS)
|
||||
a.Equal(pub.Payload, actual.Payload)
|
||||
a.Equal(pub.PacketID, actual.PacketID)
|
||||
a.Equal(err, TestNotifier.dropErr)
|
||||
case *queue.Pubrel:
|
||||
actual := TestNotifier.dropElem[0].MessageWithID.(*queue.Pubrel)
|
||||
pubrel := elem.MessageWithID.(*queue.Pubrel)
|
||||
a.Equal(pubrel.PacketID, actual.PacketID)
|
||||
a.Equal(err, TestNotifier.dropErr)
|
||||
default:
|
||||
a.FailNow("unexpected elem type")
|
||||
|
||||
}
|
||||
initDrop()
|
||||
}
|
||||
|
||||
func reconnect(a *assert.Assertions, cleanStart bool, store queue.Store) {
|
||||
a.NoError(store.Close())
|
||||
a.NoError(store.Init(&queue.InitOptions{
|
||||
CleanStart: cleanStart,
|
||||
Version: packets.Version5,
|
||||
ReadBytesLimit: 100,
|
||||
Notifier: TestNotifier,
|
||||
}))
|
||||
}
|
||||
|
||||
type New func(config config.Config, hooks server.Hooks) (server.Persistence, error)
|
||||
|
||||
func TestQueue(t *testing.T, store queue.Store) {
|
||||
initDrop()
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
a.NoError(initStore(store))
|
||||
a.NoError(add(store))
|
||||
assertQueueLen(a, 2, 5)
|
||||
testRead(a, store)
|
||||
testDrop(a, store)
|
||||
testReplace(a, store)
|
||||
testCleanStart(a, store)
|
||||
testReadExceedsDrop(a, store)
|
||||
testClose(a, store)
|
||||
}
|
||||
|
||||
func testDrop(a *assert.Assertions, store queue.Store) {
|
||||
// wait inflight messages to expire
|
||||
time.Sleep(TestServerConfig.MQTT.InflightExpiry)
|
||||
for i := 0; i < 3; i++ {
|
||||
err := store.Add(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 2,
|
||||
Retained: false,
|
||||
Topic: "123",
|
||||
Payload: []byte("123"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
a.Nil(err)
|
||||
}
|
||||
// drop expired inflight message (pid=1)
|
||||
dropElem := initElems[0]
|
||||
// queue: 1,2,0(qos2),0(qos2),0(qos2) (1 and 2 are expired inflight messages)
|
||||
err := store.Add(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "123",
|
||||
Payload: []byte("123"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
a.NoError(err)
|
||||
assertDrop(a, dropElem, queue.ErrDropExpiredInflight)
|
||||
assertQueueLen(a, 1, 5)
|
||||
|
||||
e, err := store.Read([]packets.PacketID{5, 6, 7})
|
||||
a.NoError(err)
|
||||
a.Len(e, 3)
|
||||
a.EqualValues(5, e[0].MessageWithID.ID())
|
||||
a.EqualValues(6, e[1].MessageWithID.ID())
|
||||
a.EqualValues(7, e[2].MessageWithID.ID())
|
||||
// queue: 2,5(qos2),6(qos2),7(qos2), 0(qos1) (2 is expired inflight message)
|
||||
assertQueueLen(a, 4, 5)
|
||||
// drop expired inflight message (pid=2)
|
||||
dropElem = initElems[1]
|
||||
err = store.Add(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "1234",
|
||||
Payload: []byte("1234"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
a.NoError(err)
|
||||
// queue: 5(qos2),6(qos2),7(qos2), 0(qos1), 0(qos1)
|
||||
assertDrop(a, dropElem, queue.ErrDropExpiredInflight)
|
||||
|
||||
assertQueueLen(a, 3, 5)
|
||||
e, err = store.Read([]packets.PacketID{8, 9})
|
||||
|
||||
a.NoError(err)
|
||||
|
||||
// queue: 5(qos2),6(qos2),7(qos2),8(qos1),9(qos1)
|
||||
a.Len(e, 2)
|
||||
a.EqualValues(8, e[0].MessageWithID.ID())
|
||||
a.EqualValues(9, e[1].MessageWithID.ID())
|
||||
assertQueueLen(a, 5, 5)
|
||||
|
||||
// drop the elem that is going to enqueue if there is no more non-inflight messages.
|
||||
dropElem = &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "123",
|
||||
Payload: []byte("123"),
|
||||
PacketID: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
err = store.Add(dropElem)
|
||||
a.NoError(err)
|
||||
assertDrop(a, dropElem, queue.ErrDropQueueFull)
|
||||
assertQueueLen(a, 5, 5)
|
||||
|
||||
// queue: 5(qos2),6(qos2),7(qos2),8(qos1),9(qos1)
|
||||
a.NoError(store.Remove(5))
|
||||
a.NoError(store.Remove(6))
|
||||
// queue: 7(qos2),8(qos2),9(qos2)
|
||||
assertQueueLen(a, 3, 3)
|
||||
|
||||
dropQoS0 := &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 0,
|
||||
Retained: false,
|
||||
Topic: "/t_qos0",
|
||||
Payload: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
a.NoError(store.Add(dropQoS0))
|
||||
// queue: 7(qos2),8(qos2),9(qos2),0 (qos0/t_qos0)
|
||||
assertQueueLen(a, 3, 4)
|
||||
|
||||
// add expired elem
|
||||
dropExpired := &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Now().Add(-10 * time.Second),
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 0,
|
||||
Retained: false,
|
||||
Topic: "/drop",
|
||||
Payload: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
a.NoError(store.Add(dropExpired))
|
||||
// queue: 7(qos2),8(qos2),9(qos2), 0(qos0/t_qos0), 0(qos0/drop)
|
||||
assertQueueLen(a, 3, 5)
|
||||
dropFront := &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "/drop_front",
|
||||
Payload: []byte("drop_front"),
|
||||
},
|
||||
},
|
||||
}
|
||||
// drop the expired non-inflight message
|
||||
a.NoError(store.Add(dropFront))
|
||||
// queue: 7(qos2),8(qos2),9(qos2), 0(qos0/t_qos0), 0(qos1/drop_front)
|
||||
assertDrop(a, dropExpired, queue.ErrDropExpired)
|
||||
assertQueueLen(a, 3, 5)
|
||||
|
||||
// drop qos0 message
|
||||
a.Nil(store.Add(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: packets.Qos1,
|
||||
Retained: false,
|
||||
Topic: "/t_qos1",
|
||||
Payload: []byte("/t_qos1"),
|
||||
},
|
||||
},
|
||||
}))
|
||||
// queue: 7(qos2),8(qos2),9(qos2), 0(qos1/drop_front), 0(qos1/t_qos1)
|
||||
assertDrop(a, dropQoS0, queue.ErrDropQueueFull)
|
||||
assertQueueLen(a, 3, 5)
|
||||
|
||||
expiredPub := &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Now().Add(TestServerConfig.MQTT.InflightExpiry),
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "/t",
|
||||
Payload: []byte("/t"),
|
||||
},
|
||||
},
|
||||
}
|
||||
a.NoError(store.Add(expiredPub))
|
||||
// drop the front message
|
||||
assertDrop(a, dropFront, queue.ErrDropQueueFull)
|
||||
// queue: 7(qos2),8(qos2),9(qos2), 0(qos1/t_qos1), 0(qos1/t)
|
||||
assertQueueLen(a, 3, 5)
|
||||
// replace with an expired pubrel
|
||||
expiredPubrel := &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Now().Add(-1 * time.Second),
|
||||
MessageWithID: &queue.Pubrel{
|
||||
PacketID: 7,
|
||||
},
|
||||
}
|
||||
r, err := store.Replace(expiredPubrel)
|
||||
a.True(r)
|
||||
a.NoError(err)
|
||||
assertQueueLen(a, 3, 5)
|
||||
// queue: 7(qos2-pubrel),8(qos2),9(qos2), 0(qos1/t_qos1), 0(qos1/t)
|
||||
a.NoError(store.Add(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "/t1",
|
||||
Payload: []byte("/t1"),
|
||||
},
|
||||
},
|
||||
}))
|
||||
// queue: 8(qos2),9(qos2), 0(qos1/t_qos1), 0(qos1/t), 0(qos1/t1)
|
||||
assertDrop(a, expiredPubrel, queue.ErrDropExpiredInflight)
|
||||
assertQueueLen(a, 2, 5)
|
||||
drop := &queue.Elem{
|
||||
At: time.Now(),
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 0,
|
||||
Retained: false,
|
||||
Topic: "/t2",
|
||||
Payload: []byte("/t2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
a.NoError(store.Add(drop))
|
||||
assertDrop(a, drop, queue.ErrDropQueueFull)
|
||||
assertQueueLen(a, 2, 5)
|
||||
|
||||
a.NoError(store.Remove(8))
|
||||
a.NoError(store.Remove(9))
|
||||
// queue: 0(qos1/t_qos1), 0(qos1/t), 0(qos1/t1)
|
||||
assertQueueLen(a, 0, 3)
|
||||
// wait qos1/t to expire.
|
||||
time.Sleep(TestServerConfig.MQTT.InflightExpiry)
|
||||
e, err = store.Read([]packets.PacketID{1, 2, 3})
|
||||
a.NoError(err)
|
||||
a.Len(e, 2)
|
||||
assertQueueLen(a, 2, 2)
|
||||
a.NoError(store.Remove(1))
|
||||
a.NoError(store.Remove(2))
|
||||
assertQueueLen(a, 0, 0)
|
||||
}
|
||||
|
||||
func testRead(a *assert.Assertions, store queue.Store) {
|
||||
// 2 inflight
|
||||
e, err := store.ReadInflight(1)
|
||||
a.Nil(err)
|
||||
a.Len(e, 1)
|
||||
assertMsgEqual(a, initElems[0], e[0])
|
||||
|
||||
e, err = store.ReadInflight(2)
|
||||
a.Len(e, 1)
|
||||
assertMsgEqual(a, initElems[1], e[0])
|
||||
pids := []packets.PacketID{3, 4, 5}
|
||||
e, err = store.Read(pids)
|
||||
a.Len(e, 3)
|
||||
|
||||
// must consume packet id in order and do not skip packet id if there are qos0 messages.
|
||||
a.EqualValues(3, e[0].MessageWithID.ID())
|
||||
a.EqualValues(0, e[1].MessageWithID.ID())
|
||||
a.EqualValues(4, e[2].MessageWithID.ID())
|
||||
|
||||
assertQueueLen(a, 4, 4)
|
||||
|
||||
err = store.Remove(3)
|
||||
a.NoError(err)
|
||||
err = store.Remove(4)
|
||||
a.NoError(err)
|
||||
assertQueueLen(a, 2, 2)
|
||||
|
||||
}
|
||||
|
||||
func testReplace(a *assert.Assertions, store queue.Store) {
|
||||
|
||||
var elems []*queue.Elem
|
||||
elems = append(elems, &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: 2,
|
||||
Topic: "/t_replace",
|
||||
Payload: []byte("t_replace"),
|
||||
},
|
||||
},
|
||||
}, &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: 2,
|
||||
Topic: "/t_replace",
|
||||
Payload: []byte("t_replace"),
|
||||
},
|
||||
},
|
||||
}, &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: 2,
|
||||
Topic: "/t_unread",
|
||||
Payload: []byte("t_unread"),
|
||||
PacketID: 3,
|
||||
},
|
||||
},
|
||||
})
|
||||
for i := 0; i < 2; i++ {
|
||||
elems = append(elems, &queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
QoS: 2,
|
||||
Topic: "/t_replace",
|
||||
Payload: []byte("t_replace"),
|
||||
},
|
||||
},
|
||||
})
|
||||
a.NoError(store.Add(elems[i]))
|
||||
}
|
||||
assertQueueLen(a, 0, 2)
|
||||
|
||||
e, err := store.Read([]packets.PacketID{1, 2})
|
||||
// queue: 1(qos2),2(qos2)
|
||||
a.NoError(err)
|
||||
a.Len(e, 2)
|
||||
assertQueueLen(a, 2, 2)
|
||||
r, err := store.Replace(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Pubrel{
|
||||
PacketID: 1,
|
||||
},
|
||||
})
|
||||
a.True(r)
|
||||
a.NoError(err)
|
||||
|
||||
r, err = store.Replace(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Pubrel{
|
||||
PacketID: 3,
|
||||
},
|
||||
})
|
||||
a.False(r)
|
||||
a.NoError(err)
|
||||
a.NoError(store.Add(elems[2]))
|
||||
TestNotifier.inflightLen++
|
||||
// queue: 1(qos2-pubrel),2(qos2), 3(qos2)
|
||||
|
||||
r, err = store.Replace(&queue.Elem{
|
||||
At: time.Now(),
|
||||
Expiry: time.Time{},
|
||||
MessageWithID: &queue.Pubrel{
|
||||
PacketID: packets.PacketID(3),
|
||||
}})
|
||||
a.False(r, "must not replace unread packet")
|
||||
a.NoError(err)
|
||||
assertQueueLen(a, 3, 3)
|
||||
|
||||
reconnect(a, false, store)
|
||||
|
||||
inflight, err := store.ReadInflight(5)
|
||||
a.NoError(err)
|
||||
a.Len(inflight, 3)
|
||||
a.Equal(&queue.Pubrel{
|
||||
PacketID: 1,
|
||||
}, inflight[0].MessageWithID)
|
||||
|
||||
elems[1].MessageWithID.SetID(2)
|
||||
elems[2].MessageWithID.SetID(3)
|
||||
assertMsgEqual(a, elems[1], inflight[1])
|
||||
assertMsgEqual(a, elems[2], inflight[2])
|
||||
assertQueueLen(a, 3, 3)
|
||||
|
||||
}
|
||||
|
||||
func testReadExceedsDrop(a *assert.Assertions, store queue.Store) {
|
||||
// add exceeded message
|
||||
exceeded := &queue.Elem{
|
||||
At: time.Now(),
|
||||
MessageWithID: &queue.Publish{
|
||||
Message: &gmqtt.Message{
|
||||
Dup: false,
|
||||
QoS: 1,
|
||||
Retained: false,
|
||||
Topic: "/drop_exceed",
|
||||
Payload: make([]byte, 100),
|
||||
},
|
||||
},
|
||||
}
|
||||
a.NoError(store.Add(exceeded))
|
||||
assertQueueLen(a, 0, 1)
|
||||
e, err := store.Read([]packets.PacketID{1})
|
||||
a.NoError(err)
|
||||
a.Len(e, 0)
|
||||
assertDrop(a, exceeded, queue.ErrDropExceedsMaxPacketSize)
|
||||
assertQueueLen(a, 0, 0)
|
||||
}
|
||||
|
||||
func testCleanStart(a *assert.Assertions, store queue.Store) {
|
||||
reconnect(a, true, store)
|
||||
rs, err := store.ReadInflight(10)
|
||||
a.NoError(err)
|
||||
a.Len(rs, 0)
|
||||
initDrop()
|
||||
initNotifierLen()
|
||||
}
|
||||
|
||||
func testClose(a *assert.Assertions, store queue.Store) {
|
||||
t := time.After(2 * time.Second)
|
||||
result := make(chan struct {
|
||||
len int
|
||||
err error
|
||||
})
|
||||
go func() {
|
||||
// should block
|
||||
rs, err := store.Read([]packets.PacketID{1, 2, 3})
|
||||
result <- struct {
|
||||
len int
|
||||
err error
|
||||
}{len: len(rs), err: err}
|
||||
}()
|
||||
select {
|
||||
case <-result:
|
||||
a.Fail("Read must be blocked before Close")
|
||||
case <-t:
|
||||
}
|
||||
a.NoError(store.Close())
|
||||
timeout := time.After(5 * time.Second)
|
||||
select {
|
||||
case <-timeout:
|
||||
a.Fail("Read must be unblocked after Close")
|
||||
case r := <-result:
|
||||
a.Zero(r.len)
|
||||
a.Equal(queue.ErrClosed, r.err)
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
package persistence
|
||||
|
||||
import (
|
||||
redigo "github.com/gomodule/redigo/redis"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue"
|
||||
redis_queue "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/queue/redis"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session"
|
||||
redis_sess "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session/redis"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
redis_sub "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription/redis"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack"
|
||||
redis_unack "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack/redis"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
func init() {
|
||||
server.RegisterPersistenceFactory("redis", NewRedis)
|
||||
}
|
||||
|
||||
func NewRedis(config config.Config) (server.Persistence, error) {
|
||||
return &redis{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type redis struct {
|
||||
pool *redigo.Pool
|
||||
config config.Config
|
||||
onMsgDropped server.OnMsgDropped
|
||||
}
|
||||
|
||||
func (r *redis) NewUnackStore(config config.Config, clientID string) (unack.Store, error) {
|
||||
return redis_unack.New(redis_unack.Options{
|
||||
ClientID: clientID,
|
||||
Pool: r.pool,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (r *redis) NewSessionStore(config config.Config) (session.Store, error) {
|
||||
return redis_sess.New(r.pool), nil
|
||||
}
|
||||
|
||||
func newPool(config config.Config) *redigo.Pool {
|
||||
return &redigo.Pool{
|
||||
// Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial.
|
||||
Dial: func() (redigo.Conn, error) {
|
||||
c, err := redigo.Dial("tcp", config.Persistence.Redis.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pswd := config.Persistence.Redis.Password; pswd != "" {
|
||||
if _, err := c.Do("AUTH", pswd); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err := c.Do("SELECT", config.Persistence.Redis.Database); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
func (r *redis) Open() error {
|
||||
r.pool = newPool(r.config)
|
||||
r.pool.MaxIdle = int(*r.config.Persistence.Redis.MaxIdle)
|
||||
r.pool.MaxActive = int(*r.config.Persistence.Redis.MaxActive)
|
||||
r.pool.IdleTimeout = r.config.Persistence.Redis.IdleTimeout
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
// Test the connection
|
||||
_, err := conn.Do("PING")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *redis) NewQueueStore(config config.Config, defaultNotifier queue.Notifier, clientID string) (queue.Store, error) {
|
||||
return redis_queue.New(redis_queue.Options{
|
||||
MaxQueuedMsg: config.MQTT.MaxQueuedMsg,
|
||||
InflightExpiry: config.MQTT.InflightExpiry,
|
||||
ClientID: clientID,
|
||||
Pool: r.pool,
|
||||
DefaultNotifier: defaultNotifier,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *redis) NewSubscriptionStore(config config.Config) (subscription.Store, error) {
|
||||
return redis_sub.New(r.pool), nil
|
||||
}
|
||||
|
||||
func (r *redis) Close() error {
|
||||
return r.pool.Close()
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package mem
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session"
|
||||
)
|
||||
|
||||
var _ session.Store = (*Store)(nil)
|
||||
|
||||
func New() *Store {
|
||||
return &Store{
|
||||
mu: sync.Mutex{},
|
||||
sess: make(map[string]*gmqtt.Session),
|
||||
}
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
mu sync.Mutex
|
||||
sess map[string]*gmqtt.Session
|
||||
}
|
||||
|
||||
func (s *Store) Set(session *gmqtt.Session) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.sess[session.ClientID] = session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Remove(clientID string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.sess, clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Get(clientID string) (*gmqtt.Session, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.sess[clientID], nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAll() ([]*gmqtt.Session, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetSessionExpiry(clientID string, expiry uint32) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s, ok := s.sess[clientID]; ok {
|
||||
s.ExpiryInterval = expiry
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Iterate(fn session.IterateFn) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, v := range s.sess {
|
||||
cont := fn(v)
|
||||
if !cont {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/encoding"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session"
|
||||
)
|
||||
|
||||
const (
|
||||
sessPrefix = "session:"
|
||||
)
|
||||
|
||||
var _ session.Store = (*Store)(nil)
|
||||
|
||||
type Store struct {
|
||||
mu sync.Mutex
|
||||
pool *redis.Pool
|
||||
}
|
||||
|
||||
func New(pool *redis.Pool) *Store {
|
||||
return &Store{
|
||||
mu: sync.Mutex{},
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
func getKey(clientID string) string {
|
||||
return sessPrefix + clientID
|
||||
}
|
||||
func (s *Store) Set(session *gmqtt.Session) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
b := &bytes.Buffer{}
|
||||
encoding.EncodeMessage(session.Will, b)
|
||||
_, err := c.Do("hset", getKey(session.ClientID),
|
||||
"client_id", session.ClientID,
|
||||
"will", b.Bytes(),
|
||||
"will_delay_interval", session.WillDelayInterval,
|
||||
"connected_at", session.ConnectedAt.Unix(),
|
||||
"expiry_interval", session.ExpiryInterval,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) Remove(clientID string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
_, err := c.Do("del", getKey(clientID))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) Get(clientID string) (*gmqtt.Session, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
return getSessionLocked(getKey(clientID), c)
|
||||
}
|
||||
|
||||
func getSessionLocked(key string, c redis.Conn) (*gmqtt.Session, error) {
|
||||
replay, err := redis.Values(c.Do("hmget", key, "client_id", "will", "will_delay_interval", "connected_at", "expiry_interval"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sess := &gmqtt.Session{}
|
||||
var connectedAt uint32
|
||||
var will []byte
|
||||
_, err = redis.Scan(replay, &sess.ClientID, &will, &sess.WillDelayInterval, &connectedAt, &sess.ExpiryInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sess.ConnectedAt = time.Unix(int64(connectedAt), 0)
|
||||
sess.Will, err = encoding.DecodeMessageFromBytes(will)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetSessionExpiry(clientID string, expiry uint32) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
_, err := c.Do("hset", getKey(clientID),
|
||||
"expiry_interval", expiry,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) Iterate(fn session.IterateFn) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
iter := 0
|
||||
for {
|
||||
arr, err := redis.Values(c.Do("SCAN", iter, "MATCH", sessPrefix+"*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(arr) >= 1 {
|
||||
for _, v := range arr[1:] {
|
||||
for _, vv := range v.([]interface{}) {
|
||||
sess, err := getSessionLocked(string(vv.([]uint8)), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cont := fn(sess)
|
||||
if !cont {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iter, _ = redis.Int(arr[0], nil)
|
||||
if iter == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package session
|
||||
|
||||
import gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
|
||||
// IterateFn is the callback function used by Iterate()
|
||||
// Return false means to stop the iteration.
|
||||
type IterateFn func(session *gmqtt.Session) bool
|
||||
|
||||
type Store interface {
|
||||
Set(session *gmqtt.Session) error
|
||||
Remove(clientID string) error
|
||||
Get(clientID string) (*gmqtt.Session, error)
|
||||
Iterate(fn IterateFn) error
|
||||
SetSessionExpiry(clientID string, expiry uint32) error
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: persistence/session/session.go
|
||||
|
||||
// Package session is a generated GoMock package.
|
||||
package session
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
)
|
||||
|
||||
// MockStore is a mock of Store interface
|
||||
type MockStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStoreMockRecorder
|
||||
}
|
||||
|
||||
// MockStoreMockRecorder is the mock recorder for MockStore
|
||||
type MockStoreMockRecorder struct {
|
||||
mock *MockStore
|
||||
}
|
||||
|
||||
// NewMockStore creates a new mock instance
|
||||
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||
mock := &MockStore{ctrl: ctrl}
|
||||
mock.recorder = &MockStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Set mocks base method
|
||||
func (m *MockStore) Set(session *gmqtt.Session) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Set", session)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set
|
||||
func (mr *MockStoreMockRecorder) Set(session interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockStore)(nil).Set), session)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockStore) Remove(clientID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Remove", clientID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockStoreMockRecorder) Remove(clientID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStore)(nil).Remove), clientID)
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockStore) Get(clientID string) (*gmqtt.Session, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", clientID)
|
||||
ret0, _ := ret[0].(*gmqtt.Session)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockStoreMockRecorder) Get(clientID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStore)(nil).Get), clientID)
|
||||
}
|
||||
|
||||
// Iterate mocks base method
|
||||
func (m *MockStore) Iterate(fn IterateFn) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Iterate", fn)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Iterate indicates an expected call of Iterate
|
||||
func (mr *MockStoreMockRecorder) Iterate(fn interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockStore)(nil).Iterate), fn)
|
||||
}
|
||||
|
||||
// SetSessionExpiry mocks base method
|
||||
func (m *MockStore) SetSessionExpiry(clientID string, expiry uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetSessionExpiry", clientID, expiry)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetSessionExpiry indicates an expected call of SetSessionExpiry
|
||||
func (mr *MockStoreMockRecorder) SetSessionExpiry(clientID, expiry interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSessionExpiry", reflect.TypeOf((*MockStore)(nil).SetSessionExpiry), clientID, expiry)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/session"
|
||||
)
|
||||
|
||||
func TestSuite(t *testing.T, store session.Store) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
var tt = []*gmqtt.Session{
|
||||
{
|
||||
ClientID: "client",
|
||||
Will: &gmqtt.Message{
|
||||
Topic: "topicA",
|
||||
Payload: []byte("abc"),
|
||||
},
|
||||
WillDelayInterval: 1,
|
||||
ConnectedAt: time.Unix(1, 0),
|
||||
ExpiryInterval: 2,
|
||||
}, {
|
||||
ClientID: "client2",
|
||||
Will: nil,
|
||||
WillDelayInterval: 0,
|
||||
ConnectedAt: time.Unix(2, 0),
|
||||
ExpiryInterval: 0,
|
||||
},
|
||||
}
|
||||
for _, v := range tt {
|
||||
a.Nil(store.Set(v))
|
||||
}
|
||||
for _, v := range tt {
|
||||
sess, err := store.Get(v.ClientID)
|
||||
a.Nil(err)
|
||||
a.EqualValues(v, sess)
|
||||
}
|
||||
var sess []*gmqtt.Session
|
||||
err := store.Iterate(func(session *gmqtt.Session) bool {
|
||||
sess = append(sess, session)
|
||||
return true
|
||||
})
|
||||
a.Nil(err)
|
||||
a.ElementsMatch(sess, tt)
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
package mem
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
)
|
||||
|
||||
// topicTrie
|
||||
type topicTrie = topicNode
|
||||
|
||||
// children
|
||||
type children = map[string]*topicNode
|
||||
|
||||
type clientOpts map[string]*gmqtt.Subscription
|
||||
|
||||
// topicNode
|
||||
type topicNode struct {
|
||||
children children
|
||||
// clients store non-share subscription
|
||||
clients clientOpts
|
||||
parent *topicNode // pointer of parent node
|
||||
topicName string
|
||||
// shared store shared subscription, key by ShareName
|
||||
shared map[string]clientOpts
|
||||
}
|
||||
|
||||
// newTopicTrie create a new trie tree
|
||||
func newTopicTrie() *topicTrie {
|
||||
return newNode()
|
||||
}
|
||||
|
||||
// newNode create a new trie node
|
||||
func newNode() *topicNode {
|
||||
return &topicNode{
|
||||
children: children{},
|
||||
clients: make(clientOpts),
|
||||
shared: make(map[string]clientOpts),
|
||||
}
|
||||
}
|
||||
|
||||
// newChild create a child node of t
|
||||
func (t *topicNode) newChild() *topicNode {
|
||||
n := newNode()
|
||||
n.parent = t
|
||||
return n
|
||||
}
|
||||
|
||||
// subscribe add a subscription and return the added node
|
||||
func (t *topicTrie) subscribe(clientID string, s *gmqtt.Subscription) *topicNode {
|
||||
topicSlice := strings.Split(s.TopicFilter, "/")
|
||||
var pNode = t
|
||||
for _, lv := range topicSlice {
|
||||
if _, ok := pNode.children[lv]; !ok {
|
||||
pNode.children[lv] = pNode.newChild()
|
||||
}
|
||||
pNode = pNode.children[lv]
|
||||
}
|
||||
// shared subscription
|
||||
if s.ShareName != "" {
|
||||
if pNode.shared[s.ShareName] == nil {
|
||||
pNode.shared[s.ShareName] = make(clientOpts)
|
||||
}
|
||||
pNode.shared[s.ShareName][clientID] = s
|
||||
} else {
|
||||
// non-shared
|
||||
pNode.clients[clientID] = s
|
||||
}
|
||||
pNode.topicName = s.TopicFilter
|
||||
return pNode
|
||||
}
|
||||
|
||||
// find walk through the tire and return the node that represent the topicFilter.
|
||||
// Return nil if not found
|
||||
func (t *topicTrie) find(topicFilter string) *topicNode {
|
||||
topicSlice := strings.Split(topicFilter, "/")
|
||||
var pNode = t
|
||||
for _, lv := range topicSlice {
|
||||
if _, ok := pNode.children[lv]; ok {
|
||||
pNode = pNode.children[lv]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if pNode.topicName == topicFilter {
|
||||
return pNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unsubscribe
|
||||
func (t *topicTrie) unsubscribe(clientID string, topicName string, shareName string) {
|
||||
topicSlice := strings.Split(topicName, "/")
|
||||
l := len(topicSlice)
|
||||
var pNode = t
|
||||
for _, lv := range topicSlice {
|
||||
if _, ok := pNode.children[lv]; ok {
|
||||
pNode = pNode.children[lv]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if shareName != "" {
|
||||
if c := pNode.shared[shareName]; c != nil {
|
||||
delete(c, clientID)
|
||||
if len(pNode.shared[shareName]) == 0 {
|
||||
delete(pNode.shared, shareName)
|
||||
}
|
||||
if len(pNode.shared) == 0 && len(pNode.children) == 0 {
|
||||
delete(pNode.parent.children, topicSlice[l-1])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete(pNode.clients, clientID)
|
||||
if len(pNode.clients) == 0 && len(pNode.children) == 0 {
|
||||
delete(pNode.parent.children, topicSlice[l-1])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// setRs set the node subscription info into rs
|
||||
func setRs(node *topicNode, rs subscription.ClientSubscriptions) {
|
||||
for cid, subOpts := range node.clients {
|
||||
rs[cid] = append(rs[cid], subOpts)
|
||||
}
|
||||
|
||||
for _, c := range node.shared {
|
||||
for cid, subOpts := range c {
|
||||
rs[cid] = append(rs[cid], subOpts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matchTopic get all matched topic for given topicSlice, and set into rs
|
||||
func (t *topicTrie) matchTopic(topicSlice []string, rs subscription.ClientSubscriptions) {
|
||||
endFlag := len(topicSlice) == 1
|
||||
if cnode := t.children["#"]; cnode != nil {
|
||||
setRs(cnode, rs)
|
||||
}
|
||||
if cnode := t.children["+"]; cnode != nil {
|
||||
if endFlag {
|
||||
setRs(cnode, rs)
|
||||
if n := cnode.children["#"]; n != nil {
|
||||
setRs(n, rs)
|
||||
}
|
||||
} else {
|
||||
cnode.matchTopic(topicSlice[1:], rs)
|
||||
}
|
||||
}
|
||||
if cnode := t.children[topicSlice[0]]; cnode != nil {
|
||||
if endFlag {
|
||||
setRs(cnode, rs)
|
||||
if n := cnode.children["#"]; n != nil {
|
||||
setRs(n, rs)
|
||||
}
|
||||
} else {
|
||||
cnode.matchTopic(topicSlice[1:], rs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getMatchedTopicFilter return a map key by clientID that contain all matched topic for the given topicName.
|
||||
func (t *topicTrie) getMatchedTopicFilter(topicName string) subscription.ClientSubscriptions {
|
||||
topicLv := strings.Split(topicName, "/")
|
||||
subs := make(subscription.ClientSubscriptions)
|
||||
t.matchTopic(topicLv, subs)
|
||||
return subs
|
||||
}
|
||||
|
||||
func isSystemTopic(topicName string) bool {
|
||||
return len(topicName) >= 1 && topicName[0] == '$'
|
||||
}
|
||||
|
||||
func (t *topicTrie) preOrderTraverse(fn subscription.IterateFn) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
if t.topicName != "" {
|
||||
for clientID, subOpts := range t.clients {
|
||||
if !fn(clientID, subOpts) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range t.shared {
|
||||
for clientID, subOpts := range c {
|
||||
if !fn(clientID, subOpts) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range t.children {
|
||||
if !c.preOrderTraverse(fn) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
package mem
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var testTopicMatch = []struct {
|
||||
subTopic string //subscribe topic
|
||||
topic string //publish topic
|
||||
isMatch bool
|
||||
}{
|
||||
{subTopic: "#", topic: "/abc/def", isMatch: true},
|
||||
{subTopic: "/a", topic: "a", isMatch: false},
|
||||
{subTopic: "a/#", topic: "a", isMatch: true},
|
||||
{subTopic: "+", topic: "/a", isMatch: false},
|
||||
|
||||
{subTopic: "a/", topic: "a", isMatch: false},
|
||||
{subTopic: "a/+", topic: "a/123/4", isMatch: false},
|
||||
{subTopic: "a/#", topic: "a/123/4", isMatch: true},
|
||||
|
||||
{subTopic: "/a/+/+/abcd", topic: "/a/dfdf/3434/abcd", isMatch: true},
|
||||
{subTopic: "/a/+/+/abcd", topic: "/a/dfdf/3434/abcdd", isMatch: false},
|
||||
{subTopic: "/a/+/abc/", topic: "/a/dfdf/abc/", isMatch: true},
|
||||
{subTopic: "/a/+/abc/", topic: "/a/dfdf/abc", isMatch: false},
|
||||
{subTopic: "/a/+/+/", topic: "/a/dfdf/", isMatch: false},
|
||||
{subTopic: "/a/+/+", topic: "/a/dfdf/", isMatch: true},
|
||||
{subTopic: "/a/+/+/#", topic: "/a/dfdf/", isMatch: true},
|
||||
}
|
||||
|
||||
var topicMatchQosTest = []struct {
|
||||
topics []packets.Topic
|
||||
matchTopic struct {
|
||||
name string // matched topic name
|
||||
qos uint8 // matched qos
|
||||
}
|
||||
}{
|
||||
{
|
||||
topics: []packets.Topic{
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos1,
|
||||
},
|
||||
Name: "a/b",
|
||||
},
|
||||
{
|
||||
Name: "a/#",
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "a/+",
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos0,
|
||||
},
|
||||
},
|
||||
},
|
||||
matchTopic: struct {
|
||||
name string
|
||||
qos uint8
|
||||
}{
|
||||
name: "a/b",
|
||||
qos: packets.Qos2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testSubscribeAndFind = struct {
|
||||
subTopics map[string][]packets.Topic // subscription
|
||||
findTopics map[string][]struct { //key by clientID
|
||||
exist bool
|
||||
topicName string
|
||||
wantQos uint8
|
||||
}
|
||||
}{
|
||||
subTopics: map[string][]packets.Topic{
|
||||
"cid1": {
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos1,
|
||||
}, Name: "t1/t2/+"},
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos2,
|
||||
}, Name: "t1/t2/"},
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos0,
|
||||
}, Name: "t1/t2/cid1"},
|
||||
},
|
||||
"cid2": {
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos2,
|
||||
}, Name: "t1/t2/+"},
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos1,
|
||||
}, Name: "t1/t2/"},
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos0,
|
||||
}, Name: "t1/t2/cid2"},
|
||||
},
|
||||
},
|
||||
findTopics: map[string][]struct { //key by clientID
|
||||
exist bool
|
||||
topicName string
|
||||
wantQos uint8
|
||||
}{
|
||||
"cid1": {
|
||||
{exist: true, topicName: "t1/t2/+", wantQos: packets.Qos1},
|
||||
{exist: true, topicName: "t1/t2/", wantQos: packets.Qos2},
|
||||
{exist: false, topicName: "t1/t2/cid2"},
|
||||
{exist: false, topicName: "t1/t2/cid3"},
|
||||
},
|
||||
"cid2": {
|
||||
{exist: true, topicName: "t1/t2/+", wantQos: packets.Qos2},
|
||||
{exist: true, topicName: "t1/t2/", wantQos: packets.Qos1},
|
||||
{exist: false, topicName: "t1/t2/cid1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testUnsubscribe = struct {
|
||||
subTopics map[string][]packets.Topic //key by clientID
|
||||
unsubscribe map[string][]string // clientID => topic name
|
||||
afterUnsub map[string][]struct { // test after unsubscribe, key by clientID
|
||||
exist bool
|
||||
topicName string
|
||||
wantQos uint8
|
||||
}
|
||||
}{
|
||||
subTopics: map[string][]packets.Topic{
|
||||
"cid1": {
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos1,
|
||||
}, Name: "t1/t2/t3"},
|
||||
{SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos2,
|
||||
}, Name: "t1/t2"},
|
||||
},
|
||||
"cid2": {
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos2,
|
||||
},
|
||||
Name: "t1/t2/t3"},
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos1,
|
||||
}, Name: "t1/t2"},
|
||||
},
|
||||
},
|
||||
unsubscribe: map[string][]string{
|
||||
"cid1": {"t1/t2/t3", "t4/t5"},
|
||||
"cid2": {"t1/t2/t3"},
|
||||
},
|
||||
afterUnsub: map[string][]struct { // test after unsubscribe
|
||||
exist bool
|
||||
topicName string
|
||||
wantQos uint8
|
||||
}{
|
||||
"cid1": {
|
||||
{exist: false, topicName: "t1/t2/t3"},
|
||||
{exist: true, topicName: "t1/t2", wantQos: packets.Qos2},
|
||||
},
|
||||
"cid2": {
|
||||
{exist: false, topicName: "t1/t2/+"},
|
||||
{exist: true, topicName: "t1/t2", wantQos: packets.Qos1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testPreOrderTraverse = struct {
|
||||
topics []packets.Topic
|
||||
clientID string
|
||||
}{
|
||||
topics: []packets.Topic{
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos0,
|
||||
},
|
||||
Name: "a/b/c",
|
||||
},
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos1,
|
||||
},
|
||||
Name: "/a/b/c",
|
||||
},
|
||||
{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: packets.Qos2,
|
||||
},
|
||||
Name: "b/c/d",
|
||||
},
|
||||
},
|
||||
clientID: "abc",
|
||||
}
|
||||
|
||||
func TestTopicTrie_matchedClients(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
for _, v := range testTopicMatch {
|
||||
trie := newTopicTrie()
|
||||
trie.subscribe("cid", &gmqtt.Subscription{
|
||||
TopicFilter: v.subTopic,
|
||||
})
|
||||
qos := trie.getMatchedTopicFilter(v.topic)
|
||||
if v.isMatch {
|
||||
a.EqualValues(qos["cid"][0].QoS, 0, v.subTopic)
|
||||
} else {
|
||||
_, ok := qos["cid"]
|
||||
a.False(ok, v.subTopic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicTrie_matchedClients_Qos(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
for _, v := range topicMatchQosTest {
|
||||
trie := newTopicTrie()
|
||||
for _, tt := range v.topics {
|
||||
trie.subscribe("cid", &gmqtt.Subscription{
|
||||
TopicFilter: tt.Name,
|
||||
QoS: tt.Qos,
|
||||
})
|
||||
}
|
||||
rs := trie.getMatchedTopicFilter(v.matchTopic.name)
|
||||
a.EqualValues(v.matchTopic.qos, rs["cid"][0].QoS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicTrie_subscribeAndFind(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
trie := newTopicTrie()
|
||||
for cid, v := range testSubscribeAndFind.subTopics {
|
||||
for _, topic := range v {
|
||||
trie.subscribe(cid, &gmqtt.Subscription{
|
||||
TopicFilter: topic.Name,
|
||||
QoS: topic.Qos,
|
||||
})
|
||||
}
|
||||
}
|
||||
for cid, v := range testSubscribeAndFind.findTopics {
|
||||
for _, tt := range v {
|
||||
node := trie.find(tt.topicName)
|
||||
if tt.exist {
|
||||
a.Equal(tt.wantQos, node.clients[cid].QoS)
|
||||
} else {
|
||||
if node != nil {
|
||||
_, ok := node.clients[cid]
|
||||
a.False(ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicTrie_unsubscribe(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
trie := newTopicTrie()
|
||||
for cid, v := range testUnsubscribe.subTopics {
|
||||
for _, topic := range v {
|
||||
trie.subscribe(cid, &gmqtt.Subscription{
|
||||
TopicFilter: topic.Name,
|
||||
QoS: topic.Qos,
|
||||
})
|
||||
}
|
||||
}
|
||||
for cid, v := range testUnsubscribe.unsubscribe {
|
||||
for _, tt := range v {
|
||||
trie.unsubscribe(cid, tt, "")
|
||||
}
|
||||
}
|
||||
for cid, v := range testUnsubscribe.afterUnsub {
|
||||
for _, tt := range v {
|
||||
matched := trie.getMatchedTopicFilter(tt.topicName)
|
||||
if tt.exist {
|
||||
a.EqualValues(matched[cid][0].QoS, tt.wantQos)
|
||||
} else {
|
||||
a.Equal(0, len(matched))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicTrie_preOrderTraverse(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
trie := newTopicTrie()
|
||||
for _, v := range testPreOrderTraverse.topics {
|
||||
trie.subscribe(testPreOrderTraverse.clientID, &gmqtt.Subscription{
|
||||
TopicFilter: v.Name,
|
||||
QoS: v.Qos,
|
||||
})
|
||||
}
|
||||
var rs []packets.Topic
|
||||
trie.preOrderTraverse(func(clientID string, subscription *gmqtt.Subscription) bool {
|
||||
a.Equal(testPreOrderTraverse.clientID, clientID)
|
||||
rs = append(rs, packets.Topic{
|
||||
SubOptions: packets.SubOptions{
|
||||
Qos: subscription.QoS,
|
||||
},
|
||||
Name: subscription.TopicFilter,
|
||||
})
|
||||
return true
|
||||
})
|
||||
a.ElementsMatch(testPreOrderTraverse.topics, rs)
|
||||
}
|
|
@ -1,385 +0,0 @@
|
|||
package mem
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
)
|
||||
|
||||
var _ subscription.Store = (*TrieDB)(nil)
|
||||
|
||||
// TrieDB implement the subscription.Interface, it use trie tree to store topics.
|
||||
type TrieDB struct {
|
||||
sync.RWMutex
|
||||
userIndex map[string]map[string]*topicNode // [clientID][topicFilter]
|
||||
userTrie *topicTrie
|
||||
|
||||
// system topic which begin with "$"
|
||||
systemIndex map[string]map[string]*topicNode // [clientID][topicFilter]
|
||||
systemTrie *topicTrie
|
||||
|
||||
// shared subscription which begin with "$share"
|
||||
sharedIndex map[string]map[string]*topicNode // [clientID][shareName/topicFilter]
|
||||
sharedTrie *topicTrie
|
||||
|
||||
// statistics of the server and each client
|
||||
stats subscription.Stats
|
||||
clientStats map[string]*subscription.Stats // [clientID]
|
||||
|
||||
}
|
||||
|
||||
func (db *TrieDB) Init(clientIDs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *TrieDB) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func iterateShared(fn subscription.IterateFn, options subscription.IterationOptions, index map[string]map[string]*topicNode, trie *topicTrie) bool {
|
||||
// 查询指定topicFilter
|
||||
if options.TopicName != "" && options.MatchType == subscription.MatchName { //寻找指定topicName
|
||||
var shareName string
|
||||
var topicFilter string
|
||||
if strings.HasPrefix(options.TopicName, "$share/") {
|
||||
shared := strings.SplitN(options.TopicName, "/", 3)
|
||||
shareName = shared[1]
|
||||
topicFilter = shared[2]
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
node := trie.find(topicFilter)
|
||||
if node == nil {
|
||||
return true
|
||||
}
|
||||
if options.ClientID != "" { // 指定topicName & 指定clientID
|
||||
if c := node.shared[shareName]; c != nil {
|
||||
if sub, ok := c[options.ClientID]; ok {
|
||||
if !fn(options.ClientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if c := node.shared[shareName]; c != nil {
|
||||
for clientID, sub := range c {
|
||||
if !fn(clientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 查询Match指定topicFilter
|
||||
if options.TopicName != "" && options.MatchType == subscription.MatchFilter { // match指定的topicfilter
|
||||
node := trie.getMatchedTopicFilter(options.TopicName)
|
||||
if node == nil {
|
||||
return true
|
||||
}
|
||||
if options.ClientID != "" {
|
||||
for _, v := range node[options.ClientID] {
|
||||
if !fn(options.ClientID, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for clientID, subs := range node {
|
||||
for _, v := range subs {
|
||||
if !fn(clientID, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 查询指定clientID下的所有topic
|
||||
if options.ClientID != "" {
|
||||
for _, v := range index[options.ClientID] {
|
||||
for _, c := range v.shared {
|
||||
if sub, ok := c[options.ClientID]; ok {
|
||||
if !fn(options.ClientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 遍历
|
||||
return trie.preOrderTraverse(fn)
|
||||
}
|
||||
|
||||
func iterateNonShared(fn subscription.IterateFn, options subscription.IterationOptions, index map[string]map[string]*topicNode, trie *topicTrie) bool {
|
||||
// 查询指定topicFilter
|
||||
if options.TopicName != "" && options.MatchType == subscription.MatchName { //寻找指定topicName
|
||||
node := trie.find(options.TopicName)
|
||||
if node == nil {
|
||||
return true
|
||||
}
|
||||
if options.ClientID != "" { // 指定topicName & 指定clientID
|
||||
if sub, ok := node.clients[options.ClientID]; ok {
|
||||
if !fn(options.ClientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range node.shared {
|
||||
if sub, ok := v[options.ClientID]; ok {
|
||||
if !fn(options.ClientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 指定topic name 不指定clientid
|
||||
for clientID, sub := range node.clients {
|
||||
if !fn(clientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, c := range node.shared {
|
||||
for clientID, sub := range c {
|
||||
if !fn(clientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 查询Match指定topicFilter
|
||||
if options.TopicName != "" && options.MatchType == subscription.MatchFilter { // match指定的topicfilter
|
||||
node := trie.getMatchedTopicFilter(options.TopicName)
|
||||
if node == nil {
|
||||
return true
|
||||
}
|
||||
if options.ClientID != "" {
|
||||
for _, v := range node[options.ClientID] {
|
||||
if !fn(options.ClientID, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for clientID, subs := range node {
|
||||
for _, v := range subs {
|
||||
if !fn(clientID, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 查询指定clientID下的所有topic
|
||||
if options.ClientID != "" {
|
||||
for _, v := range index[options.ClientID] {
|
||||
sub := v.clients[options.ClientID]
|
||||
if !fn(options.ClientID, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 遍历
|
||||
return trie.preOrderTraverse(fn)
|
||||
|
||||
}
|
||||
|
||||
// IterateLocked is the non thread-safe version of Iterate
|
||||
func (db *TrieDB) IterateLocked(fn subscription.IterateFn, options subscription.IterationOptions) {
|
||||
if options.Type&subscription.TypeShared == subscription.TypeShared {
|
||||
if !iterateShared(fn, options, db.sharedIndex, db.sharedTrie) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if options.Type&subscription.TypeNonShared == subscription.TypeNonShared {
|
||||
// The Server MUST NOT match Topic Filters starting with a wildcard character (# or +) with Topic Names beginning with a $ character [MQTT-4.7.2-1]
|
||||
if !(options.TopicName != "" && isSystemTopic(options.TopicName)) {
|
||||
if !iterateNonShared(fn, options, db.userIndex, db.userTrie) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if options.Type&subscription.TypeSYS == subscription.TypeSYS {
|
||||
if options.TopicName != "" && !isSystemTopic(options.TopicName) {
|
||||
return
|
||||
}
|
||||
if !iterateNonShared(fn, options, db.systemIndex, db.systemTrie) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func (db *TrieDB) Iterate(fn subscription.IterateFn, options subscription.IterationOptions) {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
db.IterateLocked(fn, options)
|
||||
}
|
||||
|
||||
// GetStats is the non thread-safe version of GetStats
|
||||
func (db *TrieDB) GetStatusLocked() subscription.Stats {
|
||||
return db.stats
|
||||
}
|
||||
|
||||
// GetStats returns the statistic information of the store
|
||||
func (db *TrieDB) GetStats() subscription.Stats {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
return db.GetStatusLocked()
|
||||
}
|
||||
|
||||
// GetClientStatsLocked the non thread-safe version of GetClientStats
|
||||
func (db *TrieDB) GetClientStatsLocked(clientID string) (subscription.Stats, error) {
|
||||
if stats, ok := db.clientStats[clientID]; !ok {
|
||||
return subscription.Stats{}, subscription.ErrClientNotExists
|
||||
} else {
|
||||
return *stats, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *TrieDB) GetClientStats(clientID string) (subscription.Stats, error) {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
return db.GetClientStatsLocked(clientID)
|
||||
}
|
||||
|
||||
// NewStore create a new TrieDB instance
|
||||
func NewStore() *TrieDB {
|
||||
return &TrieDB{
|
||||
userIndex: make(map[string]map[string]*topicNode),
|
||||
userTrie: newTopicTrie(),
|
||||
|
||||
systemIndex: make(map[string]map[string]*topicNode),
|
||||
systemTrie: newTopicTrie(),
|
||||
|
||||
sharedIndex: make(map[string]map[string]*topicNode),
|
||||
sharedTrie: newTopicTrie(),
|
||||
|
||||
clientStats: make(map[string]*subscription.Stats),
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeLocked is the non thread-safe version of Subscribe
|
||||
func (db *TrieDB) SubscribeLocked(clientID string, subscriptions ...*gmqtt.Subscription) subscription.SubscribeResult {
|
||||
var node *topicNode
|
||||
var index map[string]map[string]*topicNode
|
||||
rs := make(subscription.SubscribeResult, len(subscriptions))
|
||||
for k, sub := range subscriptions {
|
||||
topicName := sub.TopicFilter
|
||||
rs[k].Subscription = sub
|
||||
if sub.ShareName != "" {
|
||||
node = db.sharedTrie.subscribe(clientID, sub)
|
||||
index = db.sharedIndex
|
||||
} else if isSystemTopic(topicName) {
|
||||
node = db.systemTrie.subscribe(clientID, sub)
|
||||
index = db.systemIndex
|
||||
} else {
|
||||
node = db.userTrie.subscribe(clientID, sub)
|
||||
index = db.userIndex
|
||||
}
|
||||
if index[clientID] == nil {
|
||||
index[clientID] = make(map[string]*topicNode)
|
||||
if db.clientStats[clientID] == nil {
|
||||
db.clientStats[clientID] = &subscription.Stats{}
|
||||
}
|
||||
}
|
||||
if _, ok := index[clientID][topicName]; !ok {
|
||||
db.stats.SubscriptionsTotal++
|
||||
db.stats.SubscriptionsCurrent++
|
||||
db.clientStats[clientID].SubscriptionsTotal++
|
||||
db.clientStats[clientID].SubscriptionsCurrent++
|
||||
} else {
|
||||
rs[k].AlreadyExisted = true
|
||||
}
|
||||
index[clientID][topicName] = node
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// SubscribeLocked add subscriptions for the client
|
||||
func (db *TrieDB) Subscribe(clientID string, subscriptions ...*gmqtt.Subscription) (subscription.SubscribeResult, error) {
|
||||
db.Lock()
|
||||
defer db.Unlock()
|
||||
return db.SubscribeLocked(clientID, subscriptions...), nil
|
||||
}
|
||||
|
||||
// UnsubscribeLocked is the non thread-safe version of Unsubscribe
|
||||
func (db *TrieDB) UnsubscribeLocked(clientID string, topics ...string) {
|
||||
var index map[string]map[string]*topicNode
|
||||
var topicTrie *topicTrie
|
||||
for _, topic := range topics {
|
||||
var shareName string
|
||||
shareName, topic := subscription.SplitTopic(topic)
|
||||
if shareName != "" {
|
||||
topicTrie = db.sharedTrie
|
||||
index = db.sharedIndex
|
||||
} else if isSystemTopic(topic) {
|
||||
index = db.systemIndex
|
||||
topicTrie = db.systemTrie
|
||||
} else {
|
||||
index = db.userIndex
|
||||
topicTrie = db.userTrie
|
||||
}
|
||||
if _, ok := index[clientID]; ok {
|
||||
if _, ok := index[clientID][topic]; ok {
|
||||
db.stats.SubscriptionsCurrent--
|
||||
db.clientStats[clientID].SubscriptionsCurrent--
|
||||
}
|
||||
delete(index[clientID], topic)
|
||||
}
|
||||
topicTrie.unsubscribe(clientID, topic, shareName)
|
||||
}
|
||||
}
|
||||
|
||||
// Unsubscribe remove subscriptions for the client
|
||||
func (db *TrieDB) Unsubscribe(clientID string, topics ...string) error {
|
||||
db.Lock()
|
||||
defer db.Unlock()
|
||||
db.UnsubscribeLocked(clientID, topics...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *TrieDB) unsubscribeAll(index map[string]map[string]*topicNode, clientID string) {
|
||||
db.stats.SubscriptionsCurrent -= uint64(len(index[clientID]))
|
||||
if db.clientStats[clientID] != nil {
|
||||
db.clientStats[clientID].SubscriptionsCurrent -= uint64(len(index[clientID]))
|
||||
}
|
||||
for topicName, node := range index[clientID] {
|
||||
delete(node.clients, clientID)
|
||||
if len(node.clients) == 0 && len(node.children) == 0 {
|
||||
ss := strings.Split(topicName, "/")
|
||||
delete(node.parent.children, ss[len(ss)-1])
|
||||
}
|
||||
}
|
||||
delete(index, clientID)
|
||||
}
|
||||
|
||||
// UnsubscribeAllLocked is the non thread-safe version of UnsubscribeAll
|
||||
func (db *TrieDB) UnsubscribeAllLocked(clientID string) {
|
||||
db.unsubscribeAll(db.userIndex, clientID)
|
||||
db.unsubscribeAll(db.systemIndex, clientID)
|
||||
db.unsubscribeAll(db.sharedIndex, clientID)
|
||||
}
|
||||
|
||||
// UnsubscribeAll delete all subscriptions of the client
|
||||
func (db *TrieDB) UnsubscribeAll(clientID string) error {
|
||||
db.Lock()
|
||||
defer db.Unlock()
|
||||
// user topics
|
||||
db.UnsubscribeAllLocked(clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMatchedTopicFilter return a map key by clientID that contain all matched topic for the given topicName.
|
||||
func (db *TrieDB) getMatchedTopicFilter(topicName string) subscription.ClientSubscriptions {
|
||||
// system topic
|
||||
if isSystemTopic(topicName) {
|
||||
return db.systemTrie.getMatchedTopicFilter(topicName)
|
||||
}
|
||||
return db.userTrie.getMatchedTopicFilter(topicName)
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
redigo "github.com/gomodule/redigo/redis"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/encoding"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription/mem"
|
||||
)
|
||||
|
||||
const (
|
||||
subPrefix = "sub:"
|
||||
)
|
||||
|
||||
var _ subscription.Store = (*sub)(nil)
|
||||
|
||||
func EncodeSubscription(sub *mqttbroker.Subscription) []byte {
|
||||
w := &bytes.Buffer{}
|
||||
encoding.WriteString(w, []byte(sub.ShareName))
|
||||
encoding.WriteString(w, []byte(sub.TopicFilter))
|
||||
encoding.WriteUint32(w, sub.ID)
|
||||
w.WriteByte(sub.QoS)
|
||||
encoding.WriteBool(w, sub.NoLocal)
|
||||
encoding.WriteBool(w, sub.RetainAsPublished)
|
||||
w.WriteByte(sub.RetainHandling)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
func DecodeSubscription(b []byte) (*gmqtt.Subscription, error) {
|
||||
sub := &gmqtt.Subscription{}
|
||||
r := bytes.NewBuffer(b)
|
||||
share, err := encoding.ReadString(r)
|
||||
if err != nil {
|
||||
return &gmqtt.Subscription{}, err
|
||||
}
|
||||
sub.ShareName = string(share)
|
||||
topic, err := encoding.ReadString(r)
|
||||
if err != nil {
|
||||
return &gmqtt.Subscription{}, err
|
||||
}
|
||||
sub.TopicFilter = string(topic)
|
||||
sub.ID, err = encoding.ReadUint32(r)
|
||||
if err != nil {
|
||||
return &gmqtt.Subscription{}, err
|
||||
}
|
||||
sub.QoS, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return &gmqtt.Subscription{}, err
|
||||
}
|
||||
sub.NoLocal, err = encoding.ReadBool(r)
|
||||
if err != nil {
|
||||
return &gmqtt.Subscription{}, err
|
||||
}
|
||||
sub.RetainAsPublished, err = encoding.ReadBool(r)
|
||||
if err != nil {
|
||||
return &gmqtt.Subscription{}, err
|
||||
}
|
||||
sub.RetainHandling, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func New(pool *redigo.Pool) *sub {
|
||||
return &sub{
|
||||
mu: &sync.Mutex{},
|
||||
memStore: mem.NewStore(),
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
type sub struct {
|
||||
mu *sync.Mutex
|
||||
memStore *mem.TrieDB
|
||||
pool *redigo.Pool
|
||||
}
|
||||
|
||||
// Init loads the subscriptions of given clientIDs from backend into memory.
|
||||
func (s *sub) Init(clientIDs []string) error {
|
||||
if len(clientIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
for _, v := range clientIDs {
|
||||
rs, err := redigo.Values(c.Do("hgetall", subPrefix+v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 1; i < len(rs); i = i + 2 {
|
||||
sub, err := DecodeSubscription(rs[i].([]byte))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.memStore.SubscribeLocked(strings.TrimLeft(v, subPrefix), sub)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sub) Close() error {
|
||||
_ = s.memStore.Close()
|
||||
return s.pool.Close()
|
||||
}
|
||||
|
||||
func (s *sub) Subscribe(clientID string, subscriptions ...*gmqtt.Subscription) (rs subscription.SubscribeResult, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
// hset sub:clientID topicFilter xxx
|
||||
for _, v := range subscriptions {
|
||||
err = c.Send("hset", subPrefix+clientID, subscription.GetFullTopicName(v.ShareName, v.TopicFilter), EncodeSubscription(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = c.Flush()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs = s.memStore.SubscribeLocked(clientID, subscriptions...)
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (s *sub) Unsubscribe(clientID string, topics ...string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
_, err := c.Do("hdel", subPrefix+clientID, topics)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.memStore.UnsubscribeLocked(clientID, topics...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sub) UnsubscribeAll(clientID string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
_, err := c.Do("del", subPrefix+clientID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.memStore.UnsubscribeAllLocked(clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sub) Iterate(fn subscription.IterateFn, options subscription.IterationOptions) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.memStore.IterateLocked(fn, options)
|
||||
}
|
||||
|
||||
func (s *sub) GetStats() subscription.Stats {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.memStore.GetStatusLocked()
|
||||
}
|
||||
|
||||
func (s *sub) GetClientStats(clientID string) (subscription.Stats, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.memStore.GetClientStatsLocked(clientID)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeSubscription(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
tt := []*mqttbroker.Subscription{
|
||||
{
|
||||
ShareName: "shareName",
|
||||
TopicFilter: "filter",
|
||||
ID: 1,
|
||||
QoS: 1,
|
||||
NoLocal: false,
|
||||
RetainAsPublished: false,
|
||||
RetainHandling: 0,
|
||||
}, {
|
||||
ShareName: "",
|
||||
TopicFilter: "abc",
|
||||
ID: 0,
|
||||
QoS: 2,
|
||||
NoLocal: false,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range tt {
|
||||
b := EncodeSubscription(v)
|
||||
sub, err := DecodeSubscription(b)
|
||||
a.Nil(err)
|
||||
a.Equal(v, sub)
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
package subscription
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
// IterationType specifies the types of subscription that will be iterated.
|
||||
type IterationType byte
|
||||
|
||||
const (
|
||||
// TypeSYS represents system topic, which start with '$'.
|
||||
TypeSYS IterationType = 1 << iota
|
||||
// TypeSYS represents shared topic, which start with '$share/'.
|
||||
TypeShared
|
||||
// TypeNonShared represents non-shared topic.
|
||||
TypeNonShared
|
||||
TypeAll = TypeSYS | TypeShared | TypeNonShared
|
||||
)
|
||||
|
||||
var (
|
||||
ErrClientNotExists = errors.New("client not exists")
|
||||
)
|
||||
|
||||
// MatchType specifies what match operation will be performed during the iteration.
|
||||
type MatchType byte
|
||||
|
||||
const (
|
||||
MatchName MatchType = 1 << iota
|
||||
MatchFilter
|
||||
)
|
||||
|
||||
// FromTopic returns the subscription instance for given topic and subscription id.
|
||||
func FromTopic(topic packets.Topic, id uint32) *gmqtt.Subscription {
|
||||
shareName, topicFilter := SplitTopic(topic.Name)
|
||||
s := &gmqtt.Subscription{
|
||||
ShareName: shareName,
|
||||
TopicFilter: topicFilter,
|
||||
ID: id,
|
||||
QoS: topic.Qos,
|
||||
NoLocal: topic.NoLocal,
|
||||
RetainAsPublished: topic.RetainAsPublished,
|
||||
RetainHandling: topic.RetainHandling,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// IterateFn is the callback function used by iterate()
|
||||
// Return false means to stop the iteration.
|
||||
type IterateFn func(clientID string, sub *gmqtt.Subscription) bool
|
||||
|
||||
// SubscribeResult is the result of Subscribe()
|
||||
type SubscribeResult = []struct {
|
||||
// Topic is the Subscribed topic
|
||||
Subscription *gmqtt.Subscription
|
||||
// AlreadyExisted shows whether the topic is already existed.
|
||||
AlreadyExisted bool
|
||||
}
|
||||
|
||||
// Stats is the statistics information of the store
|
||||
type Stats struct {
|
||||
// SubscriptionsTotal shows how many subscription has been added to the store.
|
||||
// Duplicated subscription is not counting.
|
||||
SubscriptionsTotal uint64
|
||||
// SubscriptionsCurrent shows the current subscription number in the store.
|
||||
SubscriptionsCurrent uint64
|
||||
}
|
||||
|
||||
// ClientSubscriptions groups the subscriptions by client id.
|
||||
type ClientSubscriptions map[string][]*gmqtt.Subscription
|
||||
|
||||
// IterationOptions
|
||||
type IterationOptions struct {
|
||||
// Type specifies the types of subscription that will be iterated.
|
||||
// For example, if Type = TypeShared | TypeNonShared , then all shared and non-shared subscriptions will be iterated
|
||||
Type IterationType
|
||||
// ClientID specifies the subscriber client id.
|
||||
ClientID string
|
||||
// TopicName represents topic filter or topic name. This field works together with MatchType.
|
||||
TopicName string
|
||||
// MatchType specifies the matching type of the iteration.
|
||||
// if MatchName, the IterateFn will be called when the subscription topic filter is equal to TopicName.
|
||||
// if MatchTopic, the IterateFn will be called when the TopicName match the subscription topic filter.
|
||||
MatchType MatchType
|
||||
}
|
||||
|
||||
// Store is the interface used by gmqtt.server to handler the operations of subscriptions.
|
||||
// This interface provides the ability for extensions to interact with the subscriptions.
|
||||
// Notice:
|
||||
// This methods will not trigger any gmqtt hooks.
|
||||
type Store interface {
|
||||
// Init will be called only once after the server start, the implementation should load the subscriptions of the given alertclient into memory.
|
||||
Init(clientIDs []string) error
|
||||
// Subscribe adds subscriptions to a specific client.
|
||||
// Notice:
|
||||
// This method will succeed even if the client is not exists, the subscriptions
|
||||
// will affect the new client with the client id.
|
||||
Subscribe(clientID string, subscriptions ...*gmqtt.Subscription) (rs SubscribeResult, err error)
|
||||
// Unsubscribe removes subscriptions of a specific client.
|
||||
Unsubscribe(clientID string, topics ...string) error
|
||||
// UnsubscribeAll removes all subscriptions of a specific client.
|
||||
UnsubscribeAll(clientID string) error
|
||||
// Iterate iterates all subscriptions. The callback is called once for each subscription.
|
||||
// If callback return false, the iteration will be stopped.
|
||||
// Notice:
|
||||
// The results are not sorted in any way, no ordering of any kind is guaranteed.
|
||||
// This method will walk through all subscriptions,
|
||||
// so it is a very expensive operation. Do not call it frequently.
|
||||
Iterate(fn IterateFn, options IterationOptions)
|
||||
|
||||
Close() error
|
||||
StatsReader
|
||||
}
|
||||
|
||||
// GetTopicMatched returns the subscriptions that match the passed topic.
|
||||
func GetTopicMatched(store Store, topicFilter string, t IterationType) ClientSubscriptions {
|
||||
rs := make(ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, subscription *gmqtt.Subscription) bool {
|
||||
rs[clientID] = append(rs[clientID], subscription)
|
||||
return true
|
||||
}, IterationOptions{
|
||||
Type: t,
|
||||
TopicName: topicFilter,
|
||||
MatchType: MatchFilter,
|
||||
})
|
||||
if len(rs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// Get returns the subscriptions that equals the passed topic filter.
|
||||
func Get(store Store, topicFilter string, t IterationType) ClientSubscriptions {
|
||||
rs := make(ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, subscription *gmqtt.Subscription) bool {
|
||||
rs[clientID] = append(rs[clientID], subscription)
|
||||
return true
|
||||
}, IterationOptions{
|
||||
Type: t,
|
||||
TopicName: topicFilter,
|
||||
MatchType: MatchName,
|
||||
})
|
||||
if len(rs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// GetClientSubscriptions returns the subscriptions of a specific client.
|
||||
func GetClientSubscriptions(store Store, clientID string, t IterationType) []*gmqtt.Subscription {
|
||||
var rs []*gmqtt.Subscription
|
||||
store.Iterate(func(clientID string, subscription *gmqtt.Subscription) bool {
|
||||
rs = append(rs, subscription)
|
||||
return true
|
||||
}, IterationOptions{
|
||||
Type: t,
|
||||
ClientID: clientID,
|
||||
})
|
||||
return rs
|
||||
}
|
||||
|
||||
// StatsReader provides the ability to get statistics information.
|
||||
type StatsReader interface {
|
||||
// GetStats return the global stats.
|
||||
GetStats() Stats
|
||||
// GetClientStats return the stats of a specific client.
|
||||
// If stats not exists, return an error.
|
||||
GetClientStats(clientID string) (Stats, error)
|
||||
}
|
||||
|
||||
// SplitTopic returns the shareName and topicFilter of the given topic.
|
||||
// If the topic is invalid, returns empty strings.
|
||||
func SplitTopic(topic string) (shareName, topicFilter string) {
|
||||
if strings.HasPrefix(topic, "$share/") {
|
||||
shared := strings.SplitN(topic, "/", 3)
|
||||
if len(shared) < 3 {
|
||||
return "", ""
|
||||
}
|
||||
return shared[1], shared[2]
|
||||
}
|
||||
return "", topic
|
||||
}
|
||||
|
||||
// GetFullTopicName returns the full topic name of given shareName and topicFilter
|
||||
func GetFullTopicName(shareName, topicFilter string) string {
|
||||
if shareName != "" {
|
||||
return "$share/" + shareName + "/" + topicFilter
|
||||
}
|
||||
return topicFilter
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: persistence/subscription/subscription.go
|
||||
|
||||
// Package subscription is a generated GoMock package.
|
||||
package subscription
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
)
|
||||
|
||||
// MockStore is a mock of Store interface
|
||||
type MockStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStoreMockRecorder
|
||||
}
|
||||
|
||||
// MockStoreMockRecorder is the mock recorder for MockStore
|
||||
type MockStoreMockRecorder struct {
|
||||
mock *MockStore
|
||||
}
|
||||
|
||||
// NewMockStore creates a new mock instance
|
||||
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||
mock := &MockStore{ctrl: ctrl}
|
||||
mock.recorder = &MockStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Init mocks base method
|
||||
func (m *MockStore) Init(clientIDs []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", clientIDs)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init
|
||||
func (mr *MockStoreMockRecorder) Init(clientIDs interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockStore)(nil).Init), clientIDs)
|
||||
}
|
||||
|
||||
// Subscribe mocks base method
|
||||
func (m *MockStore) Subscribe(clientID string, subscriptions ...*gmqtt.Subscription) (SubscribeResult, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{clientID}
|
||||
for _, a := range subscriptions {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Subscribe", varargs...)
|
||||
ret0, _ := ret[0].(SubscribeResult)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Subscribe indicates an expected call of Subscribe
|
||||
func (mr *MockStoreMockRecorder) Subscribe(clientID interface{}, subscriptions ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{clientID}, subscriptions...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockStore)(nil).Subscribe), varargs...)
|
||||
}
|
||||
|
||||
// Unsubscribe mocks base method
|
||||
func (m *MockStore) Unsubscribe(clientID string, topics ...string) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{clientID}
|
||||
for _, a := range topics {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Unsubscribe", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Unsubscribe indicates an expected call of Unsubscribe
|
||||
func (mr *MockStoreMockRecorder) Unsubscribe(clientID interface{}, topics ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{clientID}, topics...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockStore)(nil).Unsubscribe), varargs...)
|
||||
}
|
||||
|
||||
// UnsubscribeAll mocks base method
|
||||
func (m *MockStore) UnsubscribeAll(clientID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnsubscribeAll", clientID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnsubscribeAll indicates an expected call of UnsubscribeAll
|
||||
func (mr *MockStoreMockRecorder) UnsubscribeAll(clientID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnsubscribeAll", reflect.TypeOf((*MockStore)(nil).UnsubscribeAll), clientID)
|
||||
}
|
||||
|
||||
// Iterate mocks base method
|
||||
func (m *MockStore) Iterate(fn IterateFn, options IterationOptions) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Iterate", fn, options)
|
||||
}
|
||||
|
||||
// Iterate indicates an expected call of Iterate
|
||||
func (mr *MockStoreMockRecorder) Iterate(fn, options interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockStore)(nil).Iterate), fn, options)
|
||||
}
|
||||
|
||||
// Close mocks base method
|
||||
func (m *MockStore) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close
|
||||
func (mr *MockStoreMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStore)(nil).Close))
|
||||
}
|
||||
|
||||
// GetStats mocks base method
|
||||
func (m *MockStore) GetStats() Stats {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStats")
|
||||
ret0, _ := ret[0].(Stats)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetStats indicates an expected call of GetStats
|
||||
func (mr *MockStoreMockRecorder) GetStats() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStats", reflect.TypeOf((*MockStore)(nil).GetStats))
|
||||
}
|
||||
|
||||
// GetClientStats mocks base method
|
||||
func (m *MockStore) GetClientStats(clientID string) (Stats, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetClientStats", clientID)
|
||||
ret0, _ := ret[0].(Stats)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetClientStats indicates an expected call of GetClientStats
|
||||
func (mr *MockStoreMockRecorder) GetClientStats(clientID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientStats", reflect.TypeOf((*MockStore)(nil).GetClientStats), clientID)
|
||||
}
|
||||
|
||||
// MockStatsReader is a mock of StatsReader interface
|
||||
type MockStatsReader struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStatsReaderMockRecorder
|
||||
}
|
||||
|
||||
// MockStatsReaderMockRecorder is the mock recorder for MockStatsReader
|
||||
type MockStatsReaderMockRecorder struct {
|
||||
mock *MockStatsReader
|
||||
}
|
||||
|
||||
// NewMockStatsReader creates a new mock instance
|
||||
func NewMockStatsReader(ctrl *gomock.Controller) *MockStatsReader {
|
||||
mock := &MockStatsReader{ctrl: ctrl}
|
||||
mock.recorder = &MockStatsReaderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockStatsReader) EXPECT() *MockStatsReaderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetStats mocks base method
|
||||
func (m *MockStatsReader) GetStats() Stats {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStats")
|
||||
ret0, _ := ret[0].(Stats)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetStats indicates an expected call of GetStats
|
||||
func (mr *MockStatsReaderMockRecorder) GetStats() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStats", reflect.TypeOf((*MockStatsReader)(nil).GetStats))
|
||||
}
|
||||
|
||||
// GetClientStats mocks base method
|
||||
func (m *MockStatsReader) GetClientStats(clientID string) (Stats, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetClientStats", clientID)
|
||||
ret0, _ := ret[0].(Stats)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetClientStats indicates an expected call of GetClientStats
|
||||
func (mr *MockStatsReaderMockRecorder) GetClientStats(clientID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientStats", reflect.TypeOf((*MockStatsReader)(nil).GetClientStats), clientID)
|
||||
}
|
|
@ -1,601 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var (
|
||||
topicA = &gmqtt.Subscription{
|
||||
TopicFilter: "topic/A",
|
||||
ID: 1,
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
}
|
||||
|
||||
topicB = &gmqtt.Subscription{
|
||||
TopicFilter: "topic/B",
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: false,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 0,
|
||||
}
|
||||
|
||||
systemTopicA = &gmqtt.Subscription{
|
||||
TopicFilter: "$topic/A",
|
||||
ID: 1,
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
}
|
||||
|
||||
systemTopicB = &gmqtt.Subscription{
|
||||
TopicFilter: "$topic/B",
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: false,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 0,
|
||||
}
|
||||
|
||||
sharedTopicA1 = &gmqtt.Subscription{
|
||||
ShareName: "name1",
|
||||
TopicFilter: "topic/A",
|
||||
ID: 1,
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
}
|
||||
|
||||
sharedTopicB1 = &gmqtt.Subscription{
|
||||
ShareName: "name1",
|
||||
TopicFilter: "topic/B",
|
||||
ID: 1,
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
}
|
||||
|
||||
sharedTopicA2 = &gmqtt.Subscription{
|
||||
ShareName: "name2",
|
||||
TopicFilter: "topic/A",
|
||||
ID: 1,
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
}
|
||||
|
||||
sharedTopicB2 = &gmqtt.Subscription{
|
||||
ShareName: "name2",
|
||||
TopicFilter: "topic/B",
|
||||
ID: 1,
|
||||
|
||||
QoS: 1,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 1,
|
||||
}
|
||||
)
|
||||
|
||||
var testSubs = []struct {
|
||||
clientID string
|
||||
subs []*gmqtt.Subscription
|
||||
}{
|
||||
// non-share and non-system subscription
|
||||
{
|
||||
|
||||
clientID: "client1",
|
||||
subs: []*gmqtt.Subscription{
|
||||
topicA, topicB,
|
||||
},
|
||||
}, {
|
||||
clientID: "client2",
|
||||
subs: []*gmqtt.Subscription{
|
||||
topicA, topicB,
|
||||
},
|
||||
},
|
||||
// system subscription
|
||||
{
|
||||
|
||||
clientID: "client1",
|
||||
subs: []*gmqtt.Subscription{
|
||||
systemTopicA, systemTopicB,
|
||||
},
|
||||
}, {
|
||||
|
||||
clientID: "client2",
|
||||
subs: []*gmqtt.Subscription{
|
||||
systemTopicA, systemTopicB,
|
||||
},
|
||||
},
|
||||
// share subscription
|
||||
{
|
||||
clientID: "client1",
|
||||
subs: []*gmqtt.Subscription{
|
||||
sharedTopicA1, sharedTopicB1, sharedTopicA2, sharedTopicB2,
|
||||
},
|
||||
},
|
||||
{
|
||||
clientID: "client2",
|
||||
subs: []*gmqtt.Subscription{
|
||||
sharedTopicA1, sharedTopicB1, sharedTopicA2, sharedTopicB2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func testAddSubscribe(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
for _, v := range testSubs {
|
||||
_, err := store.Subscribe(v.clientID, v.subs...)
|
||||
a.Nil(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testGetStatus(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
var err error
|
||||
tt := []struct {
|
||||
clientID string
|
||||
topic packets.Topic
|
||||
}{
|
||||
{clientID: "id0", topic: packets.Topic{Name: "name0", SubOptions: packets.SubOptions{Qos: packets.Qos0}}},
|
||||
{clientID: "id1", topic: packets.Topic{Name: "name1", SubOptions: packets.SubOptions{Qos: packets.Qos1}}},
|
||||
{clientID: "id2", topic: packets.Topic{Name: "name2", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id3", topic: packets.Topic{Name: "name3", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id4", topic: packets.Topic{Name: "name3", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id4", topic: packets.Topic{Name: "name4", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
// test $share and system topic
|
||||
{clientID: "id4", topic: packets.Topic{Name: "$share/abc/name4", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id4", topic: packets.Topic{Name: "$SYS/abc/def", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
}
|
||||
for _, v := range tt {
|
||||
_, err = store.Subscribe(v.clientID, subscription.FromTopic(v.topic, 0))
|
||||
a.NoError(err)
|
||||
}
|
||||
stats := store.GetStats()
|
||||
expectedTotal, expectedCurrent := len(tt), len(tt)
|
||||
|
||||
a.EqualValues(expectedTotal, stats.SubscriptionsTotal)
|
||||
a.EqualValues(expectedCurrent, stats.SubscriptionsCurrent)
|
||||
|
||||
// If subscribe duplicated topic, total and current statistics should not increase
|
||||
_, err = store.Subscribe("id0", subscription.FromTopic(packets.Topic{SubOptions: packets.SubOptions{Qos: packets.Qos0}, Name: "name0"}, 0))
|
||||
a.NoError(err)
|
||||
_, err = store.Subscribe("id4", subscription.FromTopic(packets.Topic{SubOptions: packets.SubOptions{Qos: packets.Qos2}, Name: "$share/abc/name4"}, 0))
|
||||
a.NoError(err)
|
||||
|
||||
stats = store.GetStats()
|
||||
a.EqualValues(expectedTotal, stats.SubscriptionsTotal)
|
||||
a.EqualValues(expectedCurrent, stats.SubscriptionsCurrent)
|
||||
|
||||
utt := []struct {
|
||||
clientID string
|
||||
topic packets.Topic
|
||||
}{
|
||||
{clientID: "id0", topic: packets.Topic{Name: "name0", SubOptions: packets.SubOptions{Qos: packets.Qos0}}},
|
||||
{clientID: "id1", topic: packets.Topic{Name: "name1", SubOptions: packets.SubOptions{Qos: packets.Qos1}}},
|
||||
}
|
||||
expectedCurrent -= 2
|
||||
for _, v := range utt {
|
||||
a.NoError(store.Unsubscribe(v.clientID, v.topic.Name))
|
||||
}
|
||||
stats = store.GetStats()
|
||||
a.EqualValues(expectedTotal, stats.SubscriptionsTotal)
|
||||
a.EqualValues(expectedCurrent, stats.SubscriptionsCurrent)
|
||||
|
||||
//if unsubscribe not exists topic, current statistics should not decrease
|
||||
a.NoError(store.Unsubscribe("id0", "name555"))
|
||||
stats = store.GetStats()
|
||||
a.EqualValues(len(tt), stats.SubscriptionsTotal)
|
||||
a.EqualValues(expectedCurrent, stats.SubscriptionsCurrent)
|
||||
|
||||
a.NoError(store.Unsubscribe("id4", "$share/abc/name4"))
|
||||
|
||||
expectedCurrent -= 1
|
||||
stats = store.GetStats()
|
||||
a.EqualValues(expectedTotal, stats.SubscriptionsTotal)
|
||||
a.EqualValues(expectedCurrent, stats.SubscriptionsCurrent)
|
||||
|
||||
a.NoError(store.UnsubscribeAll("id4"))
|
||||
expectedCurrent -= 3
|
||||
stats = store.GetStats()
|
||||
a.EqualValues(len(tt), stats.SubscriptionsTotal)
|
||||
a.EqualValues(expectedCurrent, stats.SubscriptionsCurrent)
|
||||
}
|
||||
|
||||
func testGetClientStats(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
var err error
|
||||
tt := []struct {
|
||||
clientID string
|
||||
topic packets.Topic
|
||||
}{
|
||||
{clientID: "id0", topic: packets.Topic{Name: "name0", SubOptions: packets.SubOptions{Qos: packets.Qos0}}},
|
||||
{clientID: "id0", topic: packets.Topic{Name: "name1", SubOptions: packets.SubOptions{Qos: packets.Qos1}}},
|
||||
// test $share and system topic
|
||||
{clientID: "id0", topic: packets.Topic{Name: "$share/abc/name5", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id0", topic: packets.Topic{Name: "$SYS/a/b/c", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
|
||||
{clientID: "id1", topic: packets.Topic{Name: "name0", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id1", topic: packets.Topic{Name: "$share/abc/name5", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id2", topic: packets.Topic{Name: "$SYS/a/b/c", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
{clientID: "id2", topic: packets.Topic{Name: "name5", SubOptions: packets.SubOptions{Qos: packets.Qos2}}},
|
||||
}
|
||||
for _, v := range tt {
|
||||
_, err = store.Subscribe(v.clientID, subscription.FromTopic(v.topic, 0))
|
||||
a.NoError(err)
|
||||
}
|
||||
stats, _ := store.GetClientStats("id0")
|
||||
a.EqualValues(4, stats.SubscriptionsTotal)
|
||||
a.EqualValues(4, stats.SubscriptionsCurrent)
|
||||
|
||||
a.NoError(store.UnsubscribeAll("id0"))
|
||||
stats, _ = store.GetClientStats("id0")
|
||||
a.EqualValues(4, stats.SubscriptionsTotal)
|
||||
a.EqualValues(0, stats.SubscriptionsCurrent)
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T, new func() subscription.Store) {
|
||||
a := assert.New(t)
|
||||
store := new()
|
||||
a.Nil(store.Init(nil))
|
||||
defer store.Close()
|
||||
for i := 0; i <= 1; i++ {
|
||||
testAddSubscribe(t, store)
|
||||
t.Run("testGetTopic"+strconv.Itoa(i), func(t *testing.T) {
|
||||
testGetTopic(t, store)
|
||||
})
|
||||
t.Run("testTopicMatch"+strconv.Itoa(i), func(t *testing.T) {
|
||||
testTopicMatch(t, store)
|
||||
})
|
||||
t.Run("testIterate"+strconv.Itoa(i), func(t *testing.T) {
|
||||
testIterate(t, store)
|
||||
})
|
||||
t.Run("testUnsubscribe"+strconv.Itoa(i), func(t *testing.T) {
|
||||
testUnsubscribe(t, store)
|
||||
})
|
||||
}
|
||||
|
||||
store2 := new()
|
||||
a.Nil(store2.Init(nil))
|
||||
defer store2.Close()
|
||||
t.Run("testGetStatus", func(t *testing.T) {
|
||||
testGetStatus(t, store2)
|
||||
})
|
||||
|
||||
store3 := new()
|
||||
a.Nil(store3.Init(nil))
|
||||
defer store3.Close()
|
||||
t.Run("testGetStatus", func(t *testing.T) {
|
||||
testGetClientStats(t, store3)
|
||||
})
|
||||
}
|
||||
func testGetTopic(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
|
||||
rs := subscription.Get(store, topicA.TopicFilter, subscription.TypeAll)
|
||||
a.Equal(topicA, rs["client1"][0])
|
||||
a.Equal(topicA, rs["client2"][0])
|
||||
|
||||
rs = subscription.Get(store, topicA.TopicFilter, subscription.TypeNonShared)
|
||||
a.Equal(topicA, rs["client1"][0])
|
||||
a.Equal(topicA, rs["client2"][0])
|
||||
|
||||
rs = subscription.Get(store, systemTopicA.TopicFilter, subscription.TypeAll)
|
||||
a.Equal(systemTopicA, rs["client1"][0])
|
||||
a.Equal(systemTopicA, rs["client2"][0])
|
||||
|
||||
rs = subscription.Get(store, systemTopicA.TopicFilter, subscription.TypeSYS)
|
||||
a.Equal(systemTopicA, rs["client1"][0])
|
||||
a.Equal(systemTopicA, rs["client2"][0])
|
||||
|
||||
rs = subscription.Get(store, "$share/"+sharedTopicA1.ShareName+"/"+sharedTopicA1.TopicFilter, subscription.TypeAll)
|
||||
a.Equal(sharedTopicA1, rs["client1"][0])
|
||||
a.Equal(sharedTopicA1, rs["client2"][0])
|
||||
|
||||
}
|
||||
func testTopicMatch(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
rs := subscription.GetTopicMatched(store, topicA.TopicFilter, subscription.TypeAll)
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA, sharedTopicA1, sharedTopicA2}, rs["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA, sharedTopicA1, sharedTopicA2}, rs["client2"])
|
||||
|
||||
rs = subscription.GetTopicMatched(store, topicA.TopicFilter, subscription.TypeNonShared)
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, rs["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, rs["client2"])
|
||||
|
||||
rs = subscription.GetTopicMatched(store, topicA.TopicFilter, subscription.TypeShared)
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2}, rs["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2}, rs["client2"])
|
||||
|
||||
rs = subscription.GetTopicMatched(store, systemTopicA.TopicFilter, subscription.TypeSYS)
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, rs["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, rs["client2"])
|
||||
|
||||
}
|
||||
func testUnsubscribe(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
a.Nil(store.Unsubscribe("client1", topicA.TopicFilter))
|
||||
rs := subscription.Get(store, topicA.TopicFilter, subscription.TypeAll)
|
||||
a.Nil(rs["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, rs["client2"])
|
||||
a.Nil(store.UnsubscribeAll("client2"))
|
||||
a.Nil(store.UnsubscribeAll("client1"))
|
||||
var iterationCalled bool
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
iterationCalled = true
|
||||
return true
|
||||
}, subscription.IterationOptions{Type: subscription.TypeAll})
|
||||
a.False(iterationCalled)
|
||||
}
|
||||
func testIterate(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
|
||||
var iterationCalled bool
|
||||
// invalid subscription.IterationOptions
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
iterationCalled = true
|
||||
return true
|
||||
}, subscription.IterationOptions{})
|
||||
a.False(iterationCalled)
|
||||
testIterateNonShared(t, store)
|
||||
testIterateShared(t, store)
|
||||
testIterateSystem(t, store)
|
||||
}
|
||||
func testIterateNonShared(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
// iterate all non-shared subscriptions.
|
||||
got := make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA, topicB}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA, topicB}, got["client2"])
|
||||
|
||||
// iterate all non-shared subscriptions with ClientID option.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared,
|
||||
ClientID: "client1",
|
||||
})
|
||||
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA, topicB}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
// iterate all non-shared subscriptions that matched given topic name.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared,
|
||||
MatchType: subscription.MatchName,
|
||||
TopicName: topicA.TopicFilter,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, got["client2"])
|
||||
|
||||
// iterate all non-shared subscriptions that matched given topic name and client id
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared,
|
||||
MatchType: subscription.MatchName,
|
||||
TopicName: topicA.TopicFilter,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
// iterate all non-shared subscriptions that matched given topic filter.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared,
|
||||
MatchType: subscription.MatchFilter,
|
||||
TopicName: topicA.TopicFilter,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, got["client2"])
|
||||
|
||||
// iterate all non-shared subscriptions that matched given topic filter and client id
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared,
|
||||
MatchType: subscription.MatchFilter,
|
||||
TopicName: topicA.TopicFilter,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{topicA}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
}
|
||||
func testIterateShared(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
// iterate all shared subscriptions.
|
||||
got := make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeShared,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2, sharedTopicB1, sharedTopicB2}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2, sharedTopicB1, sharedTopicB2}, got["client2"])
|
||||
|
||||
// iterate all shared subscriptions with ClientID option.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeShared,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2, sharedTopicB1, sharedTopicB2}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
// iterate all shared subscriptions that matched given topic filter.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeShared,
|
||||
MatchType: subscription.MatchName,
|
||||
TopicName: "$share/" + sharedTopicA1.ShareName + "/" + sharedTopicA1.TopicFilter,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1}, got["client2"])
|
||||
|
||||
// iterate all shared subscriptions that matched given topic filter and client id
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeShared,
|
||||
MatchType: subscription.MatchName,
|
||||
TopicName: "$share/" + sharedTopicA1.ShareName + "/" + sharedTopicA1.TopicFilter,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
// iterate all shared subscriptions that matched given topic name.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeShared,
|
||||
MatchType: subscription.MatchFilter,
|
||||
TopicName: sharedTopicA1.TopicFilter,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2}, got["client2"])
|
||||
|
||||
// iterate all shared subscriptions that matched given topic name and clientID
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeShared,
|
||||
MatchType: subscription.MatchFilter,
|
||||
TopicName: sharedTopicA1.TopicFilter,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{sharedTopicA1, sharedTopicA2}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
}
|
||||
func testIterateSystem(t *testing.T, store subscription.Store) {
|
||||
a := assert.New(t)
|
||||
// iterate all system subscriptions.
|
||||
got := make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeSYS,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA, systemTopicB}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA, systemTopicB}, got["client2"])
|
||||
|
||||
// iterate all system subscriptions with ClientID option.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeSYS,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA, systemTopicB}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
// iterate all system subscriptions that matched given topic filter.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeSYS,
|
||||
MatchType: subscription.MatchName,
|
||||
TopicName: systemTopicA.TopicFilter,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, got["client2"])
|
||||
|
||||
// iterate all system subscriptions that matched given topic filter and client id
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeSYS,
|
||||
MatchType: subscription.MatchName,
|
||||
TopicName: systemTopicA.TopicFilter,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
|
||||
// iterate all system subscriptions that matched given topic name.
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeSYS,
|
||||
MatchType: subscription.MatchFilter,
|
||||
TopicName: systemTopicA.TopicFilter,
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, got["client1"])
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, got["client2"])
|
||||
|
||||
// iterate all system subscriptions that matched given topic name and clientID
|
||||
got = make(subscription.ClientSubscriptions)
|
||||
store.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
got[clientID] = append(got[clientID], sub)
|
||||
return true
|
||||
}, subscription.IterationOptions{
|
||||
Type: subscription.TypeSYS,
|
||||
MatchType: subscription.MatchFilter,
|
||||
TopicName: systemTopicA.TopicFilter,
|
||||
ClientID: "client1",
|
||||
})
|
||||
a.ElementsMatch([]*gmqtt.Subscription{systemTopicA}, got["client1"])
|
||||
a.Len(got["client2"], 0)
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package mem
|
||||
|
||||
import (
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var _ unack.Store = (*Store)(nil)
|
||||
|
||||
type Store struct {
|
||||
clientID string
|
||||
unackpublish map[packets.PacketID]struct{}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
ClientID string
|
||||
}
|
||||
|
||||
func New(opts Options) *Store {
|
||||
return &Store{
|
||||
clientID: opts.ClientID,
|
||||
unackpublish: make(map[packets.PacketID]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) Init(cleanStart bool) error {
|
||||
if cleanStart {
|
||||
s.unackpublish = make(map[packets.PacketID]struct{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Set(id packets.PacketID) (bool, error) {
|
||||
if _, ok := s.unackpublish[id]; ok {
|
||||
return true, nil
|
||||
}
|
||||
s.unackpublish[id] = struct{}{}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *Store) Remove(id packets.PacketID) error {
|
||||
delete(s.unackpublish, id)
|
||||
return nil
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"github.com/gomodule/redigo/redis"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
const (
|
||||
unackPrefix = "unack:"
|
||||
)
|
||||
|
||||
var _ unack.Store = (*Store)(nil)
|
||||
|
||||
type Store struct {
|
||||
clientID string
|
||||
pool *redis.Pool
|
||||
unackpublish map[packets.PacketID]struct{}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
ClientID string
|
||||
Pool *redis.Pool
|
||||
}
|
||||
|
||||
func New(opts Options) *Store {
|
||||
return &Store{
|
||||
clientID: opts.ClientID,
|
||||
pool: opts.Pool,
|
||||
unackpublish: make(map[packets.PacketID]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func getKey(clientID string) string {
|
||||
return unackPrefix + clientID
|
||||
}
|
||||
func (s *Store) Init(cleanStart bool) error {
|
||||
if cleanStart {
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
s.unackpublish = make(map[packets.PacketID]struct{})
|
||||
_, err := c.Do("del", getKey(s.clientID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Set(id packets.PacketID) (bool, error) {
|
||||
// from cache
|
||||
if _, ok := s.unackpublish[id]; ok {
|
||||
return true, nil
|
||||
}
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
_, err := c.Do("hset", getKey(s.clientID), id, 1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.unackpublish[id] = struct{}{}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *Store) Remove(id packets.PacketID) error {
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
_, err := c.Do("hdel", getKey(s.clientID), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(s.unackpublish, id)
|
||||
return nil
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/unack"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var (
|
||||
TestServerConfig = config.Config{}
|
||||
cid = "cid"
|
||||
TestClientID = cid
|
||||
)
|
||||
|
||||
func TestSuite(t *testing.T, store unack.Store) {
|
||||
a := assert.New(t)
|
||||
a.Nil(store.Init(false))
|
||||
for i := packets.PacketID(1); i < 10; i++ {
|
||||
rs, err := store.Set(i)
|
||||
a.Nil(err)
|
||||
a.False(rs)
|
||||
rs, err = store.Set(i)
|
||||
a.Nil(err)
|
||||
a.True(rs)
|
||||
err = store.Remove(i)
|
||||
a.Nil(err)
|
||||
rs, err = store.Set(i)
|
||||
a.Nil(err)
|
||||
a.False(rs)
|
||||
|
||||
}
|
||||
a.Nil(store.Init(false))
|
||||
for i := packets.PacketID(1); i < 10; i++ {
|
||||
rs, err := store.Set(i)
|
||||
a.Nil(err)
|
||||
a.True(rs)
|
||||
err = store.Remove(i)
|
||||
a.Nil(err)
|
||||
rs, err = store.Set(i)
|
||||
a.Nil(err)
|
||||
a.False(rs)
|
||||
}
|
||||
a.Nil(store.Init(true))
|
||||
for i := packets.PacketID(1); i < 10; i++ {
|
||||
rs, err := store.Set(i)
|
||||
a.Nil(err)
|
||||
a.False(rs)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package unack
|
||||
|
||||
import (
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
// Store represents a unack store for one client.
|
||||
// Unack store is used to persist the unacknowledged qos2 messages.
|
||||
type Store interface {
|
||||
// Init will be called when the client connect.
|
||||
// If cleanStart set to true, the implementation should remove any associated data in backend store.
|
||||
// If it set to false, the implementation should retrieve the associated data from backend store.
|
||||
Init(cleanStart bool) error
|
||||
// Set sets the given id into store.
|
||||
// The return boolean indicates whether the id exist.
|
||||
Set(id packets.PacketID) (bool, error)
|
||||
// Remove removes the given id from store.
|
||||
Remove(id packets.PacketID) error
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: persistence/unack/unack.go
|
||||
|
||||
// Package unack is a generated GoMock package.
|
||||
package unack
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
packets "github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
// MockStore is a mock of Store interface
|
||||
type MockStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStoreMockRecorder
|
||||
}
|
||||
|
||||
// MockStoreMockRecorder is the mock recorder for MockStore
|
||||
type MockStoreMockRecorder struct {
|
||||
mock *MockStore
|
||||
}
|
||||
|
||||
// NewMockStore creates a new mock instance
|
||||
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||
mock := &MockStore{ctrl: ctrl}
|
||||
mock.recorder = &MockStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Init mocks base method
|
||||
func (m *MockStore) Init(cleanStart bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", cleanStart)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init
|
||||
func (mr *MockStoreMockRecorder) Init(cleanStart interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockStore)(nil).Init), cleanStart)
|
||||
}
|
||||
|
||||
// Set mocks base method
|
||||
func (m *MockStore) Set(id packets.PacketID) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Set", id)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set
|
||||
func (mr *MockStoreMockRecorder) Set(id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockStore)(nil).Set), id)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockStore) Remove(id packets.PacketID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Remove", id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockStoreMockRecorder) Remove(id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStore)(nil).Remove), id)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
# Plugin
|
||||
|
||||
[Gmqtt插件机制详解](https://juejin.cn/post/6908305981923409934)
|
||||
|
||||
## How to write plugins
|
||||
|
||||
Gmqtt uses code generator to generate plugin template.
|
||||
|
||||
First, install the CLI tool:
|
||||
|
||||
```bash
|
||||
# run under gmqtt project root directory.
|
||||
go install ./cmd/gmqctl
|
||||
```
|
||||
|
||||
Enjoy:
|
||||
|
||||
```bash
|
||||
$ gmqctl gen plugin --help
|
||||
code generator
|
||||
|
||||
Usage:
|
||||
gmqctl gen plugin [flags]
|
||||
|
||||
Examples:
|
||||
The following command will generate a code template for the 'awesome' plugin, which makes use of OnBasicAuth and OnSubscribe hook and enables the configuration in ./plugin directory.
|
||||
|
||||
gmqctl gen plugin -n awesome -H OnBasicAuth,OnSubscribe -c true -o ./plugin
|
||||
|
||||
Flags:
|
||||
-c, --config Whether the plugin needs a configuration.
|
||||
-h, --help help for plugin
|
||||
-H, --hooks string The hooks use by the plugin, multiple hooks are separated by ','
|
||||
-n, --name string The plugin name.
|
||||
-o, --output string The output directory.
|
||||
|
||||
```
|
||||
|
||||
Details...TODO
|
|
@ -1,83 +0,0 @@
|
|||
# admin
|
||||
|
||||
Admin plugin use [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) to provide both REST HTTP and GRPC APIs
|
||||
for integration with external systems.
|
||||
|
||||
# API Doc
|
||||
|
||||
See [swagger](https://github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/blob/master/plugin/admin/swagger)
|
||||
|
||||
# Examples
|
||||
|
||||
## List Clients
|
||||
|
||||
```bash
|
||||
$ curl 127.0.0.1:57091/v1/clients
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"clients": [
|
||||
{
|
||||
"client_id": "ab",
|
||||
"username": "",
|
||||
"keep_alive": 60,
|
||||
"version": 4,
|
||||
"remote_addr": "127.0.0.1:51637",
|
||||
"local_addr": "127.0.0.1:58090",
|
||||
"connected_at": "2020-12-12T12:26:36Z",
|
||||
"disconnected_at": null,
|
||||
"session_expiry": 7200,
|
||||
"max_inflight": 100,
|
||||
"inflight_len": 0,
|
||||
"max_queue": 100,
|
||||
"queue_len": 0,
|
||||
"subscriptions_current": 0,
|
||||
"subscriptions_total": 0,
|
||||
"packets_received_bytes": "54",
|
||||
"packets_received_nums": "3",
|
||||
"packets_send_bytes": "8",
|
||||
"packets_send_nums": "2",
|
||||
"message_dropped": "0"
|
||||
}
|
||||
],
|
||||
"total_count": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Filter Subscriptions
|
||||
|
||||
```bash
|
||||
$ curl 127.0.0.1:57091/v1/filter_subscriptions?filter_type=1,2,3&match_type=1&topic_name=/a
|
||||
```
|
||||
|
||||
This curl is able to filter the subscription that the topic name is equal to "/a".
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"subscriptions": [
|
||||
{
|
||||
"topic_name": "/a",
|
||||
"id": 0,
|
||||
"qos": 1,
|
||||
"no_local": false,
|
||||
"retain_as_published": false,
|
||||
"retain_handling": 0,
|
||||
"client_id": "ab"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Publish Message
|
||||
|
||||
```bash
|
||||
$ curl -X POST 127.0.0.1:57091/v1/publish -d '{"topic_name":"a","payload":"test","qos":1}'
|
||||
```
|
||||
|
||||
This curl will publish the message to the broker.The broker will check if there are matched topics and send the message
|
||||
to the subscribers, just like received a message from a MQTT client.
|
|
@ -1,72 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
var _ server.Plugin = (*Admin)(nil)
|
||||
|
||||
const Name = "admin"
|
||||
|
||||
func init() {
|
||||
server.RegisterPlugin(Name, New)
|
||||
}
|
||||
|
||||
func New(config config.Config) (server.Plugin, error) {
|
||||
return &Admin{}, nil
|
||||
}
|
||||
|
||||
var log *zap.Logger
|
||||
|
||||
// Admin providers gRPC and HTTP API that enables the external system to interact with the broker.
|
||||
type Admin struct {
|
||||
statsReader server.StatsReader
|
||||
publisher server.Publisher
|
||||
clientService server.ClientService
|
||||
store *store
|
||||
}
|
||||
|
||||
func (a *Admin) registerHTTP(g server.APIRegistrar) (err error) {
|
||||
err = g.RegisterHTTPHandler(RegisterClientServiceHandlerFromEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.RegisterHTTPHandler(RegisterSubscriptionServiceHandlerFromEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.RegisterHTTPHandler(RegisterPublishServiceHandlerFromEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Admin) Load(service server.Server) error {
|
||||
log = server.LoggerWithField(zap.String("plugin", Name))
|
||||
apiRegistrar := service.APIRegistrar()
|
||||
RegisterClientServiceServer(apiRegistrar, &clientService{a: a})
|
||||
RegisterSubscriptionServiceServer(apiRegistrar, &subscriptionService{a: a})
|
||||
RegisterPublishServiceServer(apiRegistrar, &publisher{a: a})
|
||||
err := a.registerHTTP(apiRegistrar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.statsReader = service.StatsManager()
|
||||
a.store = newStore(a.statsReader, service.GetConfig())
|
||||
a.store.subscriptionService = service.SubscriptionService()
|
||||
a.publisher = service.Publisher()
|
||||
a.clientService = service.ClientService()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Admin) Unload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Admin) Name() string {
|
||||
return Name
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
)
|
||||
|
||||
type clientService struct {
|
||||
a *Admin
|
||||
}
|
||||
|
||||
func (c *clientService) mustEmbedUnimplementedClientServiceServer() {
|
||||
return
|
||||
}
|
||||
|
||||
// List lists alertclient information which the session is valid in the broker (both connected and disconnected).
|
||||
func (c *clientService) List(ctx context.Context, req *ListClientRequest) (*ListClientResponse, error) {
|
||||
page, pageSize := GetPage(req.Page, req.PageSize)
|
||||
clients, total, err := c.a.store.GetClients(page, pageSize)
|
||||
if err != nil {
|
||||
return &ListClientResponse{}, err
|
||||
}
|
||||
return &ListClientResponse{
|
||||
Clients: clients,
|
||||
TotalCount: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get returns the client information for given request client id.
|
||||
func (c *clientService) Get(ctx context.Context, req *GetClientRequest) (*GetClientResponse, error) {
|
||||
if req.ClientId == "" {
|
||||
return nil, ErrInvalidArgument("client_id", "")
|
||||
}
|
||||
client := c.a.store.GetClientByID(req.ClientId)
|
||||
if client == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return &GetClientResponse{
|
||||
Client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete force disconnect.
|
||||
func (c *clientService) Delete(ctx context.Context, req *DeleteClientRequest) (*empty.Empty, error) {
|
||||
if req.ClientId == "" {
|
||||
return nil, ErrInvalidArgument("client_id", "")
|
||||
}
|
||||
if req.CleanSession {
|
||||
c.a.clientService.TerminateSession(req.ClientId)
|
||||
} else {
|
||||
client := c.a.clientService.GetClient(req.ClientId)
|
||||
if client != nil {
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
return &empty.Empty{}, nil
|
||||
}
|
|
@ -1,739 +0,0 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.22.0
|
||||
// protoc v3.13.0
|
||||
// source: client.proto
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
empty "github.com/golang/protobuf/ptypes/empty"
|
||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type ListClientRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
PageSize uint32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
|
||||
Page uint32 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListClientRequest) Reset() {
|
||||
*x = ListClientRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_client_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListClientRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListClientRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListClientRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_client_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListClientRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListClientRequest) Descriptor() ([]byte, []int) {
|
||||
return file_client_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ListClientRequest) GetPageSize() uint32 {
|
||||
if x != nil {
|
||||
return x.PageSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ListClientRequest) GetPage() uint32 {
|
||||
if x != nil {
|
||||
return x.Page
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ListClientResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Clients []*Client `protobuf:"bytes,1,rep,name=alertclient,proto3" json:"alertclient,omitempty"`
|
||||
TotalCount uint32 `protobuf:"varint,2,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListClientResponse) Reset() {
|
||||
*x = ListClientResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_client_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListClientResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListClientResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListClientResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_client_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListClientResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListClientResponse) Descriptor() ([]byte, []int) {
|
||||
return file_client_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ListClientResponse) GetClients() []*Client {
|
||||
if x != nil {
|
||||
return x.Clients
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListClientResponse) GetTotalCount() uint32 {
|
||||
if x != nil {
|
||||
return x.TotalCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GetClientRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetClientRequest) Reset() {
|
||||
*x = GetClientRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_client_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetClientRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetClientRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetClientRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_client_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetClientRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetClientRequest) Descriptor() ([]byte, []int) {
|
||||
return file_client_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GetClientRequest) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetClientResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetClientResponse) Reset() {
|
||||
*x = GetClientResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_client_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetClientResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetClientResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetClientResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_client_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetClientResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetClientResponse) Descriptor() ([]byte, []int) {
|
||||
return file_client_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *GetClientResponse) GetClient() *Client {
|
||||
if x != nil {
|
||||
return x.Client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeleteClientRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
CleanSession bool `protobuf:"varint,2,opt,name=clean_session,json=cleanSession,proto3" json:"clean_session,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DeleteClientRequest) Reset() {
|
||||
*x = DeleteClientRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_client_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DeleteClientRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeleteClientRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteClientRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_client_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DeleteClientRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteClientRequest) Descriptor() ([]byte, []int) {
|
||||
return file_client_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *DeleteClientRequest) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeleteClientRequest) GetCleanSession() bool {
|
||||
if x != nil {
|
||||
return x.CleanSession
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||
KeepAlive int32 `protobuf:"varint,3,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"`
|
||||
Version int32 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`
|
||||
RemoteAddr string `protobuf:"bytes,5,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"`
|
||||
LocalAddr string `protobuf:"bytes,6,opt,name=local_addr,json=localAddr,proto3" json:"local_addr,omitempty"`
|
||||
ConnectedAt *timestamp.Timestamp `protobuf:"bytes,7,opt,name=connected_at,json=connectedAt,proto3" json:"connected_at,omitempty"`
|
||||
DisconnectedAt *timestamp.Timestamp `protobuf:"bytes,8,opt,name=disconnected_at,json=disconnectedAt,proto3" json:"disconnected_at,omitempty"`
|
||||
SessionExpiry uint32 `protobuf:"varint,9,opt,name=session_expiry,json=sessionExpiry,proto3" json:"session_expiry,omitempty"`
|
||||
MaxInflight uint32 `protobuf:"varint,10,opt,name=max_inflight,json=maxInflight,proto3" json:"max_inflight,omitempty"`
|
||||
InflightLen uint32 `protobuf:"varint,11,opt,name=inflight_len,json=inflightLen,proto3" json:"inflight_len,omitempty"`
|
||||
MaxQueue uint32 `protobuf:"varint,12,opt,name=max_queue,json=maxQueue,proto3" json:"max_queue,omitempty"`
|
||||
QueueLen uint32 `protobuf:"varint,13,opt,name=queue_len,json=queueLen,proto3" json:"queue_len,omitempty"`
|
||||
SubscriptionsCurrent uint32 `protobuf:"varint,14,opt,name=subscriptions_current,json=subscriptionsCurrent,proto3" json:"subscriptions_current,omitempty"`
|
||||
SubscriptionsTotal uint32 `protobuf:"varint,15,opt,name=subscriptions_total,json=subscriptionsTotal,proto3" json:"subscriptions_total,omitempty"`
|
||||
PacketsReceivedBytes uint64 `protobuf:"varint,16,opt,name=packets_received_bytes,json=packetsReceivedBytes,proto3" json:"packets_received_bytes,omitempty"`
|
||||
PacketsReceivedNums uint64 `protobuf:"varint,17,opt,name=packets_received_nums,json=packetsReceivedNums,proto3" json:"packets_received_nums,omitempty"`
|
||||
PacketsSendBytes uint64 `protobuf:"varint,18,opt,name=packets_send_bytes,json=packetsSendBytes,proto3" json:"packets_send_bytes,omitempty"`
|
||||
PacketsSendNums uint64 `protobuf:"varint,19,opt,name=packets_send_nums,json=packetsSendNums,proto3" json:"packets_send_nums,omitempty"`
|
||||
MessageDropped uint64 `protobuf:"varint,20,opt,name=message_dropped,json=messageDropped,proto3" json:"message_dropped,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Client) Reset() {
|
||||
*x = Client{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_client_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Client) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Client) ProtoMessage() {}
|
||||
|
||||
func (x *Client) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_client_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Client.ProtoReflect.Descriptor instead.
|
||||
func (*Client) Descriptor() ([]byte, []int) {
|
||||
return file_client_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Client) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Client) GetUsername() string {
|
||||
if x != nil {
|
||||
return x.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Client) GetKeepAlive() int32 {
|
||||
if x != nil {
|
||||
return x.KeepAlive
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetRemoteAddr() string {
|
||||
if x != nil {
|
||||
return x.RemoteAddr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Client) GetLocalAddr() string {
|
||||
if x != nil {
|
||||
return x.LocalAddr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Client) GetConnectedAt() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.ConnectedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Client) GetDisconnectedAt() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.DisconnectedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Client) GetSessionExpiry() uint32 {
|
||||
if x != nil {
|
||||
return x.SessionExpiry
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetMaxInflight() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxInflight
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetInflightLen() uint32 {
|
||||
if x != nil {
|
||||
return x.InflightLen
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetMaxQueue() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxQueue
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetQueueLen() uint32 {
|
||||
if x != nil {
|
||||
return x.QueueLen
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetSubscriptionsCurrent() uint32 {
|
||||
if x != nil {
|
||||
return x.SubscriptionsCurrent
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetSubscriptionsTotal() uint32 {
|
||||
if x != nil {
|
||||
return x.SubscriptionsTotal
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetPacketsReceivedBytes() uint64 {
|
||||
if x != nil {
|
||||
return x.PacketsReceivedBytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetPacketsReceivedNums() uint64 {
|
||||
if x != nil {
|
||||
return x.PacketsReceivedNums
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetPacketsSendBytes() uint64 {
|
||||
if x != nil {
|
||||
return x.PacketsSendBytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetPacketsSendNums() uint64 {
|
||||
if x != nil {
|
||||
return x.PacketsSendNums
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Client) GetMessageDropped() uint64 {
|
||||
if x != nil {
|
||||
return x.MessageDropped
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_client_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_client_proto_rawDesc = []byte{
|
||||
0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
|
||||
0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x1a,
|
||||
0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x44, 0x0a, 0x11, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x61, 0x67,
|
||||
0x65, 0x22, 0x68, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74,
|
||||
0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f,
|
||||
0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2f, 0x0a, 0x10, 0x47,
|
||||
0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x44, 0x0a, 0x11,
|
||||
0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x22, 0x57, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x5f,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x63,
|
||||
0x6c, 0x65, 0x61, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xb8, 0x06, 0x0a, 0x06,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52,
|
||||
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f,
|
||||
0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72,
|
||||
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c,
|
||||
0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e,
|
||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e,
|
||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x43, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x64, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x25, 0x0a, 0x0e,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x09,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70,
|
||||
0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69,
|
||||
0x67, 0x68, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x49, 0x6e,
|
||||
0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67,
|
||||
0x68, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69, 0x6e,
|
||||
0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78,
|
||||
0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6d, 0x61,
|
||||
0x78, 0x51, 0x75, 0x65, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f,
|
||||
0x6c, 0x65, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x71, 0x75, 0x65, 0x75, 0x65,
|
||||
0x4c, 0x65, 0x6e, 0x12, 0x33, 0x0a, 0x15, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x14, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18,
|
||||
0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x61, 0x63,
|
||||
0x6b, 0x65, 0x74, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x62, 0x79,
|
||||
0x74, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x70, 0x61, 0x63, 0x6b, 0x65,
|
||||
0x74, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12,
|
||||
0x32, 0x0a, 0x15, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69,
|
||||
0x76, 0x65, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13,
|
||||
0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x4e,
|
||||
0x75, 0x6d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x73,
|
||||
0x65, 0x6e, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||
0x10, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x79, 0x74, 0x65,
|
||||
0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x6e,
|
||||
0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x70, 0x61,
|
||||
0x63, 0x6b, 0x65, 0x74, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x75, 0x6d, 0x73, 0x12, 0x27, 0x0a,
|
||||
0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64,
|
||||
0x18, 0x14, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44,
|
||||
0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x32, 0xcd, 0x02, 0x0a, 0x0d, 0x43, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x12, 0x22, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d,
|
||||
0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02,
|
||||
0x0d, 0x12, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x6d,
|
||||
0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x21, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74,
|
||||
0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3,
|
||||
0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x67, 0x0a,
|
||||
0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e,
|
||||
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f,
|
||||
0x76, 0x31, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x61, 0x64, 0x6d, 0x69,
|
||||
0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_client_proto_rawDescOnce sync.Once
|
||||
file_client_proto_rawDescData = file_client_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_client_proto_rawDescGZIP() []byte {
|
||||
file_client_proto_rawDescOnce.Do(func() {
|
||||
file_client_proto_rawDescData = protoimpl.X.CompressGZIP(file_client_proto_rawDescData)
|
||||
})
|
||||
return file_client_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_client_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_client_proto_goTypes = []interface{}{
|
||||
(*ListClientRequest)(nil), // 0: gmqtt.admin.api.ListClientRequest
|
||||
(*ListClientResponse)(nil), // 1: gmqtt.admin.api.ListClientResponse
|
||||
(*GetClientRequest)(nil), // 2: gmqtt.admin.api.GetClientRequest
|
||||
(*GetClientResponse)(nil), // 3: gmqtt.admin.api.GetClientResponse
|
||||
(*DeleteClientRequest)(nil), // 4: gmqtt.admin.api.DeleteClientRequest
|
||||
(*Client)(nil), // 5: gmqtt.admin.api.Client
|
||||
(*timestamp.Timestamp)(nil), // 6: google.protobuf.Timestamp
|
||||
(*empty.Empty)(nil), // 7: google.protobuf.Empty
|
||||
}
|
||||
var file_client_proto_depIdxs = []int32{
|
||||
5, // 0: gmqtt.admin.api.ListClientResponse.alertclient:type_name -> gmqtt.admin.api.Client
|
||||
5, // 1: gmqtt.admin.api.GetClientResponse.client:type_name -> gmqtt.admin.api.Client
|
||||
6, // 2: gmqtt.admin.api.Client.connected_at:type_name -> google.protobuf.Timestamp
|
||||
6, // 3: gmqtt.admin.api.Client.disconnected_at:type_name -> google.protobuf.Timestamp
|
||||
0, // 4: gmqtt.admin.api.ClientService.List:input_type -> gmqtt.admin.api.ListClientRequest
|
||||
2, // 5: gmqtt.admin.api.ClientService.Get:input_type -> gmqtt.admin.api.GetClientRequest
|
||||
4, // 6: gmqtt.admin.api.ClientService.Delete:input_type -> gmqtt.admin.api.DeleteClientRequest
|
||||
1, // 7: gmqtt.admin.api.ClientService.List:output_type -> gmqtt.admin.api.ListClientResponse
|
||||
3, // 8: gmqtt.admin.api.ClientService.Get:output_type -> gmqtt.admin.api.GetClientResponse
|
||||
7, // 9: gmqtt.admin.api.ClientService.Delete:output_type -> google.protobuf.Empty
|
||||
7, // [7:10] is the sub-list for method output_type
|
||||
4, // [4:7] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_client_proto_init() }
|
||||
func file_client_proto_init() {
|
||||
if File_client_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_client_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListClientRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_client_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListClientResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_client_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetClientRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_client_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetClientResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_client_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeleteClientRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_client_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Client); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_client_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_client_proto_goTypes,
|
||||
DependencyIndexes: file_client_proto_depIdxs,
|
||||
MessageInfos: file_client_proto_msgTypes,
|
||||
}.Build()
|
||||
File_client_proto = out.File
|
||||
file_client_proto_rawDesc = nil
|
||||
file_client_proto_goTypes = nil
|
||||
file_client_proto_depIdxs = nil
|
||||
}
|
|
@ -1,373 +0,0 @@
|
|||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: client.proto
|
||||
|
||||
/*
|
||||
Package admin is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/descriptor"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = descriptor.ForMessage
|
||||
|
||||
var (
|
||||
filter_ClientService_List_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_ClientService_List_0(ctx context.Context, marshaler runtime.Marshaler, client ClientServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListClientRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ClientService_List_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.List(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ClientService_List_0(ctx context.Context, marshaler runtime.Marshaler, server ClientServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListClientRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ClientService_List_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.List(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_ClientService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client ClientServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetClientRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["client_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id")
|
||||
}
|
||||
|
||||
protoReq.ClientId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err)
|
||||
}
|
||||
|
||||
msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ClientService_Get_0(ctx context.Context, marshaler runtime.Marshaler, server ClientServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetClientRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["client_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id")
|
||||
}
|
||||
|
||||
protoReq.ClientId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err)
|
||||
}
|
||||
|
||||
msg, err := server.Get(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_ClientService_Delete_0 = &utilities.DoubleArray{Encoding: map[string]int{"client_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||
)
|
||||
|
||||
func request_ClientService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, client ClientServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq DeleteClientRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["client_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id")
|
||||
}
|
||||
|
||||
protoReq.ClientId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ClientService_Delete_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Delete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ClientService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, server ClientServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq DeleteClientRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["client_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id")
|
||||
}
|
||||
|
||||
protoReq.ClientId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err)
|
||||
}
|
||||
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ClientService_Delete_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Delete(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterClientServiceHandlerServer registers the http handlers for service ClientService to "mux".
|
||||
// UnaryRPC :call ClientServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
func RegisterClientServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ClientServiceServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_ClientService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ClientService_List_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ClientService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_ClientService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ClientService_Get_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ClientService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("DELETE", pattern_ClientService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ClientService_Delete_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ClientService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterClientServiceHandlerFromEndpoint is same as RegisterClientServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterClientServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterClientServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterClientServiceHandler registers the http handlers for service ClientService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterClientServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterClientServiceHandlerClient(ctx, mux, NewClientServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterClientServiceHandlerClient registers the http handlers for service ClientService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ClientServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ClientServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "ClientServiceClient" to call the correct interceptors.
|
||||
func RegisterClientServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ClientServiceClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_ClientService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ClientService_List_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ClientService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_ClientService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ClientService_Get_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ClientService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("DELETE", pattern_ClientService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ClientService_Delete_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ClientService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_ClientService_List_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "alertclient"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_ClientService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "alertclient", "client_id"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_ClientService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "alertclient", "client_id"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_ClientService_List_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ClientService_Get_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ClientService_Delete_0 = runtime.ForwardResponseMessage
|
||||
)
|
|
@ -1,179 +0,0 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
empty "github.com/golang/protobuf/ptypes/empty"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// ClientServiceClient is the client API for ClientService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ClientServiceClient interface {
|
||||
// List alertclient
|
||||
List(ctx context.Context, in *ListClientRequest, opts ...grpc.CallOption) (*ListClientResponse, error)
|
||||
// Get the client for given client id.
|
||||
// Return NotFound error when client not found.
|
||||
Get(ctx context.Context, in *GetClientRequest, opts ...grpc.CallOption) (*GetClientResponse, error)
|
||||
// Disconnect the client for given client id.
|
||||
Delete(ctx context.Context, in *DeleteClientRequest, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
}
|
||||
|
||||
type clientServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewClientServiceClient(cc grpc.ClientConnInterface) ClientServiceClient {
|
||||
return &clientServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *clientServiceClient) List(ctx context.Context, in *ListClientRequest, opts ...grpc.CallOption) (*ListClientResponse, error) {
|
||||
out := new(ListClientResponse)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.ClientService/List", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clientServiceClient) Get(ctx context.Context, in *GetClientRequest, opts ...grpc.CallOption) (*GetClientResponse, error) {
|
||||
out := new(GetClientResponse)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.ClientService/Get", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clientServiceClient) Delete(ctx context.Context, in *DeleteClientRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
|
||||
out := new(empty.Empty)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.ClientService/Delete", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ClientServiceServer is the server API for ClientService service.
|
||||
// All implementations must embed UnimplementedClientServiceServer
|
||||
// for forward compatibility
|
||||
type ClientServiceServer interface {
|
||||
// List alertclient
|
||||
List(context.Context, *ListClientRequest) (*ListClientResponse, error)
|
||||
// Get the client for given client id.
|
||||
// Return NotFound error when client not found.
|
||||
Get(context.Context, *GetClientRequest) (*GetClientResponse, error)
|
||||
// Disconnect the client for given client id.
|
||||
Delete(context.Context, *DeleteClientRequest) (*empty.Empty, error)
|
||||
mustEmbedUnimplementedClientServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedClientServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedClientServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedClientServiceServer) List(context.Context, *ListClientRequest) (*ListClientResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||
}
|
||||
func (UnimplementedClientServiceServer) Get(context.Context, *GetClientRequest) (*GetClientResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
|
||||
}
|
||||
func (UnimplementedClientServiceServer) Delete(context.Context, *DeleteClientRequest) (*empty.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
|
||||
}
|
||||
func (UnimplementedClientServiceServer) mustEmbedUnimplementedClientServiceServer() {}
|
||||
|
||||
// UnsafeClientServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ClientServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeClientServiceServer interface {
|
||||
mustEmbedUnimplementedClientServiceServer()
|
||||
}
|
||||
|
||||
func RegisterClientServiceServer(s grpc.ServiceRegistrar, srv ClientServiceServer) {
|
||||
s.RegisterService(&_ClientService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _ClientService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListClientRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientServiceServer).List(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.ClientService/List",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientServiceServer).List(ctx, req.(*ListClientRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ClientService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetClientRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientServiceServer).Get(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.ClientService/Get",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientServiceServer).Get(ctx, req.(*GetClientRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ClientService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteClientRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientServiceServer).Delete(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.ClientService/Delete",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientServiceServer).Delete(ctx, req.(*DeleteClientRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _ClientService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "gmqtt.admin.api.ClientService",
|
||||
HandlerType: (*ClientServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _ClientService_List_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Get",
|
||||
Handler: _ClientService_Get_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Delete",
|
||||
Handler: _ClientService_Delete_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "client.proto",
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
var mockConfig = config.Config{
|
||||
MQTT: config.MQTT{
|
||||
MaxQueuedMsg: 10,
|
||||
},
|
||||
}
|
||||
|
||||
type dummyConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (d *dummyConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
func (d *dummyConn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
|
||||
func TestClientService_List_Get(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
cs := server.NewMockClientService(ctrl)
|
||||
sr := server.NewMockStatsReader(ctrl)
|
||||
|
||||
admin := &Admin{
|
||||
statsReader: sr,
|
||||
clientService: cs,
|
||||
store: newStore(sr, mockConfig),
|
||||
}
|
||||
c := &clientService{
|
||||
a: admin,
|
||||
}
|
||||
now := time.Now()
|
||||
client := server.NewMockClient(ctrl)
|
||||
client.EXPECT().Version().Return(packets.Version5).AnyTimes()
|
||||
client.EXPECT().Connection().Return(&dummyConn{}).AnyTimes()
|
||||
client.EXPECT().ConnectedAt().Return(now).AnyTimes()
|
||||
created := admin.OnSessionCreatedWrapper(func(ctx context.Context, client server.Client) {})
|
||||
for i := 0; i < 10; i++ {
|
||||
sr.EXPECT().GetClientStats(strconv.Itoa(i)).AnyTimes()
|
||||
client.EXPECT().ClientOptions().Return(&server.ClientOptions{
|
||||
ClientID: strconv.Itoa(i),
|
||||
Username: strconv.Itoa(i),
|
||||
KeepAlive: uint16(i),
|
||||
SessionExpiry: uint32(i),
|
||||
MaxInflight: uint16(i),
|
||||
ReceiveMax: uint16(i),
|
||||
ClientMaxPacketSize: uint32(i),
|
||||
ServerMaxPacketSize: uint32(i),
|
||||
ClientTopicAliasMax: uint16(i),
|
||||
ServerTopicAliasMax: uint16(i),
|
||||
RequestProblemInfo: true,
|
||||
UserProperties: []*packets.UserProperty{
|
||||
{
|
||||
K: []byte{1, 2},
|
||||
V: []byte{1, 2},
|
||||
},
|
||||
},
|
||||
RetainAvailable: true,
|
||||
WildcardSubAvailable: true,
|
||||
SubIDAvailable: true,
|
||||
SharedSubAvailable: true,
|
||||
})
|
||||
created(context.Background(), client)
|
||||
}
|
||||
|
||||
resp, err := c.List(context.Background(), &ListClientRequest{
|
||||
PageSize: 0,
|
||||
Page: 0,
|
||||
})
|
||||
a.Nil(err)
|
||||
a.Len(resp.Clients, 10)
|
||||
for k, v := range resp.Clients {
|
||||
addr := net.TCPAddr{}
|
||||
a.Equal(&Client{
|
||||
ClientId: strconv.Itoa(k),
|
||||
Username: strconv.Itoa(k),
|
||||
KeepAlive: int32(k),
|
||||
Version: int32(packets.Version5),
|
||||
RemoteAddr: addr.String(),
|
||||
LocalAddr: addr.String(),
|
||||
ConnectedAt: timestamppb.New(now),
|
||||
DisconnectedAt: nil,
|
||||
SessionExpiry: uint32(k),
|
||||
MaxInflight: uint32(k),
|
||||
MaxQueue: uint32(mockConfig.MQTT.MaxQueuedMsg),
|
||||
PacketsReceivedBytes: 0,
|
||||
PacketsReceivedNums: 0,
|
||||
PacketsSendBytes: 0,
|
||||
PacketsSendNums: 0,
|
||||
MessageDropped: 0,
|
||||
}, v)
|
||||
}
|
||||
|
||||
getResp, err := c.Get(context.Background(), &GetClientRequest{
|
||||
ClientId: "1",
|
||||
})
|
||||
a.Nil(err)
|
||||
a.Equal(resp.Clients[1], getResp.Client)
|
||||
|
||||
pagingResp, err := c.List(context.Background(), &ListClientRequest{
|
||||
PageSize: 2,
|
||||
Page: 2,
|
||||
})
|
||||
a.Nil(err)
|
||||
a.Len(pagingResp.Clients, 2)
|
||||
a.Equal(resp.Clients[2], pagingResp.Clients[0])
|
||||
a.Equal(resp.Clients[3], pagingResp.Clients[1])
|
||||
}
|
||||
|
||||
func TestClientService_Delete(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
cs := server.NewMockClientService(ctrl)
|
||||
sr := server.NewMockStatsReader(ctrl)
|
||||
|
||||
admin := &Admin{
|
||||
statsReader: sr,
|
||||
clientService: cs,
|
||||
store: newStore(sr, mockConfig),
|
||||
}
|
||||
c := &clientService{
|
||||
a: admin,
|
||||
}
|
||||
client := server.NewMockClient(ctrl)
|
||||
client.EXPECT().Close()
|
||||
cs.EXPECT().GetClient("1").Return(client)
|
||||
_, err := c.Delete(context.Background(), &DeleteClientRequest{
|
||||
ClientId: "1",
|
||||
CleanSession: false,
|
||||
})
|
||||
a.Nil(err)
|
||||
}
|
||||
|
||||
func TestClientService_Delete_CleanSession(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
cs := server.NewMockClientService(ctrl)
|
||||
sr := server.NewMockStatsReader(ctrl)
|
||||
|
||||
admin := &Admin{
|
||||
statsReader: sr,
|
||||
clientService: cs,
|
||||
store: newStore(sr, mockConfig),
|
||||
}
|
||||
c := &clientService{
|
||||
a: admin,
|
||||
}
|
||||
cs.EXPECT().TerminateSession("1")
|
||||
_, err := c.Delete(context.Background(), &DeleteClientRequest{
|
||||
ClientId: "1",
|
||||
CleanSession: true,
|
||||
})
|
||||
a.Nil(err)
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Config is the configuration for the admin plugin.
|
||||
type Config struct {
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
GRPC GRPCConfig `yaml:"grpc"`
|
||||
}
|
||||
|
||||
// HTTPConfig is the configuration for http endpoint.
|
||||
type HTTPConfig struct {
|
||||
// Enable indicates whether to expose http endpoint.
|
||||
Enable bool `yaml:"enable"`
|
||||
// Addr is the address that the http server listen on.
|
||||
Addr string `yaml:"http_addr"`
|
||||
}
|
||||
|
||||
// GRPCConfig is the configuration for gRPC endpoint.
|
||||
type GRPCConfig struct {
|
||||
// Addr is the address that the gRPC server listen on.
|
||||
Addr string `yaml:"http_addr"`
|
||||
}
|
||||
|
||||
// Validate validates the configuration, and return an error if it is invalid.
|
||||
func (c *Config) Validate() error {
|
||||
if c.HTTP.Enable {
|
||||
_, _, err := net.SplitHostPort(c.HTTP.Addr)
|
||||
if err != nil {
|
||||
return errors.New("invalid http_addr")
|
||||
}
|
||||
}
|
||||
_, _, err := net.SplitHostPort(c.GRPC.Addr)
|
||||
if err != nil {
|
||||
return errors.New("invalid grpc_addr")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultConfig is the default configuration.
|
||||
var DefaultConfig = Config{
|
||||
HTTP: HTTPConfig{
|
||||
Enable: true,
|
||||
Addr: "127.0.0.1:57091",
|
||||
},
|
||||
GRPC: GRPCConfig{
|
||||
Addr: "unix://./mqttd.sock",
|
||||
},
|
||||
}
|
||||
|
||||
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type cfg Config
|
||||
var v = &struct {
|
||||
Admin cfg `yaml:"admin"`
|
||||
}{
|
||||
Admin: cfg(DefaultConfig),
|
||||
}
|
||||
if err := unmarshal(v); err != nil {
|
||||
return err
|
||||
}
|
||||
emptyGRPC := GRPCConfig{}
|
||||
if v.Admin.GRPC == emptyGRPC {
|
||||
v.Admin.GRPC = DefaultConfig.GRPC
|
||||
}
|
||||
emptyHTTP := HTTPConfig{}
|
||||
if v.Admin.HTTP == emptyHTTP {
|
||||
v.Admin.HTTP = DefaultConfig.HTTP
|
||||
}
|
||||
empty := cfg(Config{})
|
||||
if v.Admin == empty {
|
||||
v.Admin = cfg(DefaultConfig)
|
||||
}
|
||||
*c = Config(v.Admin)
|
||||
return nil
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
func (a *Admin) HookWrapper() server.HookWrapper {
|
||||
return server.HookWrapper{
|
||||
OnSessionCreatedWrapper: a.OnSessionCreatedWrapper,
|
||||
OnSessionResumedWrapper: a.OnSessionResumedWrapper,
|
||||
OnClosedWrapper: a.OnClosedWrapper,
|
||||
OnSessionTerminatedWrapper: a.OnSessionTerminatedWrapper,
|
||||
OnSubscribedWrapper: a.OnSubscribedWrapper,
|
||||
OnUnsubscribedWrapper: a.OnUnsubscribedWrapper,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) OnSessionCreatedWrapper(pre server.OnSessionCreated) server.OnSessionCreated {
|
||||
return func(ctx context.Context, client server.Client) {
|
||||
pre(ctx, client)
|
||||
a.store.addClient(client)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) OnSessionResumedWrapper(pre server.OnSessionResumed) server.OnSessionResumed {
|
||||
return func(ctx context.Context, client server.Client) {
|
||||
pre(ctx, client)
|
||||
a.store.addClient(client)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) OnClosedWrapper(pre server.OnClosed) server.OnClosed {
|
||||
return func(ctx context.Context, client server.Client, err error) {
|
||||
pre(ctx, client, err)
|
||||
a.store.setClientDisconnected(client.ClientOptions().ClientID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) OnSessionTerminatedWrapper(pre server.OnSessionTerminated) server.OnSessionTerminated {
|
||||
return func(ctx context.Context, clientID string, reason server.SessionTerminatedReason) {
|
||||
pre(ctx, clientID, reason)
|
||||
a.store.removeClient(clientID)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) OnSubscribedWrapper(pre server.OnSubscribed) server.OnSubscribed {
|
||||
return func(ctx context.Context, client server.Client, subscription *gmqtt.Subscription) {
|
||||
pre(ctx, client, subscription)
|
||||
a.store.addSubscription(client.ClientOptions().ClientID, subscription)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) OnUnsubscribedWrapper(pre server.OnUnsubscribed) server.OnUnsubscribed {
|
||||
return func(ctx context.Context, client server.Client, topicName string) {
|
||||
pre(ctx, client, topicName)
|
||||
a.store.removeSubscription(client.ClientOptions().ClientID, topicName)
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package gmqtt.admin.api;
|
||||
option go_package = ".;admin";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
message ListClientRequest {
|
||||
uint32 page_size = 1;
|
||||
uint32 page = 2;
|
||||
}
|
||||
|
||||
message ListClientResponse {
|
||||
repeated Client clients = 1;
|
||||
uint32 total_count = 2;
|
||||
}
|
||||
|
||||
message GetClientRequest {
|
||||
string client_id = 1;
|
||||
}
|
||||
|
||||
message GetClientResponse {
|
||||
Client client = 1;
|
||||
}
|
||||
|
||||
|
||||
message DeleteClientRequest {
|
||||
string client_id = 1;
|
||||
bool clean_session = 2;
|
||||
}
|
||||
|
||||
message Client {
|
||||
string client_id = 1;
|
||||
string username = 2;
|
||||
int32 keep_alive = 3;
|
||||
int32 version = 4;
|
||||
string remote_addr = 5;
|
||||
string local_addr = 6;
|
||||
google.protobuf.Timestamp connected_at = 7;
|
||||
google.protobuf.Timestamp disconnected_at = 8;
|
||||
uint32 session_expiry = 9;
|
||||
uint32 max_inflight = 10;
|
||||
uint32 inflight_len = 11;
|
||||
uint32 max_queue = 12;
|
||||
uint32 queue_len = 13;
|
||||
uint32 subscriptions_current = 14;
|
||||
uint32 subscriptions_total = 15;
|
||||
uint64 packets_received_bytes = 16;
|
||||
uint64 packets_received_nums = 17;
|
||||
uint64 packets_send_bytes = 18;
|
||||
uint64 packets_send_nums = 19;
|
||||
uint64 message_dropped = 20;
|
||||
}
|
||||
|
||||
|
||||
service ClientService {
|
||||
// List clients
|
||||
rpc List (ListClientRequest) returns (ListClientResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/v1/clients"
|
||||
};
|
||||
}
|
||||
// Get the client for given client id.
|
||||
// Return NotFound error when client not found.
|
||||
rpc Get (GetClientRequest) returns (GetClientResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/v1/clients/{client_id}"
|
||||
};
|
||||
}
|
||||
// Disconnect the client for given client id.
|
||||
rpc Delete (DeleteClientRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/clients/{client_id}"
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
protoc -I. \
|
||||
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway \
|
||||
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
|
||||
--go-grpc_out=../ \
|
||||
--go_out=../ \
|
||||
--grpc-gateway_out=../ \
|
||||
--swagger_out=../swagger \
|
||||
*.proto
|
|
@ -1,36 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package gmqtt.admin.api;
|
||||
option go_package = ".;admin";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
message PublishRequest {
|
||||
string topic_name = 1;
|
||||
string payload = 2;
|
||||
uint32 qos = 3;
|
||||
bool retained = 4;
|
||||
// the following fields are using in v5 client.
|
||||
string content_type = 5;
|
||||
string correlation_data = 6;
|
||||
uint32 message_expiry = 7;
|
||||
uint32 payload_format = 8;
|
||||
string response_topic = 9;
|
||||
repeated UserProperties user_properties = 10;
|
||||
}
|
||||
|
||||
message UserProperties {
|
||||
bytes K = 1;
|
||||
bytes V = 2;
|
||||
}
|
||||
|
||||
service PublishService {
|
||||
// Publish message to broker
|
||||
rpc Publish (PublishRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
post: "/v1/publish"
|
||||
body:"*"
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package gmqtt.admin.api;
|
||||
option go_package = ".;admin";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
enum SubFilterType {
|
||||
SUB_FILTER_TYPE_SYS_UNSPECIFIED = 0;
|
||||
SUB_FILTER_TYPE_SYS = 1;
|
||||
SUB_FILTER_TYPE_SHARED = 2;
|
||||
SUB_FILTER_TYPE_NON_SHARED = 3;
|
||||
}
|
||||
enum SubMatchType {
|
||||
SUB_MATCH_TYPE_MATCH_UNSPECIFIED = 0;
|
||||
SUB_MATCH_TYPE_MATCH_NAME = 1;
|
||||
SUB_MATCH_TYPE_MATCH_FILTER = 2;
|
||||
}
|
||||
|
||||
message ListSubscriptionRequest {
|
||||
uint32 page_size = 1;
|
||||
uint32 page = 2;
|
||||
}
|
||||
|
||||
message ListSubscriptionResponse {
|
||||
repeated Subscription subscriptions = 1;
|
||||
uint32 total_count = 2;
|
||||
}
|
||||
message FilterSubscriptionRequest {
|
||||
// If set, only filter the subscriptions that belongs to the client.
|
||||
string client_id = 1;
|
||||
// filter_type indicates what kinds of topics are going to filter.
|
||||
// If there are multiple types, use ',' to separate. e.g : 1,2
|
||||
// There are 3 kinds of topic can be filtered, defined by SubFilterType:
|
||||
// 1 = System Topic(begin with '$')
|
||||
// 2 = Shared Topic
|
||||
// 3 = NonShared Topic
|
||||
string filter_type = 2;
|
||||
// If 1 (SUB_MATCH_TYPE_MATCH_NAME), the server will return subscriptions which has the same topic name with request topic_name.
|
||||
// If 2 (SUB_MATCH_TYPE_MATCH_FILTER),the server will return subscriptions which match the request topic_name .
|
||||
// match_type must be set when filter_type is not empty.
|
||||
SubMatchType match_type = 3;
|
||||
// topic_name must be set when match_type is not zero.
|
||||
string topic_name = 4;
|
||||
// The maximum subscriptions can be returned.
|
||||
int32 limit = 5;
|
||||
}
|
||||
message FilterSubscriptionResponse {
|
||||
repeated Subscription subscriptions = 1;
|
||||
}
|
||||
|
||||
message SubscribeRequest {
|
||||
string client_id = 1;
|
||||
repeated Subscription subscriptions = 2;
|
||||
}
|
||||
|
||||
message SubscribeResponse {
|
||||
// indicates whether it is a new subscription or the subscription is already existed.
|
||||
repeated bool new = 1;
|
||||
}
|
||||
|
||||
message UnsubscribeRequest {
|
||||
string client_id = 1;
|
||||
repeated string topics = 2;
|
||||
}
|
||||
|
||||
message Subscription {
|
||||
string topic_name = 1;
|
||||
uint32 id = 2;
|
||||
uint32 qos = 3;
|
||||
bool no_local = 4;
|
||||
bool retain_as_published = 5;
|
||||
uint32 retain_handling = 6;
|
||||
string client_id = 7;
|
||||
}
|
||||
service SubscriptionService {
|
||||
// List subscriptions.
|
||||
rpc List (ListSubscriptionRequest) returns (ListSubscriptionResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/v1/subscriptions"
|
||||
};
|
||||
}
|
||||
// Filter subscriptions, paging is not supported in this API.
|
||||
rpc Filter(FilterSubscriptionRequest) returns (FilterSubscriptionResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/filter_subscriptions"
|
||||
};
|
||||
}
|
||||
// Subscribe topics for the client.
|
||||
rpc Subscribe (SubscribeRequest) returns (SubscribeResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/subscribe"
|
||||
body:"*"
|
||||
};
|
||||
}
|
||||
// Unsubscribe topics for the client.
|
||||
rpc Unsubscribe (UnsubscribeRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/unsubscribe"
|
||||
body:"*"
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
type publisher struct {
|
||||
a *Admin
|
||||
}
|
||||
|
||||
func (p *publisher) mustEmbedUnimplementedPublishServiceServer() {
|
||||
return
|
||||
}
|
||||
|
||||
// Publish publishes a message into broker.
|
||||
func (p *publisher) Publish(ctx context.Context, req *PublishRequest) (resp *empty.Empty, err error) {
|
||||
if !packets.ValidV5Topic([]byte(req.TopicName)) {
|
||||
return nil, ErrInvalidArgument("topic_name", "")
|
||||
}
|
||||
if req.Qos > uint32(packets.Qos2) {
|
||||
return nil, ErrInvalidArgument("qos", "")
|
||||
}
|
||||
if req.PayloadFormat != 0 && req.PayloadFormat != 1 {
|
||||
return nil, ErrInvalidArgument("payload_format", "")
|
||||
}
|
||||
if req.ResponseTopic != "" && !packets.ValidV5Topic([]byte(req.ResponseTopic)) {
|
||||
return nil, ErrInvalidArgument("response_topic", "")
|
||||
}
|
||||
var userPpt []packets.UserProperty
|
||||
for _, v := range req.UserProperties {
|
||||
userPpt = append(userPpt, packets.UserProperty{
|
||||
K: v.K,
|
||||
V: v.V,
|
||||
})
|
||||
}
|
||||
|
||||
p.a.publisher.Publish(&mqttbroker.Message{
|
||||
Dup: false,
|
||||
QoS: byte(req.Qos),
|
||||
Retained: req.Retained,
|
||||
Topic: req.TopicName,
|
||||
Payload: []byte(req.Payload),
|
||||
ContentType: req.ContentType,
|
||||
CorrelationData: []byte(req.CorrelationData),
|
||||
MessageExpiry: req.MessageExpiry,
|
||||
PayloadFormat: packets.PayloadFormat(req.PayloadFormat),
|
||||
ResponseTopic: req.ResponseTopic,
|
||||
UserProperties: userPpt,
|
||||
})
|
||||
return &empty.Empty{}, nil
|
||||
}
|
|
@ -1,331 +0,0 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.22.0
|
||||
// protoc v3.13.0
|
||||
// source: publish.proto
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
empty "github.com/golang/protobuf/ptypes/empty"
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type PublishRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
TopicName string `protobuf:"bytes,1,opt,name=topic_name,json=topicName,proto3" json:"topic_name,omitempty"`
|
||||
Payload string `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
Qos uint32 `protobuf:"varint,3,opt,name=qos,proto3" json:"qos,omitempty"`
|
||||
Retained bool `protobuf:"varint,4,opt,name=retained,proto3" json:"retained,omitempty"`
|
||||
// the following fields are using in v5 client.
|
||||
ContentType string `protobuf:"bytes,5,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
CorrelationData string `protobuf:"bytes,6,opt,name=correlation_data,json=correlationData,proto3" json:"correlation_data,omitempty"`
|
||||
MessageExpiry uint32 `protobuf:"varint,7,opt,name=message_expiry,json=messageExpiry,proto3" json:"message_expiry,omitempty"`
|
||||
PayloadFormat uint32 `protobuf:"varint,8,opt,name=payload_format,json=payloadFormat,proto3" json:"payload_format,omitempty"`
|
||||
ResponseTopic string `protobuf:"bytes,9,opt,name=response_topic,json=responseTopic,proto3" json:"response_topic,omitempty"`
|
||||
UserProperties []*UserProperties `protobuf:"bytes,10,rep,name=user_properties,json=userProperties,proto3" json:"user_properties,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PublishRequest) Reset() {
|
||||
*x = PublishRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_publish_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PublishRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PublishRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PublishRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_publish_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PublishRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PublishRequest) Descriptor() ([]byte, []int) {
|
||||
return file_publish_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetTopicName() string {
|
||||
if x != nil {
|
||||
return x.TopicName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetPayload() string {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetQos() uint32 {
|
||||
if x != nil {
|
||||
return x.Qos
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetRetained() bool {
|
||||
if x != nil {
|
||||
return x.Retained
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetContentType() string {
|
||||
if x != nil {
|
||||
return x.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetCorrelationData() string {
|
||||
if x != nil {
|
||||
return x.CorrelationData
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetMessageExpiry() uint32 {
|
||||
if x != nil {
|
||||
return x.MessageExpiry
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetPayloadFormat() uint32 {
|
||||
if x != nil {
|
||||
return x.PayloadFormat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetResponseTopic() string {
|
||||
if x != nil {
|
||||
return x.ResponseTopic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PublishRequest) GetUserProperties() []*UserProperties {
|
||||
if x != nil {
|
||||
return x.UserProperties
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserProperties struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
K []byte `protobuf:"bytes,1,opt,name=K,proto3" json:"K,omitempty"`
|
||||
V []byte `protobuf:"bytes,2,opt,name=V,proto3" json:"V,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UserProperties) Reset() {
|
||||
*x = UserProperties{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_publish_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UserProperties) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UserProperties) ProtoMessage() {}
|
||||
|
||||
func (x *UserProperties) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_publish_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UserProperties.ProtoReflect.Descriptor instead.
|
||||
func (*UserProperties) Descriptor() ([]byte, []int) {
|
||||
return file_publish_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *UserProperties) GetK() []byte {
|
||||
if x != nil {
|
||||
return x.K
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UserProperties) GetV() []byte {
|
||||
if x != nil {
|
||||
return x.V
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_publish_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_publish_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x0f, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e,
|
||||
0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
|
||||
0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x84, 0x03, 0x0a, 0x0e,
|
||||
0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d,
|
||||
0x0a, 0x0a, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x71, 0x6f, 0x73, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x71, 0x6f, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x74,
|
||||
0x61, 0x69, 0x6e, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x74,
|
||||
0x61, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
|
||||
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x72, 0x72,
|
||||
0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44,
|
||||
0x61, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x65,
|
||||
0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61,
|
||||
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61,
|
||||
0x74, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x6f,
|
||||
0x70, 0x69, 0x63, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69,
|
||||
0x65, 0x73, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69,
|
||||
0x65, 0x73, 0x22, 0x2c, 0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72,
|
||||
0x74, 0x69, 0x65, 0x73, 0x12, 0x0c, 0x0a, 0x01, 0x4b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x01, 0x4b, 0x12, 0x0c, 0x0a, 0x01, 0x56, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x56,
|
||||
0x32, 0x6c, 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x12, 0x5a, 0x0a, 0x07, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x1f, 0x2e,
|
||||
0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x22, 0x0b,
|
||||
0x2f, 0x76, 0x31, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x3a, 0x01, 0x2a, 0x42, 0x09,
|
||||
0x5a, 0x07, 0x2e, 0x3b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_publish_proto_rawDescOnce sync.Once
|
||||
file_publish_proto_rawDescData = file_publish_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_publish_proto_rawDescGZIP() []byte {
|
||||
file_publish_proto_rawDescOnce.Do(func() {
|
||||
file_publish_proto_rawDescData = protoimpl.X.CompressGZIP(file_publish_proto_rawDescData)
|
||||
})
|
||||
return file_publish_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_publish_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_publish_proto_goTypes = []interface{}{
|
||||
(*PublishRequest)(nil), // 0: gmqtt.admin.api.PublishRequest
|
||||
(*UserProperties)(nil), // 1: gmqtt.admin.api.UserProperties
|
||||
(*empty.Empty)(nil), // 2: google.protobuf.Empty
|
||||
}
|
||||
var file_publish_proto_depIdxs = []int32{
|
||||
1, // 0: gmqtt.admin.api.PublishRequest.user_properties:type_name -> gmqtt.admin.api.UserProperties
|
||||
0, // 1: gmqtt.admin.api.PublishService.Publish:input_type -> gmqtt.admin.api.PublishRequest
|
||||
2, // 2: gmqtt.admin.api.PublishService.Publish:output_type -> google.protobuf.Empty
|
||||
2, // [2:3] is the sub-list for method output_type
|
||||
1, // [1:2] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_publish_proto_init() }
|
||||
func file_publish_proto_init() {
|
||||
if File_publish_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_publish_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PublishRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_publish_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UserProperties); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_publish_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_publish_proto_goTypes,
|
||||
DependencyIndexes: file_publish_proto_depIdxs,
|
||||
MessageInfos: file_publish_proto_msgTypes,
|
||||
}.Build()
|
||||
File_publish_proto = out.File
|
||||
file_publish_proto_rawDesc = nil
|
||||
file_publish_proto_goTypes = nil
|
||||
file_publish_proto_depIdxs = nil
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: publish.proto
|
||||
|
||||
/*
|
||||
Package admin is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/descriptor"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = descriptor.ForMessage
|
||||
|
||||
func request_PublishService_Publish_0(ctx context.Context, marshaler runtime.Marshaler, client PublishServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq PublishRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Publish(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_PublishService_Publish_0(ctx context.Context, marshaler runtime.Marshaler, server PublishServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq PublishRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Publish(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterPublishServiceHandlerServer registers the http handlers for service PublishService to "mux".
|
||||
// UnaryRPC :call PublishServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
func RegisterPublishServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server PublishServiceServer) error {
|
||||
|
||||
mux.Handle("POST", pattern_PublishService_Publish_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_PublishService_Publish_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PublishService_Publish_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterPublishServiceHandlerFromEndpoint is same as RegisterPublishServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterPublishServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterPublishServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterPublishServiceHandler registers the http handlers for service PublishService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterPublishServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterPublishServiceHandlerClient(ctx, mux, NewPublishServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterPublishServiceHandlerClient registers the http handlers for service PublishService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "PublishServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "PublishServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "PublishServiceClient" to call the correct interceptors.
|
||||
func RegisterPublishServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client PublishServiceClient) error {
|
||||
|
||||
mux.Handle("POST", pattern_PublishService_Publish_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PublishService_Publish_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PublishService_Publish_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_PublishService_Publish_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "publish"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_PublishService_Publish_0 = runtime.ForwardResponseMessage
|
||||
)
|
|
@ -1,101 +0,0 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
empty "github.com/golang/protobuf/ptypes/empty"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// PublishServiceClient is the client API for PublishService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type PublishServiceClient interface {
|
||||
// Publish message to broker
|
||||
Publish(ctx context.Context, in *PublishRequest, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
}
|
||||
|
||||
type publishServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewPublishServiceClient(cc grpc.ClientConnInterface) PublishServiceClient {
|
||||
return &publishServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *publishServiceClient) Publish(ctx context.Context, in *PublishRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
|
||||
out := new(empty.Empty)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.PublishService/Publish", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// PublishServiceServer is the server API for PublishService service.
|
||||
// All implementations must embed UnimplementedPublishServiceServer
|
||||
// for forward compatibility
|
||||
type PublishServiceServer interface {
|
||||
// Publish message to broker
|
||||
Publish(context.Context, *PublishRequest) (*empty.Empty, error)
|
||||
mustEmbedUnimplementedPublishServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedPublishServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedPublishServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedPublishServiceServer) Publish(context.Context, *PublishRequest) (*empty.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Publish not implemented")
|
||||
}
|
||||
func (UnimplementedPublishServiceServer) mustEmbedUnimplementedPublishServiceServer() {}
|
||||
|
||||
// UnsafePublishServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to PublishServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafePublishServiceServer interface {
|
||||
mustEmbedUnimplementedPublishServiceServer()
|
||||
}
|
||||
|
||||
func RegisterPublishServiceServer(s grpc.ServiceRegistrar, srv PublishServiceServer) {
|
||||
s.RegisterService(&_PublishService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _PublishService_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PublishRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(PublishServiceServer).Publish(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.PublishService/Publish",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(PublishServiceServer).Publish(ctx, req.(*PublishRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _PublishService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "gmqtt.admin.api.PublishService",
|
||||
HandlerType: (*PublishServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Publish",
|
||||
Handler: _PublishService_Publish_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "publish.proto",
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
func TestPublisher_Publish(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mp := server.NewMockPublisher(ctrl)
|
||||
pub := &publisher{
|
||||
a: &Admin{
|
||||
publisher: mp,
|
||||
},
|
||||
}
|
||||
msg := &gmqtt.Message{
|
||||
QoS: 1,
|
||||
Retained: true,
|
||||
Topic: "topic",
|
||||
Payload: []byte("abc"),
|
||||
ContentType: "ct",
|
||||
CorrelationData: []byte("co"),
|
||||
MessageExpiry: 1,
|
||||
PayloadFormat: 1,
|
||||
ResponseTopic: "resp",
|
||||
UserProperties: []packets.UserProperty{
|
||||
{
|
||||
K: []byte("K"),
|
||||
V: []byte("V"),
|
||||
},
|
||||
},
|
||||
}
|
||||
mp.EXPECT().Publish(msg)
|
||||
_, err := pub.Publish(context.Background(), &PublishRequest{
|
||||
TopicName: msg.Topic,
|
||||
Payload: string(msg.Payload),
|
||||
Qos: uint32(msg.QoS),
|
||||
Retained: msg.Retained,
|
||||
ContentType: msg.ContentType,
|
||||
CorrelationData: string(msg.CorrelationData),
|
||||
MessageExpiry: msg.MessageExpiry,
|
||||
PayloadFormat: uint32(msg.PayloadFormat),
|
||||
ResponseTopic: msg.ResponseTopic,
|
||||
UserProperties: []*UserProperties{
|
||||
{
|
||||
K: []byte("K"),
|
||||
V: []byte("V"),
|
||||
},
|
||||
},
|
||||
})
|
||||
a.Nil(err)
|
||||
}
|
||||
|
||||
func TestPublisher_Publish_InvalidArgument(t *testing.T) {
|
||||
var tt = []struct {
|
||||
name string
|
||||
field string
|
||||
req *PublishRequest
|
||||
}{
|
||||
{
|
||||
name: "invalid_topic_name",
|
||||
field: "topic_name",
|
||||
req: &PublishRequest{
|
||||
TopicName: "$share/a",
|
||||
Qos: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_qos",
|
||||
field: "qos",
|
||||
req: &PublishRequest{
|
||||
TopicName: "a",
|
||||
Qos: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_payload_format",
|
||||
field: "payload_format",
|
||||
req: &PublishRequest{
|
||||
TopicName: "a",
|
||||
Qos: 2,
|
||||
PayloadFormat: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_response_topic",
|
||||
field: "response_topic",
|
||||
req: &PublishRequest{
|
||||
TopicName: "a",
|
||||
Qos: 2,
|
||||
PayloadFormat: 1,
|
||||
ResponseTopic: "#/",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, v := range tt {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mp := server.NewMockPublisher(ctrl)
|
||||
pub := &publisher{
|
||||
a: &Admin{
|
||||
publisher: mp,
|
||||
},
|
||||
}
|
||||
_, err := pub.Publish(context.Background(), v.req)
|
||||
s, ok := status.FromError(err)
|
||||
a.True(ok)
|
||||
a.Contains(s.Message(), v.field)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
clientMu sync.RWMutex
|
||||
clientIndexer *Indexer
|
||||
subMu sync.RWMutex
|
||||
subIndexer *Indexer
|
||||
config config.Config
|
||||
statsReader server.StatsReader
|
||||
subscriptionService server.SubscriptionService
|
||||
}
|
||||
|
||||
func newStore(statsReader server.StatsReader, config config.Config) *store {
|
||||
return &store{
|
||||
clientIndexer: NewIndexer(),
|
||||
subIndexer: NewIndexer(),
|
||||
statsReader: statsReader,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) addSubscription(clientID string, sub *gmqtt.Subscription) {
|
||||
s.subMu.Lock()
|
||||
defer s.subMu.Unlock()
|
||||
|
||||
subInfo := &Subscription{
|
||||
TopicName: sub.GetFullTopicName(),
|
||||
Id: sub.ID,
|
||||
Qos: uint32(sub.QoS),
|
||||
NoLocal: sub.NoLocal,
|
||||
RetainAsPublished: sub.RetainAsPublished,
|
||||
RetainHandling: uint32(sub.RetainHandling),
|
||||
ClientId: clientID,
|
||||
}
|
||||
key := clientID + "_" + sub.GetFullTopicName()
|
||||
s.subIndexer.Set(key, subInfo)
|
||||
|
||||
}
|
||||
|
||||
func (s *store) removeSubscription(clientID string, topicName string) {
|
||||
s.subMu.Lock()
|
||||
defer s.subMu.Unlock()
|
||||
s.subIndexer.Remove(clientID + "_" + topicName)
|
||||
}
|
||||
|
||||
func (s *store) addClient(client server.Client) {
|
||||
c := newClientInfo(client, uint32(s.config.MQTT.MaxQueuedMsg))
|
||||
s.clientMu.Lock()
|
||||
s.clientIndexer.Set(c.ClientId, c)
|
||||
s.clientMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *store) setClientDisconnected(clientID string) {
|
||||
s.clientMu.Lock()
|
||||
defer s.clientMu.Unlock()
|
||||
l := s.clientIndexer.GetByID(clientID)
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
l.Value.(*Client).DisconnectedAt = timestamppb.Now()
|
||||
}
|
||||
|
||||
func (s *store) removeClient(clientID string) {
|
||||
s.clientMu.Lock()
|
||||
s.clientIndexer.Remove(clientID)
|
||||
s.clientMu.Unlock()
|
||||
}
|
||||
|
||||
// GetClientByID returns the client information for the given client id.
|
||||
func (s *store) GetClientByID(clientID string) *Client {
|
||||
s.clientMu.RLock()
|
||||
defer s.clientMu.RUnlock()
|
||||
c := s.getClientByIDLocked(clientID)
|
||||
fillClientInfo(c, s.statsReader)
|
||||
return c
|
||||
}
|
||||
|
||||
func newClientInfo(client server.Client, maxQueue uint32) *Client {
|
||||
clientOptions := client.ClientOptions()
|
||||
rs := &Client{
|
||||
ClientId: clientOptions.ClientID,
|
||||
Username: clientOptions.Username,
|
||||
KeepAlive: int32(clientOptions.KeepAlive),
|
||||
Version: int32(client.Version()),
|
||||
RemoteAddr: client.Connection().RemoteAddr().String(),
|
||||
LocalAddr: client.Connection().LocalAddr().String(),
|
||||
ConnectedAt: timestamppb.New(client.ConnectedAt()),
|
||||
DisconnectedAt: nil,
|
||||
SessionExpiry: clientOptions.SessionExpiry,
|
||||
MaxInflight: uint32(clientOptions.MaxInflight),
|
||||
MaxQueue: maxQueue,
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func (s *store) getClientByIDLocked(clientID string) *Client {
|
||||
if i := s.clientIndexer.GetByID(clientID); i != nil {
|
||||
return i.Value.(*Client)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fillClientInfo(c *Client, stsReader server.StatsReader) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
sts, ok := stsReader.GetClientStats(c.ClientId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.SubscriptionsCurrent = uint32(sts.SubscriptionStats.SubscriptionsCurrent)
|
||||
c.SubscriptionsTotal = uint32(sts.SubscriptionStats.SubscriptionsTotal)
|
||||
c.PacketsReceivedBytes = sts.PacketStats.BytesReceived.Total
|
||||
c.PacketsReceivedNums = sts.PacketStats.ReceivedTotal.Total
|
||||
c.PacketsSendBytes = sts.PacketStats.BytesSent.Total
|
||||
c.PacketsSendNums = sts.PacketStats.SentTotal.Total
|
||||
c.MessageDropped = sts.MessageStats.GetDroppedTotal()
|
||||
c.InflightLen = uint32(sts.MessageStats.InflightCurrent)
|
||||
c.QueueLen = uint32(sts.MessageStats.QueuedCurrent)
|
||||
}
|
||||
|
||||
// GetClients
|
||||
func (s *store) GetClients(page, pageSize uint) (rs []*Client, total uint32, err error) {
|
||||
rs = make([]*Client, 0)
|
||||
fn := func(elem *list.Element) {
|
||||
c := elem.Value.(*Client)
|
||||
fillClientInfo(c, s.statsReader)
|
||||
rs = append(rs, elem.Value.(*Client))
|
||||
}
|
||||
s.clientMu.RLock()
|
||||
defer s.clientMu.RUnlock()
|
||||
offset, n := GetOffsetN(page, pageSize)
|
||||
s.clientIndexer.Iterate(fn, offset, n)
|
||||
return rs, uint32(s.clientIndexer.Len()), nil
|
||||
}
|
||||
|
||||
// GetSubscriptions
|
||||
func (s *store) GetSubscriptions(page, pageSize uint) (rs []*Subscription, total uint32, err error) {
|
||||
rs = make([]*Subscription, 0)
|
||||
fn := func(elem *list.Element) {
|
||||
rs = append(rs, elem.Value.(*Subscription))
|
||||
}
|
||||
s.subMu.RLock()
|
||||
defer s.subMu.RUnlock()
|
||||
offset, n := GetOffsetN(page, pageSize)
|
||||
s.subIndexer.Iterate(fn, offset, n)
|
||||
return rs, uint32(s.subIndexer.Len()), nil
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
"github.com/winc-link/hummingbird/internal/pkg/packets"
|
||||
)
|
||||
|
||||
type subscriptionService struct {
|
||||
a *Admin
|
||||
}
|
||||
|
||||
func (s *subscriptionService) mustEmbedUnimplementedSubscriptionServiceServer() {
|
||||
return
|
||||
}
|
||||
|
||||
// List lists subscriptions in the broker.
|
||||
func (s *subscriptionService) List(ctx context.Context, req *ListSubscriptionRequest) (*ListSubscriptionResponse, error) {
|
||||
page, pageSize := GetPage(req.Page, req.PageSize)
|
||||
subs, total, err := s.a.store.GetSubscriptions(page, pageSize)
|
||||
if err != nil {
|
||||
return &ListSubscriptionResponse{}, err
|
||||
}
|
||||
return &ListSubscriptionResponse{
|
||||
Subscriptions: subs,
|
||||
TotalCount: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Filter filters subscriptions with the request params.
|
||||
// Paging is not supported, and the results are not sorted in any way.
|
||||
// Using huge req.Limit can impact performance.
|
||||
func (s *subscriptionService) Filter(ctx context.Context, req *FilterSubscriptionRequest) (resp *FilterSubscriptionResponse, err error) {
|
||||
var iterType subscription.IterationType
|
||||
iterOpts := subscription.IterationOptions{
|
||||
ClientID: req.ClientId,
|
||||
TopicName: req.TopicName,
|
||||
}
|
||||
if req.FilterType == "" {
|
||||
iterType = subscription.TypeAll
|
||||
} else {
|
||||
types := strings.Split(req.FilterType, ",")
|
||||
for _, v := range types {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
i, err := strconv.Atoi(v)
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrInvalidArgument("filter_type", err.Error())
|
||||
}
|
||||
switch SubFilterType(i) {
|
||||
|
||||
case SubFilterType_SUB_FILTER_TYPE_SYS:
|
||||
iterType |= subscription.TypeSYS
|
||||
case SubFilterType_SUB_FILTER_TYPE_SHARED:
|
||||
iterType |= subscription.TypeShared
|
||||
case SubFilterType_SUB_FILTER_TYPE_NON_SHARED:
|
||||
iterType |= subscription.TypeNonShared
|
||||
default:
|
||||
return nil, ErrInvalidArgument("filter_type", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iterOpts.Type = iterType
|
||||
|
||||
if req.MatchType == SubMatchType_SUB_MATCH_TYPE_MATCH_NAME {
|
||||
iterOpts.MatchType = subscription.MatchName
|
||||
} else if req.MatchType == SubMatchType_SUB_MATCH_TYPE_MATCH_FILTER {
|
||||
iterOpts.MatchType = subscription.MatchFilter
|
||||
}
|
||||
if iterOpts.TopicName == "" && iterOpts.MatchType != 0 {
|
||||
return nil, ErrInvalidArgument("topic_name", "cannot be empty while match_type Set")
|
||||
}
|
||||
if iterOpts.TopicName != "" && iterOpts.MatchType == 0 {
|
||||
return nil, ErrInvalidArgument("match_type", "cannot be empty while topic_name Set")
|
||||
}
|
||||
if iterOpts.TopicName != "" {
|
||||
if !packets.ValidV5Topic([]byte(iterOpts.TopicName)) {
|
||||
return nil, ErrInvalidArgument("topic_name", "")
|
||||
}
|
||||
}
|
||||
|
||||
if req.Limit > 1000 {
|
||||
return nil, ErrInvalidArgument("limit", fmt.Sprintf("limit too large, must <= 1000"))
|
||||
}
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 20
|
||||
}
|
||||
resp = &FilterSubscriptionResponse{
|
||||
Subscriptions: make([]*Subscription, 0),
|
||||
}
|
||||
i := int32(0)
|
||||
s.a.store.subscriptionService.Iterate(func(clientID string, sub *gmqtt.Subscription) bool {
|
||||
if i != req.Limit {
|
||||
resp.Subscriptions = append(resp.Subscriptions, &Subscription{
|
||||
TopicName: subscription.GetFullTopicName(sub.ShareName, sub.TopicFilter),
|
||||
Id: sub.ID,
|
||||
Qos: uint32(sub.QoS),
|
||||
NoLocal: sub.NoLocal,
|
||||
RetainAsPublished: sub.RetainAsPublished,
|
||||
RetainHandling: uint32(sub.RetainHandling),
|
||||
ClientId: clientID,
|
||||
})
|
||||
}
|
||||
i++
|
||||
return true
|
||||
}, iterOpts)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Subscribe makes subscriptions for the given client.
|
||||
func (s *subscriptionService) Subscribe(ctx context.Context, req *SubscribeRequest) (resp *SubscribeResponse, err error) {
|
||||
if req.ClientId == "" {
|
||||
return nil, ErrInvalidArgument("client_id", "cannot be empty")
|
||||
}
|
||||
if len(req.Subscriptions) == 0 {
|
||||
return nil, ErrInvalidArgument("subIndexer", "zero length subIndexer")
|
||||
}
|
||||
var subs []*gmqtt.Subscription
|
||||
for k, v := range req.Subscriptions {
|
||||
shareName, name := subscription.SplitTopic(v.TopicName)
|
||||
sub := &gmqtt.Subscription{
|
||||
ShareName: shareName,
|
||||
TopicFilter: name,
|
||||
ID: v.Id,
|
||||
QoS: uint8(v.Qos),
|
||||
NoLocal: v.NoLocal,
|
||||
RetainAsPublished: v.RetainAsPublished,
|
||||
RetainHandling: byte(v.RetainHandling),
|
||||
}
|
||||
err := sub.Validate()
|
||||
if err != nil {
|
||||
return nil, ErrInvalidArgument(fmt.Sprintf("subIndexer[%d]", k), err.Error())
|
||||
}
|
||||
subs = append(subs, sub)
|
||||
}
|
||||
rs, err := s.a.store.subscriptionService.Subscribe(req.ClientId, subs...)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to subscribe: %s", err.Error())
|
||||
}
|
||||
resp = &SubscribeResponse{
|
||||
New: make([]bool, 0),
|
||||
}
|
||||
for _, v := range rs {
|
||||
resp.New = append(resp.New, !v.AlreadyExisted)
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribe topic for the given client.
|
||||
func (s *subscriptionService) Unsubscribe(ctx context.Context, req *UnsubscribeRequest) (resp *empty.Empty, err error) {
|
||||
if req.ClientId == "" {
|
||||
return nil, ErrInvalidArgument("client_id", "cannot be empty")
|
||||
}
|
||||
if len(req.Topics) == 0 {
|
||||
return nil, ErrInvalidArgument("topics", "zero length topics")
|
||||
}
|
||||
|
||||
for k, v := range req.Topics {
|
||||
if !packets.ValidV5Topic([]byte(v)) {
|
||||
return nil, ErrInvalidArgument(fmt.Sprintf("topics[%d]", k), "")
|
||||
}
|
||||
}
|
||||
err = s.a.store.subscriptionService.Unsubscribe(req.ClientId, req.Topics...)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to unsubscribe: %s", err.Error()))
|
||||
}
|
||||
return &empty.Empty{}, nil
|
||||
}
|
|
@ -1,922 +0,0 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.22.0
|
||||
// protoc v3.13.0
|
||||
// source: subscription.proto
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
empty "github.com/golang/protobuf/ptypes/empty"
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type SubFilterType int32
|
||||
|
||||
const (
|
||||
SubFilterType_SUB_FILTER_TYPE_SYS_UNSPECIFIED SubFilterType = 0
|
||||
SubFilterType_SUB_FILTER_TYPE_SYS SubFilterType = 1
|
||||
SubFilterType_SUB_FILTER_TYPE_SHARED SubFilterType = 2
|
||||
SubFilterType_SUB_FILTER_TYPE_NON_SHARED SubFilterType = 3
|
||||
)
|
||||
|
||||
// Enum value maps for SubFilterType.
|
||||
var (
|
||||
SubFilterType_name = map[int32]string{
|
||||
0: "SUB_FILTER_TYPE_SYS_UNSPECIFIED",
|
||||
1: "SUB_FILTER_TYPE_SYS",
|
||||
2: "SUB_FILTER_TYPE_SHARED",
|
||||
3: "SUB_FILTER_TYPE_NON_SHARED",
|
||||
}
|
||||
SubFilterType_value = map[string]int32{
|
||||
"SUB_FILTER_TYPE_SYS_UNSPECIFIED": 0,
|
||||
"SUB_FILTER_TYPE_SYS": 1,
|
||||
"SUB_FILTER_TYPE_SHARED": 2,
|
||||
"SUB_FILTER_TYPE_NON_SHARED": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x SubFilterType) Enum() *SubFilterType {
|
||||
p := new(SubFilterType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x SubFilterType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (SubFilterType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_subscription_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (SubFilterType) Type() protoreflect.EnumType {
|
||||
return &file_subscription_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x SubFilterType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SubFilterType.Descriptor instead.
|
||||
func (SubFilterType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type SubMatchType int32
|
||||
|
||||
const (
|
||||
SubMatchType_SUB_MATCH_TYPE_MATCH_UNSPECIFIED SubMatchType = 0
|
||||
SubMatchType_SUB_MATCH_TYPE_MATCH_NAME SubMatchType = 1
|
||||
SubMatchType_SUB_MATCH_TYPE_MATCH_FILTER SubMatchType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for SubMatchType.
|
||||
var (
|
||||
SubMatchType_name = map[int32]string{
|
||||
0: "SUB_MATCH_TYPE_MATCH_UNSPECIFIED",
|
||||
1: "SUB_MATCH_TYPE_MATCH_NAME",
|
||||
2: "SUB_MATCH_TYPE_MATCH_FILTER",
|
||||
}
|
||||
SubMatchType_value = map[string]int32{
|
||||
"SUB_MATCH_TYPE_MATCH_UNSPECIFIED": 0,
|
||||
"SUB_MATCH_TYPE_MATCH_NAME": 1,
|
||||
"SUB_MATCH_TYPE_MATCH_FILTER": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x SubMatchType) Enum() *SubMatchType {
|
||||
p := new(SubMatchType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x SubMatchType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (SubMatchType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_subscription_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (SubMatchType) Type() protoreflect.EnumType {
|
||||
return &file_subscription_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x SubMatchType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SubMatchType.Descriptor instead.
|
||||
func (SubMatchType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type ListSubscriptionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
PageSize uint32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
|
||||
Page uint32 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionRequest) Reset() {
|
||||
*x = ListSubscriptionRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListSubscriptionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListSubscriptionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListSubscriptionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListSubscriptionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionRequest) GetPageSize() uint32 {
|
||||
if x != nil {
|
||||
return x.PageSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionRequest) GetPage() uint32 {
|
||||
if x != nil {
|
||||
return x.Page
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ListSubscriptionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Subscriptions []*Subscription `protobuf:"bytes,1,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"`
|
||||
TotalCount uint32 `protobuf:"varint,2,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionResponse) Reset() {
|
||||
*x = ListSubscriptionResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListSubscriptionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListSubscriptionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListSubscriptionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListSubscriptionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionResponse) GetSubscriptions() []*Subscription {
|
||||
if x != nil {
|
||||
return x.Subscriptions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListSubscriptionResponse) GetTotalCount() uint32 {
|
||||
if x != nil {
|
||||
return x.TotalCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FilterSubscriptionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// If set, only filter the subscriptions that belongs to the client.
|
||||
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
// filter_type indicates what kinds of topics are going to filter.
|
||||
// If there are multiple types, use ',' to separate. e.g : 1,2
|
||||
// There are 3 kinds of topic can be filtered, defined by SubFilterType:
|
||||
// 1 = System Topic(begin with '$')
|
||||
// 2 = Shared Topic
|
||||
// 3 = NonShared Topic
|
||||
FilterType string `protobuf:"bytes,2,opt,name=filter_type,json=filterType,proto3" json:"filter_type,omitempty"`
|
||||
// If 1 (SUB_MATCH_TYPE_MATCH_NAME), the server will return subscriptions which has the same topic name with request topic_name.
|
||||
// If 2 (SUB_MATCH_TYPE_MATCH_FILTER),the server will return subscriptions which match the request topic_name .
|
||||
// match_type must be set when filter_type is not empty.
|
||||
MatchType SubMatchType `protobuf:"varint,3,opt,name=match_type,json=matchType,proto3,enum=gmqtt.admin.api.SubMatchType" json:"match_type,omitempty"`
|
||||
// topic_name must be set when match_type is not zero.
|
||||
TopicName string `protobuf:"bytes,4,opt,name=topic_name,json=topicName,proto3" json:"topic_name,omitempty"`
|
||||
// The maximum subscriptions can be returned.
|
||||
Limit int32 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) Reset() {
|
||||
*x = FilterSubscriptionRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FilterSubscriptionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *FilterSubscriptionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FilterSubscriptionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*FilterSubscriptionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) GetFilterType() string {
|
||||
if x != nil {
|
||||
return x.FilterType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) GetMatchType() SubMatchType {
|
||||
if x != nil {
|
||||
return x.MatchType
|
||||
}
|
||||
return SubMatchType_SUB_MATCH_TYPE_MATCH_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) GetTopicName() string {
|
||||
if x != nil {
|
||||
return x.TopicName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionRequest) GetLimit() int32 {
|
||||
if x != nil {
|
||||
return x.Limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FilterSubscriptionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Subscriptions []*Subscription `protobuf:"bytes,1,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"`
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionResponse) Reset() {
|
||||
*x = FilterSubscriptionResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FilterSubscriptionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *FilterSubscriptionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FilterSubscriptionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*FilterSubscriptionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *FilterSubscriptionResponse) GetSubscriptions() []*Subscription {
|
||||
if x != nil {
|
||||
return x.Subscriptions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SubscribeRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
Subscriptions []*Subscription `protobuf:"bytes,2,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) Reset() {
|
||||
*x = SubscribeRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SubscribeRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SubscribeRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeRequest) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) GetSubscriptions() []*Subscription {
|
||||
if x != nil {
|
||||
return x.Subscriptions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SubscribeResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// indicates whether it is a new subscription or the subscription is already existed.
|
||||
New []bool `protobuf:"varint,1,rep,packed,name=new,proto3" json:"new,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SubscribeResponse) Reset() {
|
||||
*x = SubscribeResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SubscribeResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SubscribeResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SubscribeResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeResponse) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *SubscribeResponse) GetNew() []bool {
|
||||
if x != nil {
|
||||
return x.New
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnsubscribeRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
Topics []string `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UnsubscribeRequest) Reset() {
|
||||
*x = UnsubscribeRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UnsubscribeRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UnsubscribeRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UnsubscribeRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UnsubscribeRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UnsubscribeRequest) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *UnsubscribeRequest) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UnsubscribeRequest) GetTopics() []string {
|
||||
if x != nil {
|
||||
return x.Topics
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
TopicName string `protobuf:"bytes,1,opt,name=topic_name,json=topicName,proto3" json:"topic_name,omitempty"`
|
||||
Id uint32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Qos uint32 `protobuf:"varint,3,opt,name=qos,proto3" json:"qos,omitempty"`
|
||||
NoLocal bool `protobuf:"varint,4,opt,name=no_local,json=noLocal,proto3" json:"no_local,omitempty"`
|
||||
RetainAsPublished bool `protobuf:"varint,5,opt,name=retain_as_published,json=retainAsPublished,proto3" json:"retain_as_published,omitempty"`
|
||||
RetainHandling uint32 `protobuf:"varint,6,opt,name=retain_handling,json=retainHandling,proto3" json:"retain_handling,omitempty"`
|
||||
ClientId string `protobuf:"bytes,7,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Subscription) Reset() {
|
||||
*x = Subscription{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_subscription_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Subscription) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Subscription) ProtoMessage() {}
|
||||
|
||||
func (x *Subscription) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_subscription_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Subscription.ProtoReflect.Descriptor instead.
|
||||
func (*Subscription) Descriptor() ([]byte, []int) {
|
||||
return file_subscription_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *Subscription) GetTopicName() string {
|
||||
if x != nil {
|
||||
return x.TopicName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Subscription) GetId() uint32 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Subscription) GetQos() uint32 {
|
||||
if x != nil {
|
||||
return x.Qos
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Subscription) GetNoLocal() bool {
|
||||
if x != nil {
|
||||
return x.NoLocal
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Subscription) GetRetainAsPublished() bool {
|
||||
if x != nil {
|
||||
return x.RetainAsPublished
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Subscription) GetRetainHandling() uint32 {
|
||||
if x != nil {
|
||||
return x.RetainHandling
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Subscription) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_subscription_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_subscription_proto_rawDesc = []byte{
|
||||
0x0a, 0x12, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69,
|
||||
0x6e, 0x2e, 0x61, 0x70, 0x69, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70,
|
||||
0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x22, 0x4a, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70,
|
||||
0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
|
||||
0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x22, 0x80, 0x01, 0x0a,
|
||||
0x18, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x73, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x1d, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22,
|
||||
0xcc, 0x01, 0x0a, 0x19, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a,
|
||||
0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69,
|
||||
0x6c, 0x74, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x6d,
|
||||
0x61, 0x74, 0x63, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x1d, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x53, 0x75, 0x62, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09,
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x70,
|
||||
0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74,
|
||||
0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69,
|
||||
0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x61,
|
||||
0x0a, 0x1a, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0d,
|
||||
0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69,
|
||||
0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x22, 0x74, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x49, 0x64, 0x12, 0x43, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6d, 0x71, 0x74,
|
||||
0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x25, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x6e, 0x65, 0x77, 0x18, 0x01, 0x20, 0x03, 0x28, 0x08, 0x52, 0x03, 0x6e, 0x65, 0x77, 0x22, 0x49,
|
||||
0x0a, 0x12, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
||||
0x64, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x06, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x53, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f,
|
||||
0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||
0x74, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x71, 0x6f, 0x73,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x71, 0x6f, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6e,
|
||||
0x6f, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6e,
|
||||
0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x74, 0x61, 0x69, 0x6e,
|
||||
0x5f, 0x61, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x41, 0x73, 0x50, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x74, 0x61, 0x69, 0x6e,
|
||||
0x5f, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x0e, 0x72, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x69, 0x6e, 0x67, 0x12,
|
||||
0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x2a, 0x89, 0x01, 0x0a,
|
||||
0x0d, 0x53, 0x75, 0x62, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23,
|
||||
0x0a, 0x1f, 0x53, 0x55, 0x42, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50,
|
||||
0x45, 0x5f, 0x53, 0x59, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
|
||||
0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x55, 0x42, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45,
|
||||
0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x53, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16,
|
||||
0x53, 0x55, 0x42, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x55, 0x42, 0x5f,
|
||||
0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x74, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x4d,
|
||||
0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x20, 0x53, 0x55, 0x42, 0x5f,
|
||||
0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48,
|
||||
0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d,
|
||||
0x0a, 0x19, 0x53, 0x55, 0x42, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x54, 0x59, 0x50, 0x45,
|
||||
0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x1f, 0x0a,
|
||||
0x1b, 0x53, 0x55, 0x42, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x02, 0x32, 0xe9,
|
||||
0x03, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x28,
|
||||
0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74,
|
||||
0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x83,
|
||||
0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x67, 0x6d, 0x71, 0x74,
|
||||
0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f,
|
||||
0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x12, 0x6c, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
|
||||
0x65, 0x12, 0x21, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d,
|
||||
0x69, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12,
|
||||
0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x3a,
|
||||
0x01, 0x2a, 0x12, 0x66, 0x0a, 0x0b, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
|
||||
0x65, 0x12, 0x23, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1a,
|
||||
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x6e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x3a, 0x01, 0x2a, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b,
|
||||
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_subscription_proto_rawDescOnce sync.Once
|
||||
file_subscription_proto_rawDescData = file_subscription_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_subscription_proto_rawDescGZIP() []byte {
|
||||
file_subscription_proto_rawDescOnce.Do(func() {
|
||||
file_subscription_proto_rawDescData = protoimpl.X.CompressGZIP(file_subscription_proto_rawDescData)
|
||||
})
|
||||
return file_subscription_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_subscription_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_subscription_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_subscription_proto_goTypes = []interface{}{
|
||||
(SubFilterType)(0), // 0: gmqtt.admin.api.SubFilterType
|
||||
(SubMatchType)(0), // 1: gmqtt.admin.api.SubMatchType
|
||||
(*ListSubscriptionRequest)(nil), // 2: gmqtt.admin.api.ListSubscriptionRequest
|
||||
(*ListSubscriptionResponse)(nil), // 3: gmqtt.admin.api.ListSubscriptionResponse
|
||||
(*FilterSubscriptionRequest)(nil), // 4: gmqtt.admin.api.FilterSubscriptionRequest
|
||||
(*FilterSubscriptionResponse)(nil), // 5: gmqtt.admin.api.FilterSubscriptionResponse
|
||||
(*SubscribeRequest)(nil), // 6: gmqtt.admin.api.SubscribeRequest
|
||||
(*SubscribeResponse)(nil), // 7: gmqtt.admin.api.SubscribeResponse
|
||||
(*UnsubscribeRequest)(nil), // 8: gmqtt.admin.api.UnsubscribeRequest
|
||||
(*Subscription)(nil), // 9: gmqtt.admin.api.Subscription
|
||||
(*empty.Empty)(nil), // 10: google.protobuf.Empty
|
||||
}
|
||||
var file_subscription_proto_depIdxs = []int32{
|
||||
9, // 0: gmqtt.admin.api.ListSubscriptionResponse.subscriptions:type_name -> gmqtt.admin.api.Subscription
|
||||
1, // 1: gmqtt.admin.api.FilterSubscriptionRequest.match_type:type_name -> gmqtt.admin.api.SubMatchType
|
||||
9, // 2: gmqtt.admin.api.FilterSubscriptionResponse.subscriptions:type_name -> gmqtt.admin.api.Subscription
|
||||
9, // 3: gmqtt.admin.api.SubscribeRequest.subscriptions:type_name -> gmqtt.admin.api.Subscription
|
||||
2, // 4: gmqtt.admin.api.SubscriptionService.List:input_type -> gmqtt.admin.api.ListSubscriptionRequest
|
||||
4, // 5: gmqtt.admin.api.SubscriptionService.Filter:input_type -> gmqtt.admin.api.FilterSubscriptionRequest
|
||||
6, // 6: gmqtt.admin.api.SubscriptionService.Subscribe:input_type -> gmqtt.admin.api.SubscribeRequest
|
||||
8, // 7: gmqtt.admin.api.SubscriptionService.Unsubscribe:input_type -> gmqtt.admin.api.UnsubscribeRequest
|
||||
3, // 8: gmqtt.admin.api.SubscriptionService.List:output_type -> gmqtt.admin.api.ListSubscriptionResponse
|
||||
5, // 9: gmqtt.admin.api.SubscriptionService.Filter:output_type -> gmqtt.admin.api.FilterSubscriptionResponse
|
||||
7, // 10: gmqtt.admin.api.SubscriptionService.Subscribe:output_type -> gmqtt.admin.api.SubscribeResponse
|
||||
10, // 11: gmqtt.admin.api.SubscriptionService.Unsubscribe:output_type -> google.protobuf.Empty
|
||||
8, // [8:12] is the sub-list for method output_type
|
||||
4, // [4:8] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_subscription_proto_init() }
|
||||
func file_subscription_proto_init() {
|
||||
if File_subscription_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_subscription_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListSubscriptionRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListSubscriptionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FilterSubscriptionRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FilterSubscriptionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SubscribeRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SubscribeResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UnsubscribeRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_subscription_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Subscription); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_subscription_proto_rawDesc,
|
||||
NumEnums: 2,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_subscription_proto_goTypes,
|
||||
DependencyIndexes: file_subscription_proto_depIdxs,
|
||||
EnumInfos: file_subscription_proto_enumTypes,
|
||||
MessageInfos: file_subscription_proto_msgTypes,
|
||||
}.Build()
|
||||
File_subscription_proto = out.File
|
||||
file_subscription_proto_rawDesc = nil
|
||||
file_subscription_proto_goTypes = nil
|
||||
file_subscription_proto_depIdxs = nil
|
||||
}
|
|
@ -1,395 +0,0 @@
|
|||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: subscription.proto
|
||||
|
||||
/*
|
||||
Package admin is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/descriptor"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = descriptor.ForMessage
|
||||
|
||||
var (
|
||||
filter_SubscriptionService_List_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_SubscriptionService_List_0(ctx context.Context, marshaler runtime.Marshaler, client SubscriptionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListSubscriptionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SubscriptionService_List_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.List(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_SubscriptionService_List_0(ctx context.Context, marshaler runtime.Marshaler, server SubscriptionServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListSubscriptionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_SubscriptionService_List_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.List(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_SubscriptionService_Filter_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_SubscriptionService_Filter_0(ctx context.Context, marshaler runtime.Marshaler, client SubscriptionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq FilterSubscriptionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SubscriptionService_Filter_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Filter(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_SubscriptionService_Filter_0(ctx context.Context, marshaler runtime.Marshaler, server SubscriptionServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq FilterSubscriptionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_SubscriptionService_Filter_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Filter(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_SubscriptionService_Subscribe_0(ctx context.Context, marshaler runtime.Marshaler, client SubscriptionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq SubscribeRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Subscribe(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_SubscriptionService_Subscribe_0(ctx context.Context, marshaler runtime.Marshaler, server SubscriptionServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq SubscribeRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Subscribe(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_SubscriptionService_Unsubscribe_0(ctx context.Context, marshaler runtime.Marshaler, client SubscriptionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq UnsubscribeRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Unsubscribe(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_SubscriptionService_Unsubscribe_0(ctx context.Context, marshaler runtime.Marshaler, server SubscriptionServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq UnsubscribeRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Unsubscribe(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterSubscriptionServiceHandlerServer registers the http handlers for service SubscriptionService to "mux".
|
||||
// UnaryRPC :call SubscriptionServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
func RegisterSubscriptionServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server SubscriptionServiceServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_SubscriptionService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_SubscriptionService_List_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_SubscriptionService_Filter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_SubscriptionService_Filter_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_Filter_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_SubscriptionService_Subscribe_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_SubscriptionService_Subscribe_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_Subscribe_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_SubscriptionService_Unsubscribe_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_SubscriptionService_Unsubscribe_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_Unsubscribe_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterSubscriptionServiceHandlerFromEndpoint is same as RegisterSubscriptionServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterSubscriptionServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterSubscriptionServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterSubscriptionServiceHandler registers the http handlers for service SubscriptionService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterSubscriptionServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterSubscriptionServiceHandlerClient(ctx, mux, NewSubscriptionServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterSubscriptionServiceHandlerClient registers the http handlers for service SubscriptionService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "SubscriptionServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "SubscriptionServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "SubscriptionServiceClient" to call the correct interceptors.
|
||||
func RegisterSubscriptionServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client SubscriptionServiceClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_SubscriptionService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_SubscriptionService_List_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_SubscriptionService_Filter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_SubscriptionService_Filter_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_Filter_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_SubscriptionService_Subscribe_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_SubscriptionService_Subscribe_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_Subscribe_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_SubscriptionService_Unsubscribe_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_SubscriptionService_Unsubscribe_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_SubscriptionService_Unsubscribe_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_SubscriptionService_List_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "subscriptions"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_SubscriptionService_Filter_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "filter_subscriptions"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_SubscriptionService_Subscribe_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "subscribe"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_SubscriptionService_Unsubscribe_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "unsubscribe"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_SubscriptionService_List_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_SubscriptionService_Filter_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_SubscriptionService_Subscribe_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_SubscriptionService_Unsubscribe_0 = runtime.ForwardResponseMessage
|
||||
)
|
|
@ -1,215 +0,0 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
empty "github.com/golang/protobuf/ptypes/empty"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// SubscriptionServiceClient is the client API for SubscriptionService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type SubscriptionServiceClient interface {
|
||||
// List subscriptions.
|
||||
List(ctx context.Context, in *ListSubscriptionRequest, opts ...grpc.CallOption) (*ListSubscriptionResponse, error)
|
||||
// Filter subscriptions, paging is not supported in this API.
|
||||
Filter(ctx context.Context, in *FilterSubscriptionRequest, opts ...grpc.CallOption) (*FilterSubscriptionResponse, error)
|
||||
// Subscribe topics for the client.
|
||||
Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (*SubscribeResponse, error)
|
||||
// Unsubscribe topics for the client.
|
||||
Unsubscribe(ctx context.Context, in *UnsubscribeRequest, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
}
|
||||
|
||||
type subscriptionServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewSubscriptionServiceClient(cc grpc.ClientConnInterface) SubscriptionServiceClient {
|
||||
return &subscriptionServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *subscriptionServiceClient) List(ctx context.Context, in *ListSubscriptionRequest, opts ...grpc.CallOption) (*ListSubscriptionResponse, error) {
|
||||
out := new(ListSubscriptionResponse)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.SubscriptionService/List", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *subscriptionServiceClient) Filter(ctx context.Context, in *FilterSubscriptionRequest, opts ...grpc.CallOption) (*FilterSubscriptionResponse, error) {
|
||||
out := new(FilterSubscriptionResponse)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.SubscriptionService/Filter", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *subscriptionServiceClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (*SubscribeResponse, error) {
|
||||
out := new(SubscribeResponse)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.SubscriptionService/Subscribe", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *subscriptionServiceClient) Unsubscribe(ctx context.Context, in *UnsubscribeRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
|
||||
out := new(empty.Empty)
|
||||
err := c.cc.Invoke(ctx, "/gmqtt.admin.api.SubscriptionService/Unsubscribe", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SubscriptionServiceServer is the server API for SubscriptionService service.
|
||||
// All implementations must embed UnimplementedSubscriptionServiceServer
|
||||
// for forward compatibility
|
||||
type SubscriptionServiceServer interface {
|
||||
// List subscriptions.
|
||||
List(context.Context, *ListSubscriptionRequest) (*ListSubscriptionResponse, error)
|
||||
// Filter subscriptions, paging is not supported in this API.
|
||||
Filter(context.Context, *FilterSubscriptionRequest) (*FilterSubscriptionResponse, error)
|
||||
// Subscribe topics for the client.
|
||||
Subscribe(context.Context, *SubscribeRequest) (*SubscribeResponse, error)
|
||||
// Unsubscribe topics for the client.
|
||||
Unsubscribe(context.Context, *UnsubscribeRequest) (*empty.Empty, error)
|
||||
mustEmbedUnimplementedSubscriptionServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedSubscriptionServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedSubscriptionServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedSubscriptionServiceServer) List(context.Context, *ListSubscriptionRequest) (*ListSubscriptionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||
}
|
||||
func (UnimplementedSubscriptionServiceServer) Filter(context.Context, *FilterSubscriptionRequest) (*FilterSubscriptionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Filter not implemented")
|
||||
}
|
||||
func (UnimplementedSubscriptionServiceServer) Subscribe(context.Context, *SubscribeRequest) (*SubscribeResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Subscribe not implemented")
|
||||
}
|
||||
func (UnimplementedSubscriptionServiceServer) Unsubscribe(context.Context, *UnsubscribeRequest) (*empty.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Unsubscribe not implemented")
|
||||
}
|
||||
func (UnimplementedSubscriptionServiceServer) mustEmbedUnimplementedSubscriptionServiceServer() {}
|
||||
|
||||
// UnsafeSubscriptionServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to SubscriptionServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeSubscriptionServiceServer interface {
|
||||
mustEmbedUnimplementedSubscriptionServiceServer()
|
||||
}
|
||||
|
||||
func RegisterSubscriptionServiceServer(s grpc.ServiceRegistrar, srv SubscriptionServiceServer) {
|
||||
s.RegisterService(&_SubscriptionService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _SubscriptionService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListSubscriptionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SubscriptionServiceServer).List(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.SubscriptionService/List",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SubscriptionServiceServer).List(ctx, req.(*ListSubscriptionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SubscriptionService_Filter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(FilterSubscriptionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SubscriptionServiceServer).Filter(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.SubscriptionService/Filter",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SubscriptionServiceServer).Filter(ctx, req.(*FilterSubscriptionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SubscriptionService_Subscribe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SubscribeRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SubscriptionServiceServer).Subscribe(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.SubscriptionService/Subscribe",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SubscriptionServiceServer).Subscribe(ctx, req.(*SubscribeRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SubscriptionService_Unsubscribe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UnsubscribeRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SubscriptionServiceServer).Unsubscribe(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gmqtt.admin.api.SubscriptionService/Unsubscribe",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SubscriptionServiceServer).Unsubscribe(ctx, req.(*UnsubscribeRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _SubscriptionService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "gmqtt.admin.api.SubscriptionService",
|
||||
HandlerType: (*SubscriptionServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _SubscriptionService_List_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Filter",
|
||||
Handler: _SubscriptionService_Filter_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Subscribe",
|
||||
Handler: _SubscriptionService_Subscribe_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Unsubscribe",
|
||||
Handler: _SubscriptionService_Unsubscribe_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "subscription.proto",
|
||||
}
|
|
@ -1,390 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
gmqtt "github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/persistence/subscription"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
func TestSubscriptionService_List(t *testing.T) {
|
||||
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
client := server.NewMockClient(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
client.EXPECT().ClientOptions().Return(&server.ClientOptions{ClientID: "id"})
|
||||
subscribe := admin.OnSubscribedWrapper(func(ctx context.Context, client server.Client, subscription *gmqtt.Subscription) {})
|
||||
|
||||
subsc := &gmqtt.Subscription{
|
||||
ShareName: "abc",
|
||||
TopicFilter: "t",
|
||||
ID: 1,
|
||||
QoS: 2,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 2,
|
||||
}
|
||||
subscribe(context.Background(), client, subsc)
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
resp, err := sub.List(context.Background(), &ListSubscriptionRequest{
|
||||
PageSize: 0,
|
||||
Page: 0,
|
||||
})
|
||||
|
||||
a.Nil(err)
|
||||
a.Len(resp.Subscriptions, 1)
|
||||
rs := resp.Subscriptions[0]
|
||||
a.EqualValues(subsc.QoS, rs.Qos)
|
||||
a.EqualValues(subsc.GetFullTopicName(), rs.TopicName)
|
||||
a.EqualValues(subsc.ID, rs.Id)
|
||||
a.EqualValues(subsc.RetainHandling, rs.RetainHandling)
|
||||
a.EqualValues(subsc.NoLocal, rs.NoLocal)
|
||||
}
|
||||
|
||||
func TestSubscriptionService_Filter(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
ss.EXPECT().Iterate(gomock.Any(), subscription.IterationOptions{
|
||||
Type: subscription.TypeAll,
|
||||
ClientID: "cid",
|
||||
TopicName: "abc",
|
||||
MatchType: subscription.MatchName,
|
||||
})
|
||||
|
||||
_, err := sub.Filter(context.Background(), &FilterSubscriptionRequest{
|
||||
ClientId: "cid",
|
||||
FilterType: "1,2,3",
|
||||
MatchType: SubMatchType_SUB_MATCH_TYPE_MATCH_NAME,
|
||||
TopicName: "abc",
|
||||
Limit: 1,
|
||||
})
|
||||
a.Nil(err)
|
||||
|
||||
ss.EXPECT().Iterate(gomock.Any(), subscription.IterationOptions{
|
||||
Type: subscription.TypeAll,
|
||||
ClientID: "cid",
|
||||
TopicName: "abc",
|
||||
MatchType: subscription.MatchName,
|
||||
})
|
||||
|
||||
// test default filter type
|
||||
_, err = sub.Filter(context.Background(), &FilterSubscriptionRequest{
|
||||
ClientId: "cid",
|
||||
FilterType: "",
|
||||
MatchType: SubMatchType_SUB_MATCH_TYPE_MATCH_NAME,
|
||||
TopicName: "abc",
|
||||
Limit: 1,
|
||||
})
|
||||
a.Nil(err)
|
||||
|
||||
ss.EXPECT().Iterate(gomock.Any(), subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared | subscription.TypeSYS,
|
||||
ClientID: "cid",
|
||||
TopicName: "abc",
|
||||
MatchType: subscription.MatchName,
|
||||
})
|
||||
|
||||
_, err = sub.Filter(context.Background(), &FilterSubscriptionRequest{
|
||||
ClientId: "cid",
|
||||
FilterType: "1,3",
|
||||
MatchType: SubMatchType_SUB_MATCH_TYPE_MATCH_NAME,
|
||||
TopicName: "abc",
|
||||
Limit: 1,
|
||||
})
|
||||
a.Nil(err)
|
||||
|
||||
ss.EXPECT().Iterate(gomock.Any(), subscription.IterationOptions{
|
||||
Type: subscription.TypeNonShared | subscription.TypeSYS,
|
||||
ClientID: "cid",
|
||||
TopicName: "abc",
|
||||
MatchType: subscription.MatchFilter,
|
||||
})
|
||||
|
||||
_, err = sub.Filter(context.Background(), &FilterSubscriptionRequest{
|
||||
ClientId: "cid",
|
||||
FilterType: "1,3",
|
||||
MatchType: SubMatchType_SUB_MATCH_TYPE_MATCH_FILTER,
|
||||
TopicName: "abc",
|
||||
Limit: 1,
|
||||
})
|
||||
a.Nil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestSubscriptionService_Filter_InvalidArgument(t *testing.T) {
|
||||
var tt = []struct {
|
||||
name string
|
||||
field string
|
||||
req *FilterSubscriptionRequest
|
||||
}{
|
||||
{
|
||||
name: "empty_topic_name_with_match_name",
|
||||
field: "match_type",
|
||||
req: &FilterSubscriptionRequest{
|
||||
ClientId: "",
|
||||
FilterType: "",
|
||||
MatchType: SubMatchType_SUB_MATCH_TYPE_MATCH_NAME,
|
||||
TopicName: "",
|
||||
Limit: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty_topic_name_with_match_filter",
|
||||
field: "match_type",
|
||||
req: &FilterSubscriptionRequest{
|
||||
ClientId: "",
|
||||
FilterType: "",
|
||||
MatchType: SubMatchType_SUB_MATCH_TYPE_MATCH_FILTER,
|
||||
TopicName: "",
|
||||
Limit: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_topic_name",
|
||||
field: "topic_name",
|
||||
req: &FilterSubscriptionRequest{
|
||||
ClientId: "",
|
||||
FilterType: "",
|
||||
MatchType: 0,
|
||||
TopicName: "##",
|
||||
Limit: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, v := range tt {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
_, err := sub.Filter(context.Background(), v.req)
|
||||
a.NotNil(err)
|
||||
a.Contains(err.Error(), v.field)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSubscriptionService_Subscribe(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
subs := []*Subscription{
|
||||
{
|
||||
TopicName: "$share/a/b",
|
||||
Id: 1,
|
||||
Qos: 2,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 2,
|
||||
}, {
|
||||
TopicName: "abc",
|
||||
Id: 1,
|
||||
Qos: 2,
|
||||
NoLocal: true,
|
||||
RetainAsPublished: true,
|
||||
RetainHandling: 2,
|
||||
},
|
||||
}
|
||||
var expectedSubs []*gmqtt.Subscription
|
||||
for _, v := range subs {
|
||||
shareName, filter := subscription.SplitTopic(v.TopicName)
|
||||
s := &gmqtt.Subscription{
|
||||
ShareName: shareName,
|
||||
TopicFilter: filter,
|
||||
ID: v.Id,
|
||||
QoS: byte(v.Qos),
|
||||
NoLocal: v.NoLocal,
|
||||
RetainAsPublished: v.RetainAsPublished,
|
||||
RetainHandling: byte(v.RetainHandling),
|
||||
}
|
||||
expectedSubs = append(expectedSubs, s)
|
||||
}
|
||||
|
||||
ss.EXPECT().Subscribe("cid", expectedSubs).Return(subscription.SubscribeResult{
|
||||
{
|
||||
AlreadyExisted: true,
|
||||
}, {
|
||||
AlreadyExisted: false,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
resp, err := sub.Subscribe(context.Background(), &SubscribeRequest{
|
||||
ClientId: "cid",
|
||||
Subscriptions: subs,
|
||||
})
|
||||
a.Nil(err)
|
||||
resp.New = []bool{false, true}
|
||||
|
||||
}
|
||||
|
||||
func TestSubscriptionService_Subscribe_InvalidArgument(t *testing.T) {
|
||||
var tt = []struct {
|
||||
name string
|
||||
req *SubscribeRequest
|
||||
}{
|
||||
{
|
||||
name: "empty_client_id",
|
||||
req: &SubscribeRequest{
|
||||
ClientId: "",
|
||||
Subscriptions: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty_subscriptions",
|
||||
req: &SubscribeRequest{
|
||||
ClientId: "cid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_subscriptions",
|
||||
req: &SubscribeRequest{
|
||||
ClientId: "cid",
|
||||
Subscriptions: []*Subscription{
|
||||
{
|
||||
TopicName: "##",
|
||||
Id: 0,
|
||||
Qos: 0,
|
||||
NoLocal: false,
|
||||
RetainAsPublished: false,
|
||||
RetainHandling: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, v := range tt {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
_, err := sub.Subscribe(context.Background(), v.req)
|
||||
a.NotNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSubscriptionService_Unsubscribe(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
topics := []string{
|
||||
"a", "b",
|
||||
}
|
||||
ss.EXPECT().Unsubscribe("cid", topics)
|
||||
_, err := sub.Unsubscribe(context.Background(), &UnsubscribeRequest{
|
||||
ClientId: "cid",
|
||||
Topics: topics,
|
||||
})
|
||||
a.Nil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestSubscriptionService_Unsubscribe_InvalidArgument(t *testing.T) {
|
||||
var tt = []struct {
|
||||
name string
|
||||
req *UnsubscribeRequest
|
||||
}{
|
||||
{
|
||||
name: "empty_client_id",
|
||||
req: &UnsubscribeRequest{
|
||||
ClientId: "",
|
||||
Topics: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_topic_name",
|
||||
req: &UnsubscribeRequest{
|
||||
ClientId: "cid",
|
||||
Topics: []string{"+", "##"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, v := range tt {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ss := server.NewMockSubscriptionService(ctrl)
|
||||
admin := &Admin{
|
||||
store: newStore(nil, mockConfig),
|
||||
}
|
||||
sub := &subscriptionService{
|
||||
a: admin,
|
||||
}
|
||||
sub.a.store.subscriptionService = ss
|
||||
|
||||
_, err := sub.Unsubscribe(context.Background(), v.req)
|
||||
a.NotNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "client.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/clients": {
|
||||
"get": {
|
||||
"summary": "List clients",
|
||||
"operationId": "List",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiListClientResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page_size",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ClientService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/clients/{client_id}": {
|
||||
"get": {
|
||||
"summary": "Get the client for given client id.\nReturn NotFound error when client not found.",
|
||||
"operationId": "Get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiGetClientResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "client_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ClientService"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Disconnect the client for given client id.",
|
||||
"operationId": "Delete",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "client_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "clean_session",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ClientService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"apiClient": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"keep_alive": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"remote_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"local_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"connected_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"disconnected_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"session_expiry": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"max_inflight": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"inflight_len": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"max_queue": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"queue_len": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"subscriptions_current": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"subscriptions_total": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"packets_received_bytes": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"packets_received_nums": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"packets_send_bytes": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"packets_send_nums": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"message_dropped": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiGetClientResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {
|
||||
"$ref": "#/definitions/apiClient"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiListClientResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"clients": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/apiClient"
|
||||
}
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtimeError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "publish.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/publish": {
|
||||
"post": {
|
||||
"summary": "Publish message to broker",
|
||||
"operationId": "Publish",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiPublishRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"PublishService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"apiPublishRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "string"
|
||||
},
|
||||
"qos": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"retained": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"description": "the following fields are using in v5 client."
|
||||
},
|
||||
"correlation_data": {
|
||||
"type": "string"
|
||||
},
|
||||
"message_expiry": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"payload_format": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"response_topic": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_properties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/apiUserProperties"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiUserProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"K": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"V": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtimeError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,329 +0,0 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "subscription.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/filter_subscriptions": {
|
||||
"get": {
|
||||
"summary": "Filter subscriptions, paging is not supported in this API.",
|
||||
"operationId": "Filter",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiFilterSubscriptionResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "client_id",
|
||||
"description": "If set, only filter the subscriptions that belongs to the client.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "filter_type",
|
||||
"description": "filter_type indicates what kinds of topics are going to filter.\nIf there are multiple types, use ',' to separate. e.g : 1,2\nThere are 3 kinds of topic can be filtered, defined by SubFilterType:\n1 = System Topic(begin with '$')\n2 = Shared Topic\n3 = NonShared Topic.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match_type",
|
||||
"description": "If 1 (SUB_MATCH_TYPE_MATCH_NAME), the server will return subscriptions which has the same topic name with request topic_name.\nIf 2 (SUB_MATCH_TYPE_MATCH_FILTER),the server will return subscriptions which match the request topic_name .\nmatch_type must be set when filter_type is not empty.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SUB_MATCH_TYPE_MATCH_UNSPECIFIED",
|
||||
"SUB_MATCH_TYPE_MATCH_NAME",
|
||||
"SUB_MATCH_TYPE_MATCH_FILTER"
|
||||
],
|
||||
"default": "SUB_MATCH_TYPE_MATCH_UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"name": "topic_name",
|
||||
"description": "topic_name must be set when match_type is not zero.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "The maximum subscriptions can be returned.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SubscriptionService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/subscribe": {
|
||||
"post": {
|
||||
"summary": "Subscribe topics for the client.",
|
||||
"operationId": "Subscribe",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiSubscribeResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiSubscribeRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SubscriptionService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/subscriptions": {
|
||||
"get": {
|
||||
"summary": "List subscriptions.",
|
||||
"operationId": "List",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiListSubscriptionResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page_size",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SubscriptionService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/unsubscribe": {
|
||||
"post": {
|
||||
"summary": "Unsubscribe topics for the client.",
|
||||
"operationId": "Unsubscribe",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiUnsubscribeRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SubscriptionService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"apiFilterSubscriptionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subscriptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/apiSubscription"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiListSubscriptionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subscriptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/apiSubscription"
|
||||
}
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiSubMatchType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SUB_MATCH_TYPE_MATCH_UNSPECIFIED",
|
||||
"SUB_MATCH_TYPE_MATCH_NAME",
|
||||
"SUB_MATCH_TYPE_MATCH_FILTER"
|
||||
],
|
||||
"default": "SUB_MATCH_TYPE_MATCH_UNSPECIFIED"
|
||||
},
|
||||
"apiSubscribeRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"subscriptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/apiSubscription"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiSubscribeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"new": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
},
|
||||
"description": "indicates whether it is a new subscription or the subscription is already existed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiSubscription": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"qos": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"no_local": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
},
|
||||
"retain_as_published": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
},
|
||||
"retain_handling": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiUnsubscribeRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"topics": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtimeError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ErrNotFound represents a not found error.
|
||||
var ErrNotFound = status.Error(codes.NotFound, "not found")
|
||||
|
||||
// Indexer provides a index for a ordered list that supports queries in O(1).
|
||||
// All methods are not concurrency-safe.
|
||||
type Indexer struct {
|
||||
index map[string]*list.Element
|
||||
rows *list.List
|
||||
}
|
||||
|
||||
// NewIndexer is the constructor of Indexer.
|
||||
func NewIndexer() *Indexer {
|
||||
return &Indexer{
|
||||
index: make(map[string]*list.Element),
|
||||
rows: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets the value for the id.
|
||||
func (i *Indexer) Set(id string, value interface{}) {
|
||||
if e, ok := i.index[id]; ok {
|
||||
e.Value = value
|
||||
} else {
|
||||
elem := i.rows.PushBack(value)
|
||||
i.index[id] = elem
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes and returns the value for the given id.
|
||||
// Return nil if not found.
|
||||
func (i *Indexer) Remove(id string) *list.Element {
|
||||
elem := i.index[id]
|
||||
if elem != nil {
|
||||
i.rows.Remove(elem)
|
||||
}
|
||||
delete(i.index, id)
|
||||
return elem
|
||||
}
|
||||
|
||||
// GetByID returns the value for the given id.
|
||||
// Return nil if not found.
|
||||
// Notice: Any access to the return *list.Element also require the mutex,
|
||||
// because the Set method can modify the Value for *list.Element when updating the Value for the same id.
|
||||
// If the caller needs the Value in *list.Element, it must get the Value before the next Set is called.
|
||||
func (i *Indexer) GetByID(id string) *list.Element {
|
||||
return i.index[id]
|
||||
}
|
||||
|
||||
// Iterate iterates at most n elements in the list begin from offset.
|
||||
// Notice: Any access to the *list.Element in fn also require the mutex,
|
||||
// because the Set method can modify the Value for *list.Element when updating the Value for the same id.
|
||||
// If the caller needs the Value in *list.Element, it must get the Value before the next Set is called.
|
||||
func (i *Indexer) Iterate(fn func(elem *list.Element), offset, n uint) {
|
||||
if i.rows.Len() < int(offset) {
|
||||
return
|
||||
}
|
||||
var j uint
|
||||
for e := i.rows.Front(); e != nil; e = e.Next() {
|
||||
if j >= offset && j < offset+n {
|
||||
fn(e)
|
||||
}
|
||||
if j == offset+n {
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of list.
|
||||
func (i *Indexer) Len() int {
|
||||
return i.rows.Len()
|
||||
}
|
||||
|
||||
// GetPage gets page and pageSize from request params.
|
||||
func GetPage(reqPage, reqPageSize uint32) (page, pageSize uint) {
|
||||
page = 1
|
||||
pageSize = 20
|
||||
if reqPage != 0 {
|
||||
page = uint(reqPage)
|
||||
}
|
||||
if reqPageSize != 0 {
|
||||
pageSize = uint(reqPageSize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetOffsetN(page, pageSize uint) (offset, n uint) {
|
||||
offset = (page - 1) * pageSize
|
||||
n = pageSize
|
||||
return
|
||||
}
|
||||
|
||||
// ErrInvalidArgument is a wrapper function for easier invalid argument error handling.
|
||||
func ErrInvalidArgument(name string, msg string) error {
|
||||
errString := "invalid " + name
|
||||
if msg != "" {
|
||||
errString = errString + ":" + msg
|
||||
}
|
||||
return status.Error(codes.InvalidArgument, errString)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndexer(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
i := NewIndexer()
|
||||
for j := 0; j < 100; j++ {
|
||||
i.Set(strconv.Itoa(j), j)
|
||||
a.EqualValues(j, i.GetByID(strconv.Itoa(j)).Value)
|
||||
}
|
||||
a.EqualValues(100, i.Len())
|
||||
|
||||
var jj int
|
||||
i.Iterate(func(elem *list.Element) {
|
||||
v := elem.Value.(int)
|
||||
a.Equal(jj, v)
|
||||
jj++
|
||||
}, 0, uint(i.Len()))
|
||||
|
||||
e := i.Remove("5")
|
||||
a.Equal(5, e.Value.(int))
|
||||
|
||||
var rs []int
|
||||
i.Iterate(func(elem *list.Element) {
|
||||
rs = append(rs, elem.Value.(int))
|
||||
}, 4, 2)
|
||||
// 5 is removed
|
||||
a.Equal([]int{4, 6}, rs)
|
||||
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
package aplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/plugin/aplugin/snowflake"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/config"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ server.Plugin = (*APlugin)(nil)
|
||||
|
||||
const Name = "aplugin"
|
||||
|
||||
func init() {
|
||||
server.RegisterPlugin(Name, New)
|
||||
config.RegisterDefaultPluginConfig(Name, &DefaultConfig)
|
||||
}
|
||||
|
||||
func New(config config.Config) (server.Plugin, error) {
|
||||
return newAPlugin()
|
||||
}
|
||||
|
||||
type APlugin struct {
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg *sync.WaitGroup
|
||||
node *snowflake.Node
|
||||
log *zap.SugaredLogger
|
||||
ackMap sync.Map // async ack
|
||||
driverClients map[string]*DriverClient // driver map, key is username
|
||||
publisher server.Publisher
|
||||
publishChan chan PublishInfo
|
||||
}
|
||||
|
||||
func newAPlugin() (*APlugin, error) {
|
||||
node, err := snowflake.NewNode(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &APlugin{
|
||||
node: node,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
wg: &sync.WaitGroup{},
|
||||
log: zap.NewNop().Sugar(),
|
||||
driverClients: make(map[string]*DriverClient),
|
||||
publishChan: make(chan PublishInfo, 32),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *APlugin) Load(service server.Server) error {
|
||||
t.log = server.LoggerWithField(zap.String("plugin", Name)).Sugar()
|
||||
t.publisher = service.Publisher()
|
||||
t.wg.Add(1)
|
||||
go func() {
|
||||
defer t.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
t.log.Infof("plugin(%s) exit", t.Name())
|
||||
return
|
||||
case msg := <-t.publishChan:
|
||||
t.log.Infof("publish msg: topic: %s, payload: %s", msg.Topic, string(msg.Payload))
|
||||
t.publisher.Publish(&mqttbroker.Message{
|
||||
QoS: 1,
|
||||
Topic: msg.Topic,
|
||||
Payload: msg.Payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *APlugin) Unload() error {
|
||||
t.cancel()
|
||||
t.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *APlugin) Name() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (t *APlugin) genAckChan(id int64) *MsgAckChan {
|
||||
ack := &MsgAckChan{
|
||||
Id: id,
|
||||
DataChan: make(chan interface{}, 1),
|
||||
}
|
||||
t.ackMap.Store(id, ack)
|
||||
return ack
|
||||
}
|
||||
|
||||
func (t *APlugin) publishWithAckMsg(id int64, topic string, tp int, msg interface{}) (*MsgAckChan, error) {
|
||||
payload, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buff, err := json.Marshal(AsyncMsg{
|
||||
Id: id,
|
||||
Type: tp,
|
||||
Data: payload,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ackChan := t.genAckChan(id)
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
t.ackMap.Delete(id)
|
||||
return nil, errors.New("send auth msg to publish chan timeout")
|
||||
case t.publishChan <- PublishInfo{
|
||||
Topic: topic,
|
||||
Payload: buff,
|
||||
}:
|
||||
return ackChan, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) publishNotifyMsg(id int64, topic string, tp int, msg interface{}) error {
|
||||
payload, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff, err := json.Marshal(AsyncMsg{
|
||||
Id: id,
|
||||
Type: tp,
|
||||
Data: payload,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
return errors.New("send auth msg to publish chan timeout")
|
||||
case t.publishChan <- PublishInfo{
|
||||
Topic: topic,
|
||||
Payload: buff,
|
||||
}:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) validate(username, password string) error {
|
||||
//t.log.Debugf("got clientId: %s, username: %s, password: %s", clientId, username, password)
|
||||
passwd, err := md5GenPasswd(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if passwd[8:24] != password {
|
||||
return errors.New("auth failure")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func md5GenPasswd(username string) (string, error) {
|
||||
h := md5.New()
|
||||
_, err := h.Write([]byte(username))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rs := h.Sum(nil)
|
||||
return hex.EncodeToString(rs), nil
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package aplugin
|
||||
|
||||
// Config is the configuration for the aplugin plugin.
|
||||
type Config struct {
|
||||
// add your config fields
|
||||
}
|
||||
|
||||
// Validate validates the configuration, and return an error if it is invalid.
|
||||
func (c *Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultConfig is the default configuration.
|
||||
var DefaultConfig = Config{}
|
||||
|
||||
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return nil
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
package aplugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
const (
|
||||
Auth = iota + 1 // 连接鉴权
|
||||
Sub // 设备订阅校验
|
||||
Pub // 设备发布校验
|
||||
UnSub
|
||||
Connected
|
||||
Closed
|
||||
)
|
||||
|
||||
type PublishInfo struct {
|
||||
Topic string
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
type MsgAckChan struct {
|
||||
Mu sync.Mutex
|
||||
Id int64
|
||||
IsClosed bool
|
||||
DataChan chan interface{} // auth ack, device sub ack, device pub ack
|
||||
}
|
||||
|
||||
func (mac *MsgAckChan) tryCloseChan() {
|
||||
mac.Mu.Lock()
|
||||
defer mac.Mu.Unlock()
|
||||
if !mac.IsClosed {
|
||||
close(mac.DataChan)
|
||||
mac.IsClosed = true
|
||||
}
|
||||
}
|
||||
|
||||
func (mac *MsgAckChan) trySendDataAndCloseChan(data interface{}) bool {
|
||||
mac.Mu.Lock()
|
||||
defer mac.Mu.Unlock()
|
||||
if !mac.IsClosed {
|
||||
mac.DataChan <- data
|
||||
close(mac.DataChan)
|
||||
mac.IsClosed = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type (
|
||||
// AsyncMsg 异步消息统一收发
|
||||
AsyncMsg struct {
|
||||
Id int64
|
||||
Type int // 1:连接鉴权,2:设备订阅校验,3:设备发布校验,4:unsub,6:closed
|
||||
Data json.RawMessage // auth ack sub ack pub ack
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
AuthCheck struct {
|
||||
ClientId string
|
||||
Username string
|
||||
Password string
|
||||
Pass bool
|
||||
Msg string
|
||||
}
|
||||
)
|
||||
|
||||
type PubTopic struct {
|
||||
ClientId string
|
||||
Username string
|
||||
Topic string
|
||||
QoS byte
|
||||
Retained bool
|
||||
Pass bool
|
||||
Msg string
|
||||
}
|
||||
|
||||
// SubTopic 三方设备或服务订阅topic校验
|
||||
type (
|
||||
SubTopic struct {
|
||||
Topic string
|
||||
QoS byte
|
||||
Pass bool
|
||||
Msg string
|
||||
}
|
||||
SubTopics struct {
|
||||
ClientId string
|
||||
Username string
|
||||
Topics []SubTopic
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// ConnectedNotify 三方设备或服务连接成功后通知对应驱动
|
||||
ConnectedNotify struct {
|
||||
ClientId string
|
||||
Username string
|
||||
IP string
|
||||
Port string
|
||||
}
|
||||
|
||||
// ClosedNotify 三方设备或服务断开连接后通知对应驱动
|
||||
ClosedNotify struct {
|
||||
ClientId string
|
||||
Username string
|
||||
}
|
||||
|
||||
UnSubNotify struct {
|
||||
ClientId string
|
||||
Username string
|
||||
Topics []string
|
||||
}
|
||||
)
|
||||
|
||||
type DriverClient struct {
|
||||
mu sync.RWMutex
|
||||
ClientId string
|
||||
Username string
|
||||
PubTopic string
|
||||
SubTopic string
|
||||
ClientMap map[string]*ThirdClient // key is clientId
|
||||
}
|
||||
|
||||
func (dc *DriverClient) AddThirdClient(client server.Client) {
|
||||
dc.mu.Lock()
|
||||
defer dc.mu.Unlock()
|
||||
dc.ClientMap[client.ClientOptions().ClientID] = newThirdClient(client)
|
||||
}
|
||||
|
||||
func (dc *DriverClient) DeleteThirdClient(clientId string) {
|
||||
dc.mu.Lock()
|
||||
defer dc.mu.Unlock()
|
||||
delete(dc.ClientMap, clientId)
|
||||
}
|
||||
|
||||
type ThirdClient struct {
|
||||
mu sync.RWMutex
|
||||
client server.Client
|
||||
subs map[string]struct{}
|
||||
pubs map[string]struct{}
|
||||
}
|
||||
|
||||
func (tc *ThirdClient) AddTopics(topics []string, t int) {
|
||||
tc.mu.Lock()
|
||||
defer tc.mu.Unlock()
|
||||
|
||||
if t == Sub {
|
||||
for i := range topics {
|
||||
tc.subs[topics[i]] = struct{}{}
|
||||
}
|
||||
} else if t == Pub {
|
||||
for i := range topics {
|
||||
tc.pubs[topics[i]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *ThirdClient) DeleteTopics(topics []string, t int) {
|
||||
tc.mu.Lock()
|
||||
defer tc.mu.Unlock()
|
||||
|
||||
if t == UnSub {
|
||||
for i := range topics {
|
||||
delete(tc.subs, topics[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *ThirdClient) CheckTopic(topic string, t int) bool {
|
||||
tc.mu.RLock()
|
||||
defer tc.mu.RUnlock()
|
||||
|
||||
if t == Sub {
|
||||
_, ok := tc.subs[topic]
|
||||
return ok
|
||||
} else if t == Pub {
|
||||
_, ok := tc.pubs[topic]
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newThirdClient(c server.Client) *ThirdClient {
|
||||
return &ThirdClient{
|
||||
client: c,
|
||||
subs: make(map[string]struct{}),
|
||||
pubs: make(map[string]struct{}),
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package aplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/winc-link/hummingbird/internal/hummingbird/mqttbroker/server"
|
||||
)
|
||||
|
||||
func (t *APlugin) HookWrapper() server.HookWrapper {
|
||||
return server.HookWrapper{}
|
||||
}
|
||||
|
||||
func (t *APlugin) OnBasicAuthWrapper(pre server.OnBasicAuth) server.OnBasicAuth {
|
||||
return func(ctx context.Context, client server.Client, req *server.ConnectRequest) (err error) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) OnSubscribeWrapper(pre server.OnSubscribe) server.OnSubscribe {
|
||||
return func(ctx context.Context, client server.Client, req *server.SubscribeRequest) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) OnUnsubscribeWrapper(pre server.OnUnsubscribe) server.OnUnsubscribe {
|
||||
return func(ctx context.Context, client server.Client, req *server.UnsubscribeRequest) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) OnMsgArrivedWrapper(pre server.OnMsgArrived) server.OnMsgArrived {
|
||||
return func(ctx context.Context, client server.Client, req *server.MsgArrivedRequest) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) OnConnectedWrapper(pre server.OnConnected) server.OnConnected {
|
||||
return func(ctx context.Context, client server.Client) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *APlugin) OnClosedWrapper(pre server.OnClosed) server.OnClosed {
|
||||
return func(ctx context.Context, client server.Client, err error) {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,391 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2016, Bruce
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Package snowflake provides a very simple Twitter snowflake generator and parser.
|
||||
package snowflake
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC in milliseconds
|
||||
// You may customize this to set a different epoch for your application.
|
||||
Epoch int64 = 1288834974657
|
||||
|
||||
// NodeBits holds the number of bits to use for Node
|
||||
// Remember, you have a total 22 bits to share between Node/Step
|
||||
NodeBits uint8 = 10
|
||||
|
||||
// StepBits holds the number of bits to use for Step
|
||||
// Remember, you have a total 22 bits to share between Node/Step
|
||||
StepBits uint8 = 12
|
||||
|
||||
// DEPRECATED: the below four variables will be removed in a future release.
|
||||
mu sync.Mutex
|
||||
nodeMax int64 = -1 ^ (-1 << NodeBits)
|
||||
nodeMask = nodeMax << StepBits
|
||||
stepMask int64 = -1 ^ (-1 << StepBits)
|
||||
timeShift = NodeBits + StepBits
|
||||
nodeShift = StepBits
|
||||
)
|
||||
|
||||
const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769"
|
||||
|
||||
var decodeBase32Map [256]byte
|
||||
|
||||
const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
||||
|
||||
var decodeBase58Map [256]byte
|
||||
|
||||
// A JSONSyntaxError is returned from UnmarshalJSON if an invalid ID is provided.
|
||||
type JSONSyntaxError struct{ original []byte }
|
||||
|
||||
func (j JSONSyntaxError) Error() string {
|
||||
return fmt.Sprintf("invalid snowflake ID %q", string(j.original))
|
||||
}
|
||||
|
||||
// ErrInvalidBase58 is returned by ParseBase58 when given an invalid []byte
|
||||
var ErrInvalidBase58 = errors.New("invalid base58")
|
||||
|
||||
// ErrInvalidBase32 is returned by ParseBase32 when given an invalid []byte
|
||||
var ErrInvalidBase32 = errors.New("invalid base32")
|
||||
|
||||
// Create maps for decoding Base58/Base32.
|
||||
// This speeds up the process tremendously.
|
||||
func init() {
|
||||
|
||||
for i := 0; i < len(decodeBase58Map); i++ {
|
||||
decodeBase58Map[i] = 0xFF
|
||||
}
|
||||
|
||||
for i := 0; i < len(encodeBase58Map); i++ {
|
||||
decodeBase58Map[encodeBase58Map[i]] = byte(i)
|
||||
}
|
||||
|
||||
for i := 0; i < len(decodeBase32Map); i++ {
|
||||
decodeBase32Map[i] = 0xFF
|
||||
}
|
||||
|
||||
for i := 0; i < len(encodeBase32Map); i++ {
|
||||
decodeBase32Map[encodeBase32Map[i]] = byte(i)
|
||||
}
|
||||
}
|
||||
|
||||
// A Node struct holds the basic information needed for a snowflake generator
|
||||
// node
|
||||
type Node struct {
|
||||
mu sync.Mutex
|
||||
epoch time.Time
|
||||
time int64
|
||||
node int64
|
||||
step int64
|
||||
|
||||
nodeMax int64
|
||||
nodeMask int64
|
||||
stepMask int64
|
||||
timeShift uint8
|
||||
nodeShift uint8
|
||||
}
|
||||
|
||||
// An ID is a custom type used for a snowflake ID. This is used so we can
|
||||
// attach methods onto the ID.
|
||||
type ID int64
|
||||
|
||||
// NewNode returns a new snowflake node that can be used to generate snowflake
|
||||
// IDs
|
||||
func NewNode(node int64) (*Node, error) {
|
||||
|
||||
// re-calc in case custom NodeBits or StepBits were set
|
||||
// DEPRECATED: the below block will be removed in a future release.
|
||||
mu.Lock()
|
||||
nodeMax = -1 ^ (-1 << NodeBits)
|
||||
nodeMask = nodeMax << StepBits
|
||||
stepMask = -1 ^ (-1 << StepBits)
|
||||
timeShift = NodeBits + StepBits
|
||||
nodeShift = StepBits
|
||||
mu.Unlock()
|
||||
|
||||
n := Node{}
|
||||
n.node = node
|
||||
n.nodeMax = -1 ^ (-1 << NodeBits)
|
||||
n.nodeMask = n.nodeMax << StepBits
|
||||
n.stepMask = -1 ^ (-1 << StepBits)
|
||||
n.timeShift = NodeBits + StepBits
|
||||
n.nodeShift = StepBits
|
||||
|
||||
if n.node < 0 || n.node > n.nodeMax {
|
||||
return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10))
|
||||
}
|
||||
|
||||
var curTime = time.Now()
|
||||
// add time.Duration to curTime to make sure we use the monotonic clock if available
|
||||
n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime))
|
||||
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// Generate creates and returns a unique snowflake ID
|
||||
// To help guarantee uniqueness
|
||||
// - Make sure your system is keeping accurate system time
|
||||
// - Make sure you never have multiple nodes running with the same node ID
|
||||
func (n *Node) Generate() ID {
|
||||
|
||||
n.mu.Lock()
|
||||
|
||||
now := time.Since(n.epoch).Nanoseconds() / 1000000
|
||||
|
||||
if now == n.time {
|
||||
n.step = (n.step + 1) & n.stepMask
|
||||
|
||||
if n.step == 0 {
|
||||
for now <= n.time {
|
||||
now = time.Since(n.epoch).Nanoseconds() / 1000000
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n.step = 0
|
||||
}
|
||||
|
||||
n.time = now
|
||||
|
||||
r := ID((now)<<n.timeShift |
|
||||
(n.node << n.nodeShift) |
|
||||
(n.step),
|
||||
)
|
||||
|
||||
n.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Int64 returns an int64 of the snowflake ID
|
||||
func (f ID) Int64() int64 {
|
||||
return int64(f)
|
||||
}
|
||||
|
||||
// ParseInt64 converts an int64 into a snowflake ID
|
||||
func ParseInt64(id int64) ID {
|
||||
return ID(id)
|
||||
}
|
||||
|
||||
// String returns a string of the snowflake ID
|
||||
func (f ID) String() string {
|
||||
return strconv.FormatInt(int64(f), 10)
|
||||
}
|
||||
|
||||
// ParseString converts a string into a snowflake ID
|
||||
func ParseString(id string) (ID, error) {
|
||||
i, err := strconv.ParseInt(id, 10, 64)
|
||||
return ID(i), err
|
||||
|
||||
}
|
||||
|
||||
// Base2 returns a string base2 of the snowflake ID
|
||||
func (f ID) Base2() string {
|
||||
return strconv.FormatInt(int64(f), 2)
|
||||
}
|
||||
|
||||
// ParseBase2 converts a Base2 string into a snowflake ID
|
||||
func ParseBase2(id string) (ID, error) {
|
||||
i, err := strconv.ParseInt(id, 2, 64)
|
||||
return ID(i), err
|
||||
}
|
||||
|
||||
// Base32 uses the z-base-32 character set but encodes and decodes similar
|
||||
// to base58, allowing it to create an even smaller result string.
|
||||
// NOTE: There are many different base32 implementations so becareful when
|
||||
// doing any interoperation.
|
||||
func (f ID) Base32() string {
|
||||
|
||||
if f < 32 {
|
||||
return string(encodeBase32Map[f])
|
||||
}
|
||||
|
||||
b := make([]byte, 0, 12)
|
||||
for f >= 32 {
|
||||
b = append(b, encodeBase32Map[f%32])
|
||||
f /= 32
|
||||
}
|
||||
b = append(b, encodeBase32Map[f])
|
||||
|
||||
for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 {
|
||||
b[x], b[y] = b[y], b[x]
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ParseBase32 parses a base32 []byte into a snowflake ID
|
||||
// NOTE: There are many different base32 implementations so becareful when
|
||||
// doing any interoperation.
|
||||
func ParseBase32(b []byte) (ID, error) {
|
||||
|
||||
var id int64
|
||||
|
||||
for i := range b {
|
||||
if decodeBase32Map[b[i]] == 0xFF {
|
||||
return -1, ErrInvalidBase32
|
||||
}
|
||||
id = id*32 + int64(decodeBase32Map[b[i]])
|
||||
}
|
||||
|
||||
return ID(id), nil
|
||||
}
|
||||
|
||||
// Base36 returns a base36 string of the snowflake ID
|
||||
func (f ID) Base36() string {
|
||||
return strconv.FormatInt(int64(f), 36)
|
||||
}
|
||||
|
||||
// ParseBase36 converts a Base36 string into a snowflake ID
|
||||
func ParseBase36(id string) (ID, error) {
|
||||
i, err := strconv.ParseInt(id, 36, 64)
|
||||
return ID(i), err
|
||||
}
|
||||
|
||||
// Base58 returns a base58 string of the snowflake ID
|
||||
func (f ID) Base58() string {
|
||||
|
||||
if f < 58 {
|
||||
return string(encodeBase58Map[f])
|
||||
}
|
||||
|
||||
b := make([]byte, 0, 11)
|
||||
for f >= 58 {
|
||||
b = append(b, encodeBase58Map[f%58])
|
||||
f /= 58
|
||||
}
|
||||
b = append(b, encodeBase58Map[f])
|
||||
|
||||
for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 {
|
||||
b[x], b[y] = b[y], b[x]
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ParseBase58 parses a base58 []byte into a snowflake ID
|
||||
func ParseBase58(b []byte) (ID, error) {
|
||||
|
||||
var id int64
|
||||
|
||||
for i := range b {
|
||||
if decodeBase58Map[b[i]] == 0xFF {
|
||||
return -1, ErrInvalidBase58
|
||||
}
|
||||
id = id*58 + int64(decodeBase58Map[b[i]])
|
||||
}
|
||||
|
||||
return ID(id), nil
|
||||
}
|
||||
|
||||
// Base64 returns a base64 string of the snowflake ID
|
||||
func (f ID) Base64() string {
|
||||
return base64.StdEncoding.EncodeToString(f.Bytes())
|
||||
}
|
||||
|
||||
// ParseBase64 converts a base64 string into a snowflake ID
|
||||
func ParseBase64(id string) (ID, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(id)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return ParseBytes(b)
|
||||
|
||||
}
|
||||
|
||||
// Bytes returns a byte slice of the snowflake ID
|
||||
func (f ID) Bytes() []byte {
|
||||
return []byte(f.String())
|
||||
}
|
||||
|
||||
// ParseBytes converts a byte slice into a snowflake ID
|
||||
func ParseBytes(id []byte) (ID, error) {
|
||||
i, err := strconv.ParseInt(string(id), 10, 64)
|
||||
return ID(i), err
|
||||
}
|
||||
|
||||
// IntBytes returns an array of bytes of the snowflake ID, encoded as a
|
||||
// big endian integer.
|
||||
func (f ID) IntBytes() [8]byte {
|
||||
var b [8]byte
|
||||
binary.BigEndian.PutUint64(b[:], uint64(f))
|
||||
return b
|
||||
}
|
||||
|
||||
// ParseIntBytes converts an array of bytes encoded as big endian integer as
|
||||
// a snowflake ID
|
||||
func ParseIntBytes(id [8]byte) ID {
|
||||
return ID(int64(binary.BigEndian.Uint64(id[:])))
|
||||
}
|
||||
|
||||
// Time returns an int64 unix timestamp in milliseconds of the snowflake ID time
|
||||
// DEPRECATED: the below function will be removed in a future release.
|
||||
func (f ID) Time() int64 {
|
||||
return (int64(f) >> timeShift) + Epoch
|
||||
}
|
||||
|
||||
// Node returns an int64 of the snowflake ID node number
|
||||
// DEPRECATED: the below function will be removed in a future release.
|
||||
func (f ID) Node() int64 {
|
||||
return int64(f) & nodeMask >> nodeShift
|
||||
}
|
||||
|
||||
// Step returns an int64 of the snowflake step (or sequence) number
|
||||
// DEPRECATED: the below function will be removed in a future release.
|
||||
func (f ID) Step() int64 {
|
||||
return int64(f) & stepMask
|
||||
}
|
||||
|
||||
// MarshalJSON returns a json byte array string of the snowflake ID.
|
||||
func (f ID) MarshalJSON() ([]byte, error) {
|
||||
buff := make([]byte, 0, 22)
|
||||
buff = append(buff, '"')
|
||||
buff = strconv.AppendInt(buff, int64(f), 10)
|
||||
buff = append(buff, '"')
|
||||
return buff, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a json byte array of a snowflake ID into an ID type.
|
||||
func (f *ID) UnmarshalJSON(b []byte) error {
|
||||
if len(b) < 3 || b[0] != '"' || b[len(b)-1] != '"' {
|
||||
return JSONSyntaxError{b}
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(string(b[1:len(b)-1]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*f = ID(i)
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue