├── .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 | --------------------------------------------------------------------------------