refactor: rm mqtt broker & rm db file

master
hebl 2024-06-23 10:06:45 +08:00
parent 2c16f21ff1
commit b6b8094b83
182 changed files with 84 additions and 29777 deletions

5
.gitignore vendored
View File

@ -13,4 +13,7 @@ hummingbird
## binary
cmd/hummingbird-core/hummingbird-core
cmd/mqtt-broker/mqtt-broker
cmd/mqtt-broker/mqtt-broker
kuiper
db-data/leveldb-core-data

View File

@ -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"]

View File

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

View File

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

View File

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

View File

@ -1,9 +0,0 @@
package mqttd
var (
DefaultConfigDir = "./res/"
)
func GetDefaultConfigDir() (string, error) {
return DefaultConfigDir, nil
}

View File

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

View File

@ -1,6 +0,0 @@
packages:
- admin
# - federation
- aplugin
# for external plugin, use full import path
# - gitlab.com/tedge/edgex/internal/thummingbird/mqttbroker/plugin/prometheus

View File

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

View File

@ -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 clients 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 subscriptions 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

View File

@ -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 clients 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 subscriptions 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

View File

@ -1,3 +0,0 @@
# This is a sample plain password file for the auth plugin.
- username: root
password: root

View File

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

View File

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

View File

@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
listeners:
mqtt:
log:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:"*"
};
}
}

View File

@ -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:"*"
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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