├── .gitignore
├── mqtt
├── topic.go
├── client_rep.go
├── message.go
├── redis.go
├── mqtt.go
├── parser.go
└── cmd_func.go
├── testing
├── test-redis.go
└── test.py
├── README.md
├── COPYING
└── gossipd.go
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.bench
3 |
--------------------------------------------------------------------------------
/mqtt/topic.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type Topic struct {
8 | Content string
9 | RetainedMessage *MqttMessage
10 | }
11 |
12 | var G_topicss map[string]*Topic = make(map[string]*Topic)
13 | var G_topics_lockk *sync.Mutex = new(sync.Mutex)
14 |
15 | func CreateTopic(content string) *Topic {
16 | topic := new(Topic)
17 | topic.Content = content
18 | topic.RetainedMessage = nil
19 | return topic
20 | }
--------------------------------------------------------------------------------
/testing/test-redis.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/garyburd/redigo/redis"
5 | "fmt"
6 | "encoding/gob"
7 | "bytes"
8 | "log"
9 | )
10 |
11 | type P struct {
12 | X, Y, Z int
13 | Name string
14 | }
15 |
16 | func main() {
17 | fmt.Println("Testing Redis...")
18 | c, err := redis.Dial("tcp", "localhost:6379")
19 |
20 | if err != nil {
21 | fmt.Println("failed to connec")
22 | } else {
23 | fmt.Println("connected")
24 | }
25 |
26 | defer c.Close()
27 |
28 | v, err := c.Do("PING")
29 | fmt.Printf("val:(%s), err:(%s)\n", v, err)
30 |
31 |
32 | v, err = redis.Int64(c.Do("GET", "target"))
33 | fmt.Printf("val:(%d), err:(%s)\n", v, err)
34 | if err != nil {
35 | fmt.Println(err)
36 | }
37 |
38 | return
39 |
40 | ret, _ := redis.Values(c.Do("KEYS", "*"))
41 | for _, key := range(ret) {
42 | sk := string(key.([]byte))
43 | fmt.Println(sk)
44 | }
45 |
46 | p := P{50, 210, 46, "AdaBoost"}
47 | var buf bytes.Buffer
48 | enc := gob.NewEncoder(&buf)
49 | err = enc.Encode(&p)
50 | if (err != nil) {
51 | log.Fatal("encode error:", err)
52 | }
53 |
54 | c.Do("SET", "gob", buf.Bytes())
55 |
56 | val, _ := redis.Bytes(c.Do("GET", "gob"))
57 | buf = *bytes.NewBuffer(val)
58 | var q P
59 | dec := gob.NewDecoder(&buf)
60 | err = dec.Decode(&q)
61 | if (err != nil) {
62 | log.Fatal("decode error:", err)
63 | }
64 |
65 | fmt.Printf("P is: %q: {%d,%d,%d}\n", q.Name, q.X, q.Y, q.Z)
66 | }
--------------------------------------------------------------------------------
/mqtt/client_rep.go:
--------------------------------------------------------------------------------
1 | /* Client representation*/
2 |
3 | package mqtt
4 |
5 | import (
6 | "net"
7 | "sync"
8 | "sync/atomic"
9 | "time"
10 | )
11 |
12 | var NextClientMessageId map[string]uint16 = make(map[string]uint16)
13 | var g_next_client_id_lock *sync.Mutex = new(sync.Mutex)
14 |
15 | type ClientRep struct {
16 | ClientId string
17 | Conn *net.Conn
18 | WriteLock *sync.Mutex
19 | LastTime int64 // Last Unix timestamp when recieved message from this client
20 | Shuttingdown chan uint8
21 | Subscriptions map[string]uint8
22 | Mqtt *Mqtt
23 | Disconnected bool
24 | }
25 |
26 | func (cr *ClientRep) UpdateLastTime() {
27 | atomic.StoreInt64(&cr.LastTime, time.Now().Unix())
28 | }
29 |
30 | func CreateClientRep(client_id string, conn *net.Conn, mqtt *Mqtt) *ClientRep {
31 | rep := new(ClientRep)
32 | rep.ClientId = client_id
33 | rep.Conn = conn
34 | rep.WriteLock = new(sync.Mutex)
35 | rep.Mqtt = mqtt
36 | rep.LastTime = time.Now().Unix()
37 | rep.Shuttingdown = make(chan uint8, 1)
38 | rep.Subscriptions = make(map[string]uint8)
39 | rep.Disconnected = false
40 | return rep
41 | }
42 |
43 | func NextOutMessageIdForClient(client_id string) uint16 {
44 | g_next_client_id_lock.Lock()
45 | defer g_next_client_id_lock.Unlock()
46 |
47 | next_id, found := NextClientMessageId[client_id]
48 | if !found {
49 | NextClientMessageId[client_id] = 1
50 | return 0
51 | }
52 | NextClientMessageId[client_id] = next_id + 1
53 | return next_id
54 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #What is Gossipd?#
2 | Gossipd is an implementation of [MQTT 3.1](http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html) broker written in Go. MQTT is an excellent protocol for mobile messaging. Facebook built the new Facebook Messager with MQTT.
3 |
4 | #What is Gossipd for?#
5 | The main reason I'm writing this is because all major open source MQTT brokers, like [Mosquitto](http://mosquitto.org/), didn't balance well between scalability and ease of use.
6 |
7 | Gossipd should be considered when the 'select' based Mosquitto can't meet your scale requirement, yet only the 'basic' part(see [Not supported features](#unsupported) for detail) of MQTT is needed in your project. Gossipd is built with performance at heart.
8 |
9 | #Usage#
10 | To use Gossipd, run:
11 |
12 | >go run gossipd
13 |
14 | The broker will start and listen on port 1883 for MQTT traffic. Command line flags:
15 |
16 | * -p PORT: specify MQTT broker's port, default is 1883
17 | * -r PORT: specify Redis's port, default is 6379
18 | * -d: when set comprehensive debugging info will be printed, this may significantly harm performance.
19 |
20 | #Dependency#
21 | * [Redis](http://redis.io) for storage
22 | * [Redigo](https://github.com/garyburd/redigo) as Redis binding for golang.
23 | * [Seelog](https://github.com/cihub/seelog) for logging
24 |
25 | #Not supported features#
26 | * QOS level 2 is not supported. Only support QOS 1 and 0.
27 |
28 | * Topic wildcard is not supported. Topic is always treated as plain string.
29 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Junyi
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of gossipd nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/mqtt/message.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import (
4 | "sync"
5 | "sync/atomic"
6 | "fmt"
7 | )
8 |
9 | /*
10 | This is the type represents a message received from publisher.
11 | FlyingMessage(message should be delivered to specific subscribers)
12 | reference MqttMessage
13 | */
14 | type MqttMessage struct {
15 | Topic string
16 | Payload string
17 | Qos uint8
18 | SenderClientId string
19 | MessageId uint16
20 | InternalId uint64
21 | CreatedAt int64
22 | Retain bool
23 | }
24 |
25 | func (msg *MqttMessage) Show() {
26 | fmt.Printf("MQTT Message:\n")
27 | fmt.Println("Topic:", msg.Topic)
28 | fmt.Println("Payload:", msg.Payload)
29 | fmt.Println("Qos:", msg.Qos)
30 | fmt.Println("SenderClientId:", msg.SenderClientId)
31 | fmt.Println("MessageId:", msg.MessageId)
32 | fmt.Println("InternalId:", msg.InternalId)
33 | fmt.Println("CreatedAt:", msg.CreatedAt)
34 | fmt.Println("Retain:", msg.Retain)
35 | }
36 |
37 | func (msg *MqttMessage) RedisKey() string {
38 | return fmt.Sprintf("gossipd.mqtt-msg.%d", msg.InternalId)
39 | }
40 |
41 | func (msg *MqttMessage) Store() {
42 | key := msg.RedisKey()
43 | G_redis_client.Store(key, msg)
44 | G_redis_client.Expire(key, 7 * 24 * 3600)
45 | }
46 |
47 | // InternalId -> Message
48 | // FIXME: Add code to store G_messages to disk
49 | var G_messages map[uint64]*MqttMessage = make(map[uint64]*MqttMessage)
50 | var G_messages_lock *sync.Mutex = new(sync.Mutex)
51 |
52 | func CreateMqttMessage(topic, payload, sender_id string,
53 | qos uint8, message_id uint16,
54 | created_at int64, retain bool) *MqttMessage {
55 |
56 | msg := new(MqttMessage)
57 | msg.Topic = topic
58 | msg.Payload = payload
59 | msg.Qos = qos
60 | msg.SenderClientId = sender_id
61 | msg.MessageId = message_id
62 | msg.InternalId = GetNextMessageInternalId()
63 | msg.CreatedAt = created_at
64 | msg.Retain = retain
65 |
66 | G_messages_lock.Lock()
67 | G_messages[msg.InternalId] = msg
68 | G_messages_lock.Unlock()
69 |
70 | msg.Store()
71 |
72 | return msg
73 | }
74 |
75 | var g_next_mqtt_message_internal_id uint64 = 0
76 | func GetNextMessageInternalId() uint64 {
77 | return atomic.AddUint64(&g_next_mqtt_message_internal_id, 1)
78 | }
79 |
80 | // This is thread-safe
81 | func GetMqttMessageById(internal_id uint64) *MqttMessage{
82 | key := fmt.Sprintf("gossipd.mqtt-msg.%d", internal_id)
83 |
84 | msg := new(MqttMessage)
85 | G_redis_client.Fetch(key, msg)
86 | return msg
87 | }
88 |
89 |
90 | /*
91 | This is the type represents a message should be delivered to
92 | specific client
93 | */
94 | type FlyingMessage struct {
95 | Qos uint8 // the Qos in effect
96 | DestClientId string
97 | MessageInternalId uint64 // The MqttMessage of interest
98 | Status uint8 // The status of this message, like PENDING_PUB(deliver occured
99 | // when client if offline), PENDING_ACK, etc
100 | ClientMessageId uint16 // The message id to be used in MQTT packet
101 | }
102 |
103 | const(
104 | PENDING_PUB = uint8(iota + 1)
105 | PENDING_ACK
106 | )
107 |
108 | func CreateFlyingMessage(dest_id string, message_internal_id uint64,
109 | qos uint8, status uint8, message_id uint16) *FlyingMessage {
110 | msg := new(FlyingMessage)
111 | msg.Qos = qos
112 | msg.DestClientId = dest_id
113 | msg.MessageInternalId = message_internal_id
114 | msg.Status = status
115 | msg.ClientMessageId = message_id
116 | return msg
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/gossipd.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "flag"
6 | log "github.com/cihub/seelog"
7 | "net"
8 | "os"
9 | "runtime/debug"
10 | "github.com/luanjunyi/gossipd/mqtt"
11 | )
12 |
13 | type CmdFunc func(mqtt *mqtt.Mqtt, conn *net.Conn, client **mqtt.ClientRep)
14 |
15 | var g_debug = flag.Bool("d", false, "enable debugging log")
16 | var g_port = flag.Int("p", 1883, "port of the broker to listen")
17 | var g_redis_port = flag.Int("r", 6379, "port of the broker to listen")
18 |
19 | var g_cmd_route = map[uint8]CmdFunc {
20 | mqtt.CONNECT: mqtt.HandleConnect,
21 | mqtt.PUBLISH: mqtt.HandlePublish,
22 | mqtt.SUBSCRIBE: mqtt.HandleSubscribe,
23 | mqtt.UNSUBSCRIBE: mqtt.HandleUnsubscribe,
24 | mqtt.PINGREQ: mqtt.HandlePingreq,
25 | mqtt.DISCONNECT: mqtt.HandleDisconnect,
26 | mqtt.PUBACK: mqtt.HandlePuback,
27 | }
28 |
29 | func handleConnection(conn *net.Conn) {
30 | remoteAddr := (*conn).RemoteAddr()
31 | var client *mqtt.ClientRep = nil
32 |
33 | defer func() {
34 | log.Debug("executing defered func in handleConnection")
35 | if r := recover(); r != nil {
36 | log.Debugf("got panic:(%s) will close connection from %s:%s", r, remoteAddr.Network(), remoteAddr.String())
37 | debug.PrintStack()
38 | }
39 | if client != nil {
40 | mqtt.ForceDisconnect(client, mqtt.G_clients_lock, mqtt.SEND_WILL)
41 | }
42 | (*conn).Close()
43 | }()
44 |
45 | var conn_str string = fmt.Sprintf("%s:%s", string(remoteAddr.Network()), remoteAddr.String())
46 | log.Debug("Got new conection", conn_str)
47 | for {
48 | // Read fixed header
49 | fixed_header, body := mqtt.ReadCompleteCommand(conn)
50 | if (fixed_header == nil) {
51 | log.Debug(conn_str, "reading header returned nil, will disconnect")
52 | return
53 | }
54 |
55 | mqtt_parsed, err := mqtt.DecodeAfterFixedHeader(fixed_header, body)
56 | if (err != nil) {
57 | log.Debug(conn_str, "read command body failed:", err.Error())
58 | }
59 |
60 | var client_id string
61 | if client == nil {
62 | client_id = ""
63 | } else {
64 | client_id = client.ClientId
65 | }
66 | log.Debugf("Got request: %s from %s", mqtt.MessageTypeStr(fixed_header.MessageType), client_id)
67 | proc, found := g_cmd_route[fixed_header.MessageType]
68 | if !found {
69 | log.Debugf("Handler func not found for message type: %d(%s)",
70 | fixed_header.MessageType, mqtt.MessageTypeStr(fixed_header.MessageType))
71 | return
72 | }
73 | proc(mqtt_parsed, conn, &client)
74 | }
75 | }
76 |
77 | func setup_logging() {
78 | level := "info"
79 | if *g_debug == true {
80 | level = "debug"
81 | }
82 | config := fmt.Sprintf(`
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | `, level)
91 |
92 | logger, err := log.LoggerFromConfigAsBytes([]byte(config))
93 |
94 | if err != nil {
95 | fmt.Println("Failed to config logging:", err)
96 | os.Exit(1)
97 | }
98 |
99 | log.ReplaceLogger(logger)
100 |
101 | log.Info("Logging config is successful")
102 | }
103 |
104 | func main() {
105 | flag.Parse()
106 |
107 | setup_logging()
108 |
109 | mqtt.RecoverFromRedis()
110 |
111 | log.Debugf("Gossipd kicking off, listening localhost:%d", *g_port)
112 |
113 | link, _ := net.Listen("tcp", fmt.Sprintf(":%d", *g_port))
114 |
115 | for {
116 | conn, err := link.Accept()
117 | if err != nil {
118 | continue
119 | }
120 | go handleConnection(&conn)
121 | }
122 | defer link.Close()
123 | }
--------------------------------------------------------------------------------
/mqtt/redis.go:
--------------------------------------------------------------------------------
1 | // FIXME: handle disconnection from redis
2 |
3 | package mqtt
4 |
5 | import (
6 | "github.com/garyburd/redigo/redis"
7 | log "github.com/cihub/seelog"
8 | "bytes"
9 | "encoding/gob"
10 | "sync"
11 | "fmt"
12 | "time"
13 | )
14 |
15 | var g_redis_lock *sync.Mutex = new(sync.Mutex)
16 |
17 | type RedisClient struct {
18 | Conn *redis.Conn
19 | }
20 |
21 |
22 | func StartRedisClient() *RedisClient {
23 | conn, err := redis.Dial("tcp", ":6379")
24 |
25 | if err != nil {
26 | panic("Failed to connect to Redis at port 6379")
27 | } else {
28 | log.Info("Redis client started")
29 | }
30 |
31 | client := new(RedisClient)
32 | client.Conn = &conn
33 |
34 | go ping_pong_redis(client, 240)
35 | return client
36 | }
37 |
38 | func ping_pong_redis(client *RedisClient, interval int) {
39 | c := time.Tick(time.Duration(interval) * time.Second)
40 | for _ = range c {
41 | g_redis_lock.Lock()
42 | (*client.Conn).Do("PING")
43 | g_redis_lock.Unlock()
44 | log.Debugf("sent PING to redis")
45 | }
46 | }
47 |
48 | func (client* RedisClient) Reconnect() {
49 | log.Debugf("aqquiring g_redis_lock")
50 |
51 | conn, err := redis.Dial("tcp", ":6379")
52 | if err != nil {
53 | panic("Failed to connect to Redis at port 6379")
54 | } else {
55 | log.Info("Redis client reconncted")
56 | }
57 | client.Conn = &conn
58 | }
59 |
60 | func (client *RedisClient) Store(key string, value interface{}) {
61 | log.Debugf("aqquiring g_redis_lock, store key=(%s)", key)
62 | g_redis_lock.Lock()
63 | defer g_redis_lock.Unlock()
64 | log.Debugf("aqquired g_redis_lock, store key=(%s)", key)
65 |
66 | client.StoreNoLock(key, value)
67 | }
68 |
69 | func (client *RedisClient) StoreNoLock(key string, value interface{}) {
70 | var buf bytes.Buffer
71 | enc := gob.NewEncoder(&buf)
72 | err := enc.Encode(value)
73 | if err != nil {
74 | panic(fmt.Sprintf("gob encoding failed, error:(%s)", err))
75 | }
76 |
77 | ret, err := (*client.Conn).Do("SET", key, buf.Bytes())
78 | if err != nil {
79 | if err.Error() == "use of closed network connection" {
80 | client.Reconnect()
81 | client.StoreNoLock(key, value)
82 | return
83 | } else {
84 | panic(fmt.Sprintf("redis failed to set key(%s): %s", key, err))
85 | }
86 | }
87 | log.Debugf("stored to redis, key=%s, val(some bytes), returned=%s",
88 | key, ret)
89 | }
90 |
91 | func (client *RedisClient) Fetch(key string, value interface{}) int {
92 | log.Debugf("aqquiring g_redis_lock, fetch key=(%s)", key)
93 | g_redis_lock.Lock()
94 | defer g_redis_lock.Unlock()
95 | log.Debugf("aqquired g_redis_lock, fetch key=(%s)", key)
96 |
97 | return client.FetchNoLock(key, value)
98 | }
99 |
100 | func (client *RedisClient) FetchNoLock(key string, value interface{}) int {
101 | str, err := redis.Bytes((*client.Conn).Do("GET", key))
102 | if err != nil {
103 | if err.Error() == "use of closed network connection" {
104 | client.Reconnect()
105 | return client.FetchNoLock(key, value)
106 | } else {
107 | log.Debugf("redis failed to fetch key(%s): %s",
108 | key, err)
109 | return 1
110 | }
111 | }
112 | buf := bytes.NewBuffer(str)
113 | dec := gob.NewDecoder(buf)
114 | err = dec.Decode(value)
115 |
116 | if (err != nil) {
117 | panic(fmt.Sprintf("gob decode failed, key=(%s), value=(%s), error:%s", key, str, err))
118 | }
119 | return 0
120 | }
121 |
122 | func (client *RedisClient) GetSubsClients() []string {
123 | g_redis_lock.Lock()
124 | defer g_redis_lock.Unlock()
125 | keys, _ := redis.Values((*client.Conn).Do("KEYS", "gossipd.client-subs.*"))
126 | clients := make([]string, 0)
127 | for _, key := range(keys) {
128 | clients = append(clients, string(key.([]byte)))
129 | }
130 | return clients
131 | }
132 |
133 | func (client *RedisClient) Delete(key string) {
134 | g_redis_lock.Lock()
135 | defer g_redis_lock.Unlock()
136 | (*client.Conn).Do("DEL", key)
137 | }
138 |
139 | func (client *RedisClient) GetRetainMessage(topic string) *MqttMessage {
140 | msg := new(MqttMessage)
141 | key := fmt.Sprintf("gossipd.topic-retained.%s", topic)
142 | var internal_id uint64
143 | ret := client.Fetch(key, &internal_id)
144 | if ret != 0 {
145 | log.Debugf("retained message internal id not found in redis for topic(%s)", topic)
146 | return nil
147 | }
148 |
149 | key = fmt.Sprintf("gossipd.mqtt-msg.%d", internal_id)
150 | ret = client.Fetch(key, &msg)
151 | if ret != 0 {
152 | log.Debugf("retained message, though internal id found, not found in redis for topic(%s)", topic)
153 | return nil
154 | }
155 | return msg
156 | }
157 |
158 | func (client *RedisClient) SetRetainMessage(topic string, msg *MqttMessage) {
159 | key := fmt.Sprintf("gossipd.topic-retained.%s", topic)
160 | internal_id := msg.InternalId
161 | client.Store(key, internal_id)
162 | }
163 |
164 | func (client *RedisClient) GetFlyingMessagesForClient(client_id string) *map[uint16]FlyingMessage {
165 | key := fmt.Sprintf("gossipd.client-msg.%s", client_id)
166 | messages := make(map[uint16]FlyingMessage)
167 | client.Fetch(key, &messages)
168 | return &messages
169 | }
170 |
171 | func (client *RedisClient) SetFlyingMessagesForClient(client_id string,
172 | messages *map[uint16]FlyingMessage) {
173 | key := fmt.Sprintf("gossipd.client-msg.%s", client_id)
174 | client.Store(key, messages)
175 | }
176 |
177 | func (client *RedisClient) RemoveAllFlyingMessagesForClient(client_id string) {
178 | key := fmt.Sprintf("gossipd.client-msg.%s", client_id)
179 | g_redis_lock.Lock()
180 | defer g_redis_lock.Unlock()
181 | (*client.Conn).Do("DEL", key)
182 | }
183 |
184 | func (client *RedisClient) AddFlyingMessage(dest_id string,
185 | fly_msg *FlyingMessage) {
186 |
187 | messages := *client.GetFlyingMessagesForClient(dest_id)
188 | messages[fly_msg.ClientMessageId] = *fly_msg
189 | client.SetFlyingMessagesForClient(dest_id, &messages)
190 | log.Debugf("Added flying message to redis client:(%s), message_id:(%d)",
191 | dest_id, fly_msg.ClientMessageId)
192 | }
193 |
194 | func (client *RedisClient) IsFlyingMessagePendingAck(client_id string, message_id uint16) bool {
195 | messages := G_redis_client.GetFlyingMessagesForClient(client_id)
196 |
197 | flying_msg, found := (*messages)[message_id]
198 |
199 | return found && flying_msg.Status == PENDING_ACK
200 | }
201 |
202 | func (client *RedisClient) Expire(key string, sec uint64) {
203 | g_redis_lock.Lock()
204 | defer g_redis_lock.Unlock()
205 |
206 | _, err := (*client.Conn).Do("EXPIRE", key, sec)
207 | if err != nil {
208 | panic(fmt.Sprintf("failed to expire key(%s)", key))
209 | }
210 | }
--------------------------------------------------------------------------------
/mqtt/mqtt.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "net"
7 | "io"
8 | log "github.com/cihub/seelog"
9 | )
10 |
11 | /* Glabal status */
12 | var G_clients map[string]*ClientRep = make(map[string]*ClientRep)
13 | var G_clients_lock *sync.Mutex = new(sync.Mutex)
14 | // Map topic => sub
15 | // sub is implemented as map, key is client_id, value is qos
16 | var G_subs map[string]map[string]uint8 = make(map[string]map[string]uint8)
17 | var G_subs_lock *sync.Mutex = new(sync.Mutex)
18 | var G_redis_client *RedisClient = StartRedisClient()
19 |
20 | // This function should be called upon starting up. It will
21 | // try to recover global status(G_subs, etc) from Redis.
22 | func RecoverFromRedis() {
23 | // Reconstruct the subscription map from redis records
24 | client_id_keys := G_redis_client.GetSubsClients()
25 | log.Info("Recovering subscription info from Redis")
26 | for _, client_id_key := range(client_id_keys) {
27 | sub_map := make(map[string]uint8)
28 | G_redis_client.Fetch(client_id_key, &sub_map)
29 | var client_id string
30 | fmt.Sscanf(client_id_key, "gossipd.client-subs.%s", &client_id)
31 |
32 | for topic, qos := range(sub_map) {
33 | // lock won't be needed since this is at the startup phase
34 | subs := G_subs[topic]
35 | if subs == nil {
36 | log.Debug("current subscription is the first client to topic:", topic)
37 | subs = make(map[string]uint8)
38 | G_subs[topic] = subs
39 | }
40 | subs[client_id] = qos
41 | }
42 | log.Debugf("client(%s) subscription info recovered", client_id)
43 | }
44 | }
45 |
46 | func (mqtt *Mqtt)Show() {
47 | if mqtt.FixedHeader != nil {
48 | mqtt.FixedHeader.Show()
49 | } else {
50 | log.Debug("Fixed header is nil")
51 | }
52 |
53 | if mqtt.ConnectFlags != nil {
54 | mqtt.ConnectFlags.Show()
55 | } else {
56 | log.Debug("ConnectFlags is nil")
57 | }
58 |
59 | fmt.Println("ProtocolName:", mqtt.ProtocolName)
60 | fmt.Println("Version:", mqtt.ProtocolVersion)
61 | fmt.Println("TopicName:", mqtt.TopicName)
62 | fmt.Println("ClientId:", mqtt.ClientId)
63 | fmt.Println("WillTopic:", mqtt.WillTopic)
64 | fmt.Println("WillMessage:", mqtt.WillMessage)
65 | fmt.Println("Username:", mqtt.Username)
66 | fmt.Println("Password:", mqtt.Password)
67 | fmt.Println("KeepAliveTimer:", mqtt.KeepAliveTimer)
68 | fmt.Println("MessageId:", mqtt.MessageId)
69 |
70 | fmt.Println("Data:", mqtt.Data)
71 | fmt.Println("Topics:", len(mqtt.Topics))
72 | for i := 0; i < len(mqtt.Topics); i++ {
73 | fmt.Printf("(%s) (qos=%d)\n", mqtt.Topics[i], mqtt.Topics_qos[i])
74 | }
75 | fmt.Println("ReturnCode:", mqtt.ReturnCode)
76 | }
77 |
78 | func (header *FixedHeader)Show() {
79 | fmt.Println("header detail:")
80 | fmt.Println("message type: ", MessageTypeStr(header.MessageType))
81 | fmt.Println("DupFlag: ", header.DupFlag)
82 | fmt.Println("Retain: ", header.Retain)
83 | fmt.Println("QOS: ", header.QosLevel)
84 | fmt.Println("length: ", header.Length)
85 | fmt.Println("\n=====================\n")
86 | }
87 |
88 | func (flags *ConnectFlags)Show() {
89 | fmt.Println("connect flags detail:")
90 | fmt.Println("UsernameFlag:", flags.UsernameFlag)
91 | fmt.Println("PasswordFlag:", flags.PasswordFlag)
92 | fmt.Println("WillRetain:", flags.WillRetain)
93 | fmt.Println("WillFlag:", flags.WillFlag)
94 | fmt.Println("WillQos:", flags.WillQos)
95 | fmt.Println("CleanSession:", flags.CleanSession)
96 | fmt.Println("\n=====================\n")
97 | }
98 |
99 | func ReadFixedHeader(conn *net.Conn) *FixedHeader {
100 | var buf = make([]byte, 2)
101 | n, _ := io.ReadFull(*conn, buf)
102 | if n != len(buf) {
103 | log.Debug("read header failed")
104 | return nil
105 | }
106 |
107 | byte1 := buf[0]
108 | header := new(FixedHeader)
109 | header.MessageType = uint8(byte1 & 0xF0 >> 4)
110 | header.DupFlag = byte1 & 0x08 > 0
111 | header.QosLevel = uint8(byte1 & 0x06 >> 1)
112 | header.Retain = byte1 & 0x01 > 0
113 |
114 | byte2 := buf[1]
115 | header.Length = decodeVarLength(byte2, conn)
116 | return header
117 | }
118 |
119 | func ReadCompleteCommand(conn *net.Conn) (*FixedHeader, []byte) {
120 | fixed_header := ReadFixedHeader(conn)
121 | if fixed_header == nil {
122 | log.Debug("failed to read fixed header")
123 | return nil, make([]byte, 0)
124 | }
125 | length := fixed_header.Length
126 | buf := make([]byte, length)
127 | n, _ := io.ReadFull(*conn, buf)
128 | if uint32(n) != length {
129 | panic(fmt.Sprintf("failed to read %d bytes specified in fixed header, only %d read", length, n))
130 | }
131 | log.Debugf("Complete command(%s) read into buffer", MessageTypeStr(fixed_header.MessageType))
132 |
133 | return fixed_header, buf
134 | }
135 |
136 | // CONNECT parse
137 | func parseConnectInfo(buf []byte) *ConnectInfo {
138 | var info = new(ConnectInfo)
139 | info.Protocol, buf = parseUTF8(buf)
140 | info.Version, buf = parseUint8(buf)
141 | flagByte := buf[0]
142 | log.Debug("parsing connect flag:", flagByte)
143 | info.UsernameFlag = (flagByte & 0x80) > 0
144 | info.PasswordFlag = (flagByte & 0x40) > 0
145 | info.WillRetain = (flagByte & 0x20) > 0
146 | info.WillQos = uint8(flagByte & 0x18 >> 3)
147 | info.WillFlag = (flagByte & 0x04) > 0
148 | info.CleanSession = (flagByte & 0x02) > 0
149 | buf = buf[1:]
150 | info.Keepalive, _ = parseUint16(buf)
151 | return info
152 | }
153 |
154 | func (con_info *ConnectInfo)Show() {
155 | fmt.Println("connect info detail:")
156 | fmt.Println("Protocol:", con_info.Protocol)
157 | fmt.Println("Version:", con_info.Version)
158 | fmt.Println("Username Flag:", con_info.UsernameFlag)
159 | fmt.Println("Password Flag:", con_info.PasswordFlag)
160 | fmt.Println("WillRetain:", con_info.WillRetain)
161 | fmt.Println("WillQos:", con_info.WillQos)
162 | fmt.Println("WillFlag:", con_info.WillFlag)
163 | fmt.Println("CleanSession:", con_info.CleanSession)
164 |
165 | fmt.Println("Keepalive:", con_info.Keepalive)
166 | }
167 |
168 | // Fixed header parse
169 |
170 | func decodeVarLength(cur byte, conn *net.Conn) uint32 {
171 | length := uint32(0)
172 | multi := uint32(1)
173 |
174 | for {
175 | length += multi * uint32(cur & 0x7f)
176 | if cur & 0x80 == 0 {
177 | break
178 | }
179 | buf := make([]byte, 1)
180 | n, _ := io.ReadFull(*conn, buf)
181 | if n != 1 {
182 | panic("failed to read variable length in MQTT header")
183 | }
184 | cur = buf[0]
185 | multi *= 128
186 | }
187 |
188 | return length
189 | }
190 |
191 | func parseUint16(buf []byte) (uint16, []byte) {
192 | return uint16(buf[0] << 8) + uint16(buf[1]), buf[2:]
193 | }
194 |
195 | func parseUint8(buf []byte) (uint8, []byte) {
196 | return uint8(buf[0]), buf[1:]
197 | }
198 |
199 | func parseUTF8(buf []byte) (string, []byte) {
200 | length, buf := parseUint16(buf)
201 | str := buf [: length]
202 | return string(str), buf[length:]
203 | }
204 |
205 | func MessageTypeStr(mt uint8) string {
206 | var strArray = []string {
207 | "reserved",
208 | "CONNECT",
209 | "CONNACK",
210 | "PUBLISH",
211 | "PUBACK",
212 | "PUBREC",
213 | "PUBREL",
214 | "PUBCOMP",
215 | "SUBSCRIBE",
216 | "SUBACK",
217 | "UNSUBSCRIBE",
218 | "UNSUBACK",
219 | "PINGREQ",
220 | "PINGRESP",
221 | "DISCONNEC"}
222 | if mt > uint8(len(strArray)) {
223 | return "Undefined MessageType"
224 | }
225 | return strArray[mt]
226 | }
227 |
228 | // Types
229 | const(
230 | CONNECT = uint8(iota + 1)
231 | CONNACK
232 | PUBLISH
233 | PUBACK
234 | PUBREC
235 | PUBREL
236 | PUBCOMP
237 | SUBSCRIBE
238 | SUBACK
239 | UNSUBSCRIBE
240 | UNSUBACK
241 | PINGREQ
242 | PINGRESP
243 | DISCONNECT
244 | )
245 |
246 | const(
247 | ACCEPTED = uint8(iota)
248 | UNACCEPTABLE_PROTOCOL_VERSION
249 | IDENTIFIER_REJECTED
250 | SERVER_UNAVAILABLE
251 | BAD_USERNAME_OR_PASSWORD
252 | NOT_AUTHORIZED
253 | )
254 |
255 | type Mqtt struct{
256 | FixedHeader *FixedHeader
257 | ProtocolName, TopicName, ClientId, WillTopic, WillMessage, Username, Password string
258 | ProtocolVersion uint8
259 | ConnectFlags *ConnectFlags
260 | KeepAliveTimer, MessageId uint16
261 | Data []byte
262 | Topics []string
263 | Topics_qos []uint8
264 | ReturnCode uint8
265 | }
266 |
267 | type ConnectFlags struct{
268 | UsernameFlag, PasswordFlag, WillRetain, WillFlag, CleanSession bool
269 | WillQos uint8
270 | }
271 |
272 | type ConnectInfo struct {
273 | Protocol string // Must be 'MQIsdp' for now
274 | Version uint8
275 | UsernameFlag bool
276 | PasswordFlag bool
277 | WillRetain bool
278 | WillQos uint8
279 | WillFlag bool
280 | CleanSession bool
281 | Keepalive uint16
282 | }
283 |
284 | type FixedHeader struct {
285 | MessageType uint8
286 | DupFlag bool
287 | Retain bool
288 | QosLevel uint8
289 | Length uint32
290 | }
291 |
292 |
293 |
294 |
--------------------------------------------------------------------------------
/mqtt/parser.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "log")
7 |
8 | func getUint8(b []byte, p *int)uint8{
9 | *p += 1
10 | return uint8(b[*p-1])
11 | }
12 |
13 | func getUint16(b []byte, p *int)uint16{
14 | *p += 2
15 | return uint16(b[*p-2]) << 8 + uint16(b[*p-1])
16 | }
17 |
18 | func getString(b []byte, p *int)string{
19 | length := int(getUint16(b, p))
20 | *p += length
21 | return string(b[*p-length:*p])
22 | }
23 |
24 | func getHeader(b []byte, p *int)*FixedHeader{
25 | byte1 := b[*p]
26 | *p += 1
27 | header := new(FixedHeader)
28 | header.MessageType = uint8(byte1 & 0xF0 >> 4)
29 | header.DupFlag = byte1 & 0x08 > 0
30 | header.QosLevel = uint8(byte1 & 0x06 >> 1)
31 | header.Retain = byte1 & 0x01 > 0
32 | header.Length = decodeLength(b, p)
33 | return header
34 | }
35 |
36 | func getConnectFlags(b []byte, p *int)*ConnectFlags{
37 | bit := b[*p]
38 | *p += 1
39 | flags := new(ConnectFlags)
40 | flags.UsernameFlag = bit & 0x80 > 0
41 | flags.PasswordFlag = bit & 0x40 > 0
42 | flags.WillRetain = bit & 0x20 > 0
43 | flags.WillQos = uint8(bit & 0x18 >> 3)
44 | flags.WillFlag = bit & 0x04 > 0
45 | flags.CleanSession = bit & 0x02 > 0
46 | return flags
47 | }
48 |
49 | func DecodeAfterFixedHeader(fixed_header *FixedHeader, buf []byte)(*Mqtt, error) {
50 | mqtt := new(Mqtt)
51 | idx := 0
52 | mqtt.FixedHeader = fixed_header
53 |
54 | if mqtt.FixedHeader.Length != uint32(len(buf)) {
55 | log.Panicf("fixed header length(%d) not equal to acutall buf length(%d)\n", mqtt.FixedHeader.Length, len(buf))
56 | }
57 |
58 | msgType := mqtt.FixedHeader.MessageType
59 | if msgType <= 0 || msgType >= 15 {
60 | log.Panicf("MessageType(%d) in fixed header not supported\n", msgType)
61 | }
62 |
63 | switch msgType {
64 | case CONNECT:{
65 | mqtt.ProtocolName = getString(buf, &idx)
66 | mqtt.ProtocolVersion = getUint8(buf, &idx)
67 | mqtt.ConnectFlags = getConnectFlags(buf, &idx)
68 | mqtt.KeepAliveTimer = getUint16(buf, &idx)
69 | mqtt.ClientId = getString(buf, &idx)
70 | if mqtt.ConnectFlags.WillFlag{
71 | mqtt.WillTopic = getString(buf, &idx)
72 | mqtt.WillMessage = getString(buf, &idx)
73 | }
74 | if mqtt.ConnectFlags.UsernameFlag && idx < len(buf){
75 | mqtt.Username = getString(buf, &idx)
76 | }
77 | if mqtt.ConnectFlags.PasswordFlag && idx < len(buf){
78 | mqtt.Password = getString(buf, &idx)
79 | }
80 | }
81 | case CONNACK:{
82 | idx += 1
83 | mqtt.ReturnCode = uint8(getUint8(buf, &idx))
84 | if code := uint8(mqtt.ReturnCode);code > 5{
85 | return nil, errors.New("ReturnCode is invalid!")
86 | }
87 | }
88 | case PUBLISH:{
89 | mqtt.TopicName = getString(buf, &idx)
90 | if qos := mqtt.FixedHeader.QosLevel;qos == 1 || qos == 2{
91 | mqtt.MessageId = getUint16(buf, &idx)
92 | }
93 | mqtt.Data = buf[idx:len(buf)]
94 | idx = len(buf)
95 | }
96 | case PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK:{
97 | mqtt.MessageId = getUint16(buf, &idx)
98 | }
99 | case SUBSCRIBE:{
100 | if qos := mqtt.FixedHeader.QosLevel;qos == 1 || qos == 2{
101 | mqtt.MessageId = getUint16(buf, &idx)
102 | }
103 | topics := make([]string, 0)
104 | topics_qos := make([]uint8, 0)
105 | for ; idx < len(buf);{
106 | topics = append(topics, getString(buf, &idx))
107 | topics_qos = append(topics_qos, getUint8(buf, &idx))
108 | }
109 | mqtt.Topics = topics
110 | mqtt.Topics_qos = topics_qos
111 | }
112 | case SUBACK:{
113 | mqtt.MessageId = getUint16(buf, &idx)
114 | topics_qos := make([]uint8, 0)
115 | for ; idx < len(buf);{
116 | topics_qos = append(topics_qos, getUint8(buf, &idx))
117 | }
118 | mqtt.Topics_qos = topics_qos
119 | }
120 | case UNSUBSCRIBE:{
121 | if qos := mqtt.FixedHeader.QosLevel;qos == 1 || qos == 2{
122 | mqtt.MessageId = getUint16(buf, &idx)
123 | }
124 | topics := make([]string, 0)
125 | for ; idx < len(buf);{
126 | topics = append(topics, getString(buf, &idx))
127 | }
128 | mqtt.Topics = topics
129 | }
130 | case PINGREQ: {
131 | // Nothing to do there
132 | // Here is one of the spots go-mode.el will
133 | // break in 'go-toto-beginning-of-line'
134 |
135 | }
136 | case PINGRESP: {
137 | // Nothing to do there
138 |
139 | }
140 | case DISCONNECT: {
141 | // Nothing to do there
142 | }
143 | }
144 |
145 | return mqtt, nil
146 | }
147 |
148 | func Decode(buf []byte)(*Mqtt, error){
149 | inx := 0
150 | fixed_header := getHeader(buf, &inx)
151 | return DecodeAfterFixedHeader(fixed_header, buf[inx:])
152 | }
153 |
154 | func setUint8(val uint8, buf *bytes.Buffer){
155 | buf.WriteByte(byte(val))
156 | }
157 |
158 | func setUint16(val uint16, buf *bytes.Buffer){
159 | buf.WriteByte(byte(val & 0xff00 >> 8))
160 | buf.WriteByte(byte(val & 0x00ff))
161 | }
162 |
163 | func setString(val string, buf *bytes.Buffer){
164 | length := uint16(len(val))
165 | setUint16(length, buf)
166 | buf.WriteString(val)
167 | }
168 |
169 | func setHeader(header *FixedHeader, buf *bytes.Buffer){
170 | val := byte(uint8(header.MessageType)) << 4
171 | val |= (boolToByte(header.DupFlag) << 3)
172 | val |= byte(header.QosLevel) << 1
173 | val |= boolToByte(header.Retain)
174 | buf.WriteByte(val)
175 | }
176 |
177 | func setConnectFlags(flags *ConnectFlags, buf *bytes.Buffer){
178 | val := boolToByte(flags.UsernameFlag) << 7
179 | val |= boolToByte(flags.PasswordFlag) << 6
180 | val |= boolToByte(flags.WillRetain) << 5
181 | val |= byte(flags.WillQos) << 3
182 | val |= boolToByte(flags.WillFlag) << 2
183 | val |= boolToByte(flags.CleanSession) << 1
184 | buf.WriteByte(val)
185 | }
186 |
187 | func boolToByte(val bool)byte{
188 | if val{
189 | return byte(1)
190 | }
191 | return byte(0)
192 | }
193 |
194 | func CreateMqtt(msg_type uint8) *Mqtt {
195 | mqtt := new(Mqtt)
196 |
197 | fixed_header := new(FixedHeader)
198 | fixed_header.MessageType = msg_type
199 | mqtt.FixedHeader = fixed_header
200 |
201 | connect_flags := new(ConnectFlags)
202 | mqtt.ConnectFlags = connect_flags
203 |
204 | switch msg_type {
205 |
206 | case CONNACK: {}
207 | case SUBACK: {}
208 | case PUBACK: {}
209 | case UNSUBACK: {}
210 | case PINGRESP: {}
211 | case PUBLISH: {}
212 |
213 | default: {
214 | log.Panicf("Can't create Mqtt of type:%d", msg_type)
215 | return nil
216 | }
217 | }
218 |
219 | return mqtt
220 | }
221 |
222 | func Encode(mqtt *Mqtt)([]byte, error){
223 | err := valid(mqtt)
224 | if err != nil{
225 | return nil, err
226 | }
227 | var headerbuf, buf bytes.Buffer
228 | setHeader(mqtt.FixedHeader, &headerbuf)
229 | switch mqtt.FixedHeader.MessageType{
230 | case CONNECT:{
231 | setString(mqtt.ProtocolName, &buf)
232 | setUint8(mqtt.ProtocolVersion, &buf)
233 | setConnectFlags(mqtt.ConnectFlags, &buf)
234 | setUint16(mqtt.KeepAliveTimer, &buf)
235 | setString(mqtt.ClientId, &buf)
236 | if mqtt.ConnectFlags.WillFlag{
237 | setString(mqtt.WillTopic, &buf)
238 | setString(mqtt.WillMessage, &buf)
239 | }
240 | if mqtt.ConnectFlags.UsernameFlag && len(mqtt.Username) > 0{
241 | setString(mqtt.Username, &buf)
242 | }
243 | if mqtt.ConnectFlags.PasswordFlag && len(mqtt.Password) > 0{
244 | setString(mqtt.Password, &buf)
245 | }
246 | }
247 | case CONNACK:{
248 | buf.WriteByte(byte(0))
249 | setUint8(uint8(mqtt.ReturnCode), &buf)
250 | }
251 | case PUBLISH:{
252 | setString(mqtt.TopicName, &buf)
253 | if qos := mqtt.FixedHeader.QosLevel;qos == 1 || qos == 2{
254 | setUint16(mqtt.MessageId, &buf)
255 | }
256 | buf.Write(mqtt.Data)
257 | }
258 | case PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK:{
259 | setUint16(mqtt.MessageId, &buf)
260 | }
261 | case SUBSCRIBE:{
262 | if qos := mqtt.FixedHeader.QosLevel;qos == 1 || qos == 2{
263 | setUint16(mqtt.MessageId, &buf)
264 | }
265 | for i := 0;i < len(mqtt.Topics);i += 1{
266 | setString(mqtt.Topics[i], &buf)
267 | setUint8(mqtt.Topics_qos[i], &buf)
268 | }
269 | }
270 | case SUBACK:{
271 | setUint16(mqtt.MessageId, &buf)
272 | for i := 0;i < len(mqtt.Topics_qos);i += 1{
273 | setUint8(mqtt.Topics_qos[i], &buf)
274 | }
275 | }
276 | case UNSUBSCRIBE:{
277 | if qos := mqtt.FixedHeader.QosLevel;qos == 1 || qos == 2{
278 | setUint16(mqtt.MessageId, &buf)
279 | }
280 | for i := 0;i < len(mqtt.Topics); i += 1{
281 | setString(mqtt.Topics[i], &buf)
282 | }
283 | }
284 | }
285 | if buf.Len() > 268435455{
286 | return nil, errors.New("Message is too long!")
287 | }
288 | encodeLength(uint32(buf.Len()), &headerbuf)
289 | headerbuf.Write(buf.Bytes())
290 | return headerbuf.Bytes(), nil
291 | }
292 |
293 | func valid(mqtt *Mqtt)error{
294 | if msgType := uint8(mqtt.FixedHeader.MessageType);msgType < 1 || msgType > 14{
295 | return errors.New("MessageType is invalid!")
296 | }
297 | if mqtt.FixedHeader.QosLevel > 3 {
298 | return errors.New("Qos Level is invalid!")
299 | }
300 | if mqtt.ConnectFlags != nil && mqtt.ConnectFlags.WillQos > 3{
301 | return errors.New("Will Qos Level is invalid!")
302 | }
303 | return nil
304 | }
305 |
306 | func decodeLength(b []byte, p *int)uint32{
307 | m := uint32(1)
308 | v := uint32(b[*p] & 0x7f)
309 | *p += 1
310 | for ; b[*p-1] & 0x80 > 0 ;{
311 | m *= 128
312 | v += uint32(b[*p] & 0x7f) * m
313 | *p += 1
314 | }
315 | return v
316 | }
317 |
318 | func encodeLength(length uint32, buf *bytes.Buffer){
319 | if length == 0{
320 | buf.WriteByte(byte(0))
321 | return
322 | }
323 | var lbuf bytes.Buffer
324 | for ; length > 0;{
325 | digit := length % 128
326 | length = length / 128
327 | if length > 0{
328 | digit = digit | 0x80
329 | }
330 | lbuf.WriteByte(byte(digit))
331 | }
332 | blen := lbuf.Bytes()
333 | for i := 1;i <= len(blen);i += 1{
334 | buf.WriteByte(blen[len(blen)-i])
335 | }
336 | }
--------------------------------------------------------------------------------
/testing/test.py:
--------------------------------------------------------------------------------
1 | import sys, os, time
2 |
3 | sys.path.append("./pylib")
4 |
5 | import argparse
6 | import itertools
7 | import logging
8 | import eventlet
9 | import socket
10 | import getpass
11 | import numpy as np
12 |
13 | eventlet.monkey_patch()
14 |
15 | from datetime import datetime
16 | from gossip_client import GossipClient
17 | from pprint import pprint
18 |
19 | logging.basicConfig(format="%(asctime)s - %(levelname)s - %(module)s-%(funcName)s(%(lineno)d): %(message)s",
20 | level = logging.DEBUG, stream = sys.stderr)
21 | _logger = logging.getLogger('root')
22 |
23 | g_publish_finish_time = None
24 | g_subscribe_finish_time = None
25 | g_subscribe_done = set()
26 | g_active_client_num = 0
27 | g_lost_connection_count
28 |
29 | class TestWorker(object):
30 | def __init__(self, worker_id, thread_num, hostname, port):
31 | self.client_id = "%s_test_%d" % (socket.gethostname(), worker_id)
32 | self.worker_id = worker_id
33 | self.thread_num = thread_num
34 | self.hostname = hostname
35 | self.port = port
36 | self.client = GossipClient(client_id = self.client_id, clean_session = True)
37 | self.client.on_connect = self.on_connect
38 | self.client.on_disconnect = self.on_disconnect
39 | self.client.on_publish = self.on_publish
40 | self.client.on_subscribe = self.on_subscribe
41 | self.client.on_message = self.on_message
42 | self.client.on_unsubscribe = self.on_unsubscribe
43 | self.subscribe_done = False
44 |
45 | def on_connect(self, mosq, obj, rc):
46 | if rc == 0:
47 | _logger.debug('worker %d connected' % self.worker_id)
48 | else:
49 | _logger.error('worker %d on_connect, rc=%d' % (self.worker_id, rc))
50 |
51 | def on_disconnect(self, mosq, obj, rc):
52 | global g_lost_connection_count
53 |
54 | if rc == 0:
55 | _logger.debug('worker %d disconnected normally' % self.worker_id)
56 | else:
57 | g_lost_connection_count += 1
58 | _logger.error('worker %d lost connection to server' % self.worker_id)
59 |
60 | if not self.subscribe_done:
61 | _logger.error('worker %d not done subscription and lost connection, will retry',
62 | self.worker_id)
63 | self._do_stuff(self.message_num)
64 |
65 | def on_publish(self, mosq, obj, mid):
66 | return
67 | _logger.debug('on worker %d, published mid=%d' % (self.worker_id, mid))
68 |
69 | def on_subscribe(self, mosq, obj, mid, qos_list):
70 | g_subscribe_done.add(self.worker_id)
71 | self.subscribe_done = True
72 | #_logger.debug('on worker %d, subscribed mid=%d' % (self.worker_id, mid))
73 |
74 | def on_unsubscribe(self, mosq, obj, mid):
75 | return
76 | _logger.debug('on woker %d, unsubscribed mid=%d' % (self.worker_id, mid))
77 |
78 | def on_message(self, mosq, obj, msg):
79 | now = time.time()
80 | sent = float(msg.payload)
81 | elapsed = now - sent
82 | self.status.append(elapsed)
83 | return
84 | _logger.debug('worker %d got message, topic=(%s), payload=(%s)' %
85 | (self.worker_id, msg.topic, msg.payload))
86 |
87 | def _do_stuff(self, message_num):
88 | ret = self.client.connect(self.hostname, self.port, keepalive=6000)
89 | if ret != 0:
90 | _logger.error("worker %d connect() returned %d" % (self.worker_id, ret))
91 | return self._do_stuff(message_num)
92 |
93 | topic = "%s-%d" % (socket.gethostname(), self.worker_id)
94 | self.subscribe_done = False
95 | self.client.subscribe(topic, qos=1)
96 |
97 | count = 0
98 | while len(self.status) < message_num:
99 | count += 1
100 | if count % 100 == 0:
101 | _logger.debug("worker %d get %d messages" % (self.worker_id, len(self.status)))
102 | ret = self.client.loop()
103 | if ret != 0:
104 | _logger.error("worker %d loop() failed, returned %d" % (self.worker_id, ret))
105 | return self._do_stuff(message_num)
106 |
107 | now = time.time()
108 | if g_publish_finish_time is not None:
109 | late = (now - g_publish_finish_time)
110 | if late > 60.0:
111 | _logger.debug("worker %d: too long waiting for message, quit with %d received" % (self.worker_id, len(self.status)))
112 | return self._do_stuff(message_num)
113 | else:
114 | _logger.debug("worker %d, %.3f seconds late" % (self.worker_id, late))
115 |
116 | g_subscribe_done.add(self.worker_id)
117 | self.subscribe_done = True
118 | self.client.disconnect()
119 |
120 |
121 | def start_listening(self, message_num):
122 | self.status = self._build_status()
123 | self.message_num = message_num
124 | self._do_stuff(message_num)
125 |
126 | def _build_status(self):
127 | return list()
128 |
129 | def collect(self):
130 | return self.status
131 |
132 | def create_subscriber(worker_id, thread_num, message_num, hostname, port):
133 | worker = TestWorker(worker_id, thread_num, hostname, port)
134 | _logger.debug("worker(%d) is started" % worker_id)
135 | worker.start_listening(message_num)
136 | _logger.debug("worker(%d) is done" % worker_id)
137 | return worker.collect()
138 |
139 | def create_publisher(message_num, thread_num, hostname, port):
140 | global g_publish_finish_time
141 |
142 | published = list()
143 |
144 | def _on_publish(mosq, obj, mid):
145 | published.append(mid)
146 | #_logger.debug('got publish ack %d' % mid)
147 |
148 | def _on_disconnect(mosq, obj, rc):
149 | global g_lost_connection_count
150 | if rc != 0:
151 | _logger.error('publisher lost connection to server, will reconnect')
152 | g_lost_connection_count += 1
153 | create_publisher(message_num, thread_num, hostname, port)
154 |
155 |
156 | pub_id = "publisher-%s" % (socket.gethostname())
157 | _logger.debug("publisher id is (%s)" % pub_id)
158 | client = GossipClient(client_id=pub_id)
159 |
160 | client.on_publish = _on_publish
161 | client.on_disconnect = _on_disconnect
162 | client.connect(hostname, port)
163 |
164 | for i in xrange(message_num):
165 | for j in xrange(thread_num):
166 | now = time.time()
167 | client.publish(topic="%s-%d" % (socket.gethostname(), j+1), payload=str(now), qos=1)
168 | count = i * thread_num + j
169 | if count % 100 == 0:
170 | _logger.debug("published %d messages" % count)
171 | ret = client.loop(10.0) # Mosquitto won't work without this sleep
172 | if ret != 0:
173 | _logger.fatal("publish loop returned %d, will retry" % ret)
174 | return create_publisher(message_num, thread_num, hostname, port)
175 |
176 | while len(published) < message_num * thread_num:
177 | _logger.debug("published %d" % len(published))
178 | ret = client.loop()
179 | if ret != 0:
180 | _logger.fatal("publisher's connection is lost, rc=%d" % rc)
181 | sys.exit(0)
182 |
183 | g_publish_finish_time = time.time()
184 | _logger.info("all messages published")
185 |
186 |
187 | def start_testing(hostname, port, thread_num, sleep):
188 | global g_active_client_num
189 | g_active_client_num = thread_num
190 |
191 | _logger.info("Testing, %s:%d, thread:%d, sleep:%d" % (hostname, port, thread_num, sleep))
192 | message_num = 10
193 | workers = list()
194 |
195 | for i in xrange(thread_num):
196 | worker = eventlet.spawn(create_subscriber, i+1, thread_num, message_num, hostname, port)
197 | workers.append(worker)
198 | time.sleep(0.01)
199 |
200 | while len(g_subscribe_done) < g_active_client_num:
201 | _logger.info("waiting %d/%d clients to finish subscribe" %
202 | (g_active_client_num - len(g_subscribe_done),
203 | g_active_client_num))
204 | time.sleep(1)
205 |
206 | create_publisher(message_num, thread_num, hostname, port)
207 |
208 | statuses = list()
209 | for worker in workers:
210 | statuses.append(worker.wait())
211 |
212 | return statuses
213 |
214 | def main():
215 | parser = argparse.ArgumentParser()
216 | parser.add_argument("-t", "--thread", dest="thread_num",
217 | default=2, help="Number of threads", type=int)
218 | parser.add_argument("-p", "--port", dest="port",
219 | default=1883, help="Port of the broker", type=int)
220 | parser.add_argument("--host", dest="host",
221 | default="localhost", help="Hostname of the broker")
222 | parser.add_argument("-s", "--sleep", dest="sleep", type=int,
223 | default=0, help="Seconds the clients will sleep between requests, default to 0(no sleep)")
224 | parser.add_argument("-o", "--output", dest="outfile",
225 | default="test.out", help="Output file default to test.out")
226 |
227 |
228 | args = parser.parse_args()
229 |
230 | thread_num = args.thread_num
231 | sleep = args.sleep
232 | port = args.port
233 | hostname = args.host
234 | outfile = args.outfile
235 |
236 | data = start_testing(hostname, port, thread_num, sleep)
237 |
238 |
239 |
240 | # with open(outfile, "w") as out:
241 | # for line in data:
242 | # cur_line = " ".join(["%.4f" % i for i in line])
243 | # out.write(cur_line + "\n")
244 |
245 | data = itertools.chain(*data)
246 | data = [i for i in data]
247 | mean = np.mean(data) * 1000
248 | max = np.max(data) * 1000
249 | min = np.min(data) * 1000
250 | std = np.std(data) * 1000
251 |
252 | print "avg:%.2f stddev:%.2f min:%.2f max%.2f lost-conn:%d" % (mean, std, max, min, g_lost_connection_count)
253 |
254 | _logger.info("testing finsished")
255 |
256 | if __name__ == "__main__":
257 | main()
258 |
--------------------------------------------------------------------------------
/mqtt/cmd_func.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import (
4 | "net"
5 | log "github.com/cihub/seelog"
6 | "time"
7 | "sync"
8 | "fmt"
9 | "runtime/debug"
10 | )
11 |
12 | const (
13 | SEND_WILL = uint8(iota)
14 | DONT_SEND_WILL
15 | )
16 |
17 | // Handle CONNECT
18 | func HandleConnect(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
19 | //mqtt.Show()
20 | client_id := mqtt.ClientId
21 |
22 | log.Debugf("Hanling CONNECT, client id:(%s)", client_id)
23 |
24 | if len(client_id) > 23 {
25 | log.Debugf("client id(%s) is longer than 23, will send IDENTIFIER_REJECTED", client_id)
26 | SendConnack(IDENTIFIER_REJECTED, conn, nil)
27 | return
28 | }
29 |
30 | if mqtt.ProtocolName != "MQIsdp" || mqtt.ProtocolVersion != 3 {
31 | log.Debugf("ProtocolName(%s) and/or version(%d) not supported, will send UNACCEPTABLE_PROTOCOL_VERSION",
32 | mqtt.ProtocolName, mqtt.ProtocolVersion)
33 | SendConnack(UNACCEPTABLE_PROTOCOL_VERSION, conn, nil)
34 | return
35 | }
36 |
37 | G_clients_lock.Lock()
38 | client_rep, existed := G_clients[client_id]
39 | if existed {
40 | log.Debugf("%s existed, will close old connection", client_id)
41 | ForceDisconnect(client_rep, nil, DONT_SEND_WILL)
42 |
43 | } else {
44 | log.Debugf("Appears to be new client, will create ClientRep")
45 | }
46 |
47 | client_rep = CreateClientRep(client_id, conn, mqtt)
48 |
49 | G_clients[client_id] = client_rep
50 | G_clients_lock.Unlock()
51 |
52 | *client = client_rep
53 | go CheckTimeout(client_rep)
54 | log.Debugf("Timeout checker go-routine started")
55 |
56 | if !client_rep.Mqtt.ConnectFlags.CleanSession {
57 | // deliver flying messages
58 | DeliverOnConnection(client_id)
59 | // restore subscriptions to client_rep
60 | subs := make(map[string]uint8)
61 | key := fmt.Sprintf("gossipd.client-subs.%s", client_id)
62 | G_redis_client.Fetch(key, &subs)
63 | client_rep.Subscriptions = subs
64 |
65 | } else {
66 | // Remove subscriptions and flying message
67 | RemoveAllSubscriptionsOnConnect(client_id)
68 | empty := make(map[uint16]FlyingMessage)
69 | G_redis_client.SetFlyingMessagesForClient(client_id, &empty)
70 | }
71 |
72 | SendConnack(ACCEPTED, conn, client_rep.WriteLock)
73 | log.Debugf("New client is all set and CONNACK is sent")
74 | }
75 |
76 | func SendConnack(rc uint8, conn *net.Conn, lock *sync.Mutex) {
77 | resp := CreateMqtt(CONNACK)
78 | resp.ReturnCode = rc
79 |
80 | bytes, _ := Encode(resp)
81 | MqttSendToClient(bytes, conn, lock)
82 | }
83 |
84 | /* Handle PUBLISH*/
85 | // FIXME: support qos = 2
86 | func HandlePublish(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
87 | if *client == nil {
88 | panic("client_resp is nil, that means we don't have ClientRep for this client sending PUBLISH")
89 | return
90 | }
91 |
92 | client_id := (*client).Mqtt.ClientId
93 | client_rep := *client
94 | client_rep.UpdateLastTime()
95 | topic := mqtt.TopicName
96 | payload := string(mqtt.Data)
97 | qos := mqtt.FixedHeader.QosLevel
98 | retain := mqtt.FixedHeader.Retain
99 | message_id := mqtt.MessageId
100 | timestamp := time.Now().Unix()
101 | log.Debugf("Handling PUBLISH, client_id: %s, topic:(%s), payload:(%s), qos=%d, retain=%t, message_id=%d",
102 | client_id, topic, payload, qos, retain, message_id)
103 |
104 | // Create new MQTT message
105 | mqtt_msg := CreateMqttMessage(topic, payload, client_id, qos, message_id, timestamp, retain)
106 | msg_internal_id := mqtt_msg.InternalId
107 | log.Debugf("Created new MQTT message, internal id:(%s)", msg_internal_id)
108 |
109 | PublishMessage(mqtt_msg)
110 |
111 | // Send PUBACK if QOS is 1
112 | if qos == 1 {
113 | SendPuback(message_id, conn, client_rep.WriteLock)
114 | log.Debugf("PUBACK sent to client(%s)", client_id)
115 | }
116 | }
117 |
118 | func SendPuback(msg_id uint16, conn *net.Conn, lock *sync.Mutex) {
119 | resp := CreateMqtt(PUBACK)
120 | resp.MessageId = msg_id
121 | bytes, _ := Encode(resp)
122 | MqttSendToClient(bytes, conn, lock)
123 |
124 | }
125 |
126 | /* Handle SUBSCRIBE */
127 |
128 | func HandleSubscribe(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
129 | if *client == nil {
130 | panic("client_resp is nil, that means we don't have ClientRep for this client sending SUBSCRIBE")
131 | return
132 | }
133 |
134 | client_id := (*client).Mqtt.ClientId
135 | log.Debugf("Handling SUBSCRIBE, client_id: %s", client_id)
136 | client_rep := *client
137 | client_rep.UpdateLastTime()
138 |
139 | defer func() {
140 | G_subs_lock.Unlock()
141 | SendSuback(mqtt.MessageId, mqtt.Topics_qos, conn, client_rep.WriteLock)
142 | }()
143 |
144 | G_subs_lock.Lock()
145 | for i := 0; i < len(mqtt.Topics); i++ {
146 | topic := mqtt.Topics[i]
147 | qos := mqtt.Topics_qos[i]
148 | log.Debugf("will subscribe client(%s) to topic(%s) with qos=%d",
149 | client_id, topic, qos)
150 |
151 | subs := G_subs[topic]
152 | if subs == nil {
153 | log.Debugf("current subscription is the first client to topic:(%s)", topic)
154 | subs = make(map[string]uint8)
155 | G_subs[topic] = subs
156 | }
157 |
158 | // FIXME: this may override existing subscription with higher QOS
159 | subs[client_id] = qos
160 | client_rep.Subscriptions[topic] = qos
161 |
162 | if !client_rep.Mqtt.ConnectFlags.CleanSession {
163 | // Store subscriptions to redis
164 | key := fmt.Sprintf("gossipd.client-subs.%s", client_id)
165 | G_redis_client.Store(key, client_rep.Subscriptions)
166 | }
167 |
168 | log.Debugf("finding retained message for (%s)", topic)
169 | retained_msg := G_redis_client.GetRetainMessage(topic)
170 | if retained_msg != nil {
171 | go Deliver(client_id, qos, retained_msg)
172 | log.Debugf("delivered retained message for (%s)", topic)
173 | }
174 | }
175 | log.Debugf("Subscriptions are all processed, will send SUBACK")
176 | showSubscriptions()
177 | }
178 |
179 | func SendSuback(msg_id uint16, qos_list []uint8, conn *net.Conn, lock *sync.Mutex) {
180 | resp := CreateMqtt(SUBACK)
181 | resp.MessageId = msg_id
182 | resp.Topics_qos = qos_list
183 |
184 | bytes, _ := Encode(resp)
185 | MqttSendToClient(bytes, conn, lock)
186 | }
187 |
188 | /* Handle UNSUBSCRIBE */
189 |
190 | func HandleUnsubscribe(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
191 | if *client == nil {
192 | panic("client_resp is nil, that means we don't have ClientRep for this client sending UNSUBSCRIBE")
193 | return
194 | }
195 |
196 | client_id := (*client).Mqtt.ClientId
197 | log.Debugf("Handling UNSUBSCRIBE, client_id: %s", client_id)
198 | client_rep := *client
199 | client_rep.UpdateLastTime()
200 |
201 | defer func() {
202 | G_subs_lock.Unlock()
203 | SendUnsuback(mqtt.MessageId, conn, client_rep.WriteLock)
204 | }()
205 |
206 | G_subs_lock.Lock()
207 | for i := 0; i < len(mqtt.Topics); i++ {
208 | topic := mqtt.Topics[i]
209 |
210 | log.Debugf("unsubscribing client(%s) from topic(%s)",
211 | client_id, topic)
212 |
213 | delete(client_rep.Subscriptions, topic)
214 |
215 | subs := G_subs[topic]
216 | if subs == nil {
217 | log.Debugf("topic(%s) has no subscription, no need to unsubscribe", topic)
218 | } else {
219 | delete(subs, client_id)
220 | if len(subs) == 0 {
221 | delete(G_subs, topic)
222 | log.Debugf("last subscription of topic(%s) is removed, so this topic is removed as well", topic)
223 | }
224 | }
225 | }
226 | log.Debugf("unsubscriptions are all processed, will send UNSUBACK")
227 |
228 | showSubscriptions()
229 | }
230 |
231 | func SendUnsuback(msg_id uint16, conn *net.Conn, lock *sync.Mutex) {
232 | resp := CreateMqtt(UNSUBACK)
233 | resp.MessageId = msg_id
234 | bytes, _ := Encode(resp)
235 | MqttSendToClient(bytes, conn, lock)
236 | }
237 |
238 | /* Handle PINGREQ */
239 |
240 | func HandlePingreq(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
241 | if *client == nil {
242 | panic("client_resp is nil, that means we don't have ClientRep for this client sending PINGREQ")
243 | return
244 | }
245 |
246 | client_id := (*client).Mqtt.ClientId
247 | log.Debugf("Handling PINGREQ, client_id: %s", client_id)
248 | client_rep := *client
249 | client_rep.UpdateLastTime()
250 |
251 | SendPingresp(conn, client_rep.WriteLock)
252 | log.Debugf("Sent PINGRESP, client_id: %s", client_id)
253 | }
254 |
255 | func SendPingresp(conn *net.Conn, lock *sync.Mutex) {
256 | resp := CreateMqtt(PINGRESP)
257 | bytes, _ := Encode(resp)
258 | MqttSendToClient(bytes, conn, lock)
259 | }
260 |
261 | /* Handle DISCONNECT */
262 |
263 | func HandleDisconnect(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
264 | if *client == nil {
265 | panic("client_resp is nil, that means we don't have ClientRep for this client sending DISCONNECT")
266 | return
267 | }
268 |
269 | ForceDisconnect(*client, G_clients_lock, DONT_SEND_WILL)
270 | }
271 |
272 | /* Handle PUBACK */
273 | func HandlePuback(mqtt *Mqtt, conn *net.Conn, client **ClientRep) {
274 | if *client == nil {
275 | panic("client_resp is nil, that means we don't have ClientRep for this client sending DISCONNECT")
276 | return
277 | }
278 |
279 | client_id := (*client).Mqtt.ClientId
280 | message_id := mqtt.MessageId
281 | log.Debugf("Handling PUBACK, client:(%s), message_id:(%d)", client_id, message_id)
282 |
283 | messages := G_redis_client.GetFlyingMessagesForClient(client_id)
284 |
285 | flying_msg, found := (*messages)[message_id]
286 |
287 | if !found || flying_msg.Status != PENDING_ACK {
288 | log.Debugf("message(id=%d, client=%s) is not PENDING_ACK, will ignore this PUBACK",
289 | message_id, client_id)
290 | } else {
291 | delete(*messages, message_id)
292 | G_redis_client.SetFlyingMessagesForClient(client_id, messages)
293 | log.Debugf("acked flying message(id=%d), client:(%s)", message_id, client_id)
294 | }
295 | }
296 |
297 |
298 | /* Helper functions */
299 |
300 | // This is the main place to change if we need to use channel rather than lock
301 | func MqttSendToClient(bytes []byte, conn *net.Conn, lock *sync.Mutex) {
302 | if lock != nil {
303 | lock.Lock()
304 | defer func() {
305 | lock.Unlock()
306 | }()
307 | }
308 | (*conn).Write(bytes)
309 | }
310 |
311 | /* Checking timeout */
312 | func CheckTimeout(client *ClientRep) {
313 | defer func() {
314 | if r := recover(); r != nil {
315 | log.Debugf("got panic, will print stack")
316 | debug.PrintStack()
317 | panic(r)
318 | }
319 | }()
320 |
321 | interval := client.Mqtt.KeepAliveTimer
322 | client_id := client.ClientId
323 | ticker := time.NewTicker(time.Duration(interval) * time.Second)
324 |
325 | for {
326 | select {
327 | case <- ticker.C:
328 | now := time.Now().Unix()
329 | lastTimestamp := client.LastTime
330 | deadline := int64(float64(lastTimestamp) + float64(interval) * 1.5)
331 |
332 | if deadline < now {
333 | ForceDisconnect(client, G_clients_lock, SEND_WILL)
334 | log.Debugf("client(%s) is timeout, kicked out",
335 | client_id)
336 | } else {
337 | log.Debugf("client(%s) will be kicked out in %d seconds",
338 | client_id,
339 | deadline - now)
340 | }
341 | case <- client.Shuttingdown:
342 | log.Debugf("client(%s) is being shutting down, stopped timeout checker", client_id)
343 | return
344 | }
345 |
346 | }
347 | }
348 |
349 | func ForceDisconnect(client *ClientRep, lock *sync.Mutex, send_will uint8) {
350 | if client.Disconnected == true {
351 | return
352 | }
353 |
354 | client.Disconnected = true
355 |
356 | client_id := client.Mqtt.ClientId
357 |
358 | log.Debugf("Disconnecting client(%s), clean-session:%t",
359 | client_id, client.Mqtt.ConnectFlags.CleanSession)
360 |
361 | if lock != nil {
362 | lock.Lock()
363 | log.Debugf("lock accuired")
364 | }
365 |
366 | delete(G_clients, client_id)
367 |
368 | if client.Mqtt.ConnectFlags.CleanSession {
369 | // remove her subscriptions
370 | log.Debugf("Removing subscriptions for (%s)", client_id)
371 | G_subs_lock.Lock()
372 | for topic, _ := range(client.Subscriptions) {
373 | delete(G_subs[topic], client_id)
374 | if len(G_subs[topic]) == 0 {
375 | delete(G_subs, topic)
376 | log.Debugf("last subscription of topic(%s) is removed, so this topic is removed as well", topic)
377 | }
378 | }
379 | showSubscriptions()
380 | G_subs_lock.Unlock()
381 | log.Debugf("Removed all subscriptions for (%s)", client_id)
382 |
383 | // remove her flying messages
384 | log.Debugf("Removing all flying messages for (%s)", client_id)
385 | G_redis_client.RemoveAllFlyingMessagesForClient(client_id)
386 | log.Debugf("Removed all flying messages for (%s)", client_id)
387 | }
388 |
389 | if lock != nil {
390 | lock.Unlock()
391 | log.Debugf("lock released")
392 | }
393 |
394 | // FIXME: Send will if requested
395 | if send_will == SEND_WILL && client.Mqtt.ConnectFlags.WillFlag {
396 | will_topic := client.Mqtt.WillTopic
397 | will_payload := client.Mqtt.WillMessage
398 | will_qos := client.Mqtt.ConnectFlags.WillQos
399 | will_retain := client.Mqtt.ConnectFlags.WillRetain
400 |
401 | mqtt_msg := CreateMqttMessage(will_topic, will_payload, client_id, will_qos,
402 | 0, // message id won't be used here
403 | time.Now().Unix(), will_retain)
404 | PublishMessage(mqtt_msg)
405 |
406 | log.Debugf("Sent will for %s, topic:(%s), payload:(%s)",
407 | client_id, will_topic, will_payload)
408 | }
409 |
410 | client.Shuttingdown <- 1
411 | log.Debugf("Sent 1 to shutdown channel")
412 |
413 | log.Debugf("Closing socket of %s", client_id)
414 | (*client.Conn).Close()
415 | }
416 |
417 | func PublishMessage(mqtt_msg *MqttMessage) {
418 | topic := mqtt_msg.Topic
419 | payload := mqtt_msg.Payload
420 | log.Debugf("Publishing job, topic(%s), payload(%s)", topic, payload)
421 | // Update global topic record
422 |
423 | if mqtt_msg.Retain {
424 | G_redis_client.SetRetainMessage(topic, mqtt_msg)
425 | log.Debugf("Set the message(%s) as the current retain content of topic:%s", payload, topic)
426 | }
427 |
428 | // Dispatch delivering jobs
429 | G_subs_lock.Lock()
430 | subs, found := G_subs[topic]
431 | if found {
432 | for dest_id, dest_qos := range(subs) {
433 | go Deliver(dest_id, dest_qos, mqtt_msg)
434 | log.Debugf("Started deliver job for %s", dest_id)
435 | }
436 | }
437 | G_subs_lock.Unlock()
438 | log.Debugf("All delivering job dispatched")
439 | }
440 |
441 | func DeliverOnConnection(client_id string) {
442 | log.Debugf("client(%s) just reconnected, delivering on the fly messages", client_id)
443 | messages := G_redis_client.GetFlyingMessagesForClient(client_id)
444 | empty := make(map[uint16]FlyingMessage)
445 | G_redis_client.SetFlyingMessagesForClient(client_id, &empty)
446 | log.Debugf("client(%s), all flying messages put in pipeline, removed records in redis", client_id)
447 |
448 | for message_id, msg := range(*messages) {
449 | internal_id := msg.MessageInternalId
450 | mqtt_msg := GetMqttMessageById(internal_id)
451 | log.Debugf("re-delivering message(id=%d, internal_id=%d) for %s",
452 | message_id, internal_id, client_id)
453 | switch msg.Status {
454 | case PENDING_PUB:
455 | go Deliver(client_id, msg.Qos, mqtt_msg)
456 | case PENDING_ACK:
457 | go Deliver(client_id, msg.Qos, mqtt_msg)
458 | default:
459 | panic(fmt.Sprintf("can't re-deliver message at status(%d)", msg.Status))
460 | }
461 | }
462 | }
463 |
464 | // Real heavy lifting jobs for delivering message
465 | func DeliverMessage(dest_client_id string, qos uint8, msg *MqttMessage) {
466 | G_clients_lock.Lock()
467 | client_rep, found := G_clients[dest_client_id]
468 | G_clients_lock.Unlock()
469 | var conn *net.Conn
470 | var lock *sync.Mutex
471 | message_id := NextOutMessageIdForClient(dest_client_id)
472 | fly_msg := CreateFlyingMessage(dest_client_id, msg.InternalId, qos, PENDING_PUB, message_id)
473 |
474 | if found {
475 | conn = client_rep.Conn
476 | lock = client_rep.WriteLock
477 | } else {
478 | G_redis_client.AddFlyingMessage(dest_client_id, fly_msg)
479 | log.Debugf("client(%s) is offline, added flying message to Redis, message id=%d",
480 | dest_client_id, message_id)
481 | return
482 | }
483 |
484 | // FIXME: Add code to deal with failure
485 | resp := CreateMqtt(PUBLISH)
486 | resp.TopicName = msg.Topic
487 | if qos > 0 {
488 | resp.MessageId = message_id
489 | }
490 | resp.FixedHeader.QosLevel = qos
491 | resp.Data = []byte(msg.Payload)
492 |
493 | bytes, _ := Encode(resp)
494 |
495 | lock.Lock()
496 | defer func() {
497 | lock.Unlock()
498 | }()
499 | // FIXME: add write deatline
500 | (*conn).Write(bytes)
501 | log.Debugf("message sent by Write()")
502 |
503 | if qos == 1 {
504 | fly_msg.Status = PENDING_ACK
505 | G_redis_client.AddFlyingMessage(dest_client_id, fly_msg)
506 | log.Debugf("message(msg_id=%d) sent to client(%s), waiting for ACK, added to redis",
507 | message_id, dest_client_id)
508 | }
509 | }
510 |
511 | func Deliver(dest_client_id string, dest_qos uint8, msg *MqttMessage) {
512 | defer func() {
513 | if r := recover(); r != nil {
514 | log.Debugf("got panic, will print stack")
515 | debug.PrintStack()
516 | panic(r)
517 | }
518 | }()
519 |
520 | log.Debugf("Delivering msg(internal_id=%d) to client(%s)", msg.InternalId, dest_client_id)
521 |
522 | // Get effective qos: the smaller of the publisher and the subscriber
523 | qos := msg.Qos
524 | if dest_qos < msg.Qos {
525 | qos = dest_qos
526 | }
527 |
528 | DeliverMessage(dest_client_id, qos, msg)
529 |
530 | if qos > 0 {
531 | // Start retry
532 | go RetryDeliver(20, dest_client_id, qos, msg)
533 | }
534 | }
535 |
536 | func RetryDeliver(sleep uint64, dest_client_id string, qos uint8, msg *MqttMessage) {
537 | defer func() {
538 | if r := recover(); r != nil {
539 | log.Debugf("got panic, will print stack")
540 | debug.PrintStack()
541 | panic(r)
542 | }
543 | }()
544 |
545 | if sleep > 3600 * 4 {
546 | log.Debugf("too long retry delay(%s), abort retry deliver", sleep)
547 | return
548 | }
549 |
550 | time.Sleep(time.Duration(sleep) * time.Second)
551 |
552 | if G_redis_client.IsFlyingMessagePendingAck(dest_client_id, msg.MessageId) {
553 | DeliverMessage(dest_client_id, qos, msg)
554 | log.Debugf("Retried delivering message %s:%d, will sleep %d seconds before next attampt",
555 | dest_client_id, msg.MessageId, sleep * 2)
556 | RetryDeliver(sleep * 2, dest_client_id, qos, msg)
557 | } else {
558 | log.Debugf("message (%s:%d) is not pending ACK, stop retry delivering",
559 | dest_client_id, msg.MessageId)
560 | }
561 | }
562 |
563 | // On connection, if clean session is set, call this method
564 | // to clear all connections. This is the senario when previous
565 | // CONNECT didn't set clean session bit but current one does
566 | func RemoveAllSubscriptionsOnConnect(client_id string) {
567 | subs := new(map[string]uint8)
568 | key := fmt.Sprintf("gossipd.client-subs.%s", client_id)
569 | G_redis_client.Fetch(key, subs)
570 |
571 | G_redis_client.Delete(key)
572 |
573 | G_subs_lock.Lock()
574 | for topic, _ := range(*subs) {
575 | delete(G_subs[topic], client_id)
576 | }
577 | G_subs_lock.Unlock()
578 |
579 | }
580 |
581 | func showSubscriptions() {
582 | // Disable for now
583 | return
584 | fmt.Printf("Global Subscriptions: %d topics\n", len(G_subs))
585 | for topic, subs := range(G_subs) {
586 | fmt.Printf("\t%s: %d subscriptions\n", topic, len(subs))
587 | for client_id, qos := range(subs) {
588 | fmt.Println("\t\t", client_id, qos)
589 | }
590 | }
591 | }
592 |
593 |
--------------------------------------------------------------------------------