func Test_persistInbound_pubrel(t *testing.T) { ts := &TestStore{} pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pub.Qos = 2 pub.TopicName = "/pub2" pub.Payload = []byte{0xCC, 0x06} pub.MessageID = 55 publishKey := inboundKeyFromMID(pub.MessageID) ts.Put(publishKey, pub) m := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket) m.MessageID = 55 persistInbound(ts, m) // will overwrite publish if len(ts.mput) != 2 { t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 0 { t.Fatalf("persistInbound in bad state") } }
func Test_persistInbound_puback(t *testing.T) { ts := &TestStore{} pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pub.Qos = 1 pub.TopicName = "/pub1" pub.Payload = []byte{0xCC, 0x04} pub.MessageID = 53 publishKey := inboundKeyFromMID(pub.MessageID) ts.Put(publishKey, pub) m := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket) m.MessageID = 53 persistInbound(ts, m) // "deletes" packets.Publish from store if len(ts.mput) != 1 { // not actually deleted in TestStore t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 1 || ts.mdel[0] != 53 { t.Fatalf("persistInbound in bad state") } }
func Test_persistInbound_pubrec(t *testing.T) { ts := &TestStore{} pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pub.Qos = 2 pub.TopicName = "/pub2" pub.Payload = []byte{0xCC, 0x05} pub.MessageID = 54 publishKey := inboundKeyFromMID(pub.MessageID) ts.Put(publishKey, pub) m := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket) m.MessageID = 54 persistInbound(ts, m) if len(ts.mput) != 1 || ts.mput[0] != 54 { t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 0 { t.Fatalf("persistInbound in bad state") } }
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when // a message is published on the topic provided. func (c *Client) Subscribe(topic string, qos byte, callback MessageHandler) Token { token := newToken(packets.Subscribe).(*SubscribeToken) DEBUG.Println(CLI, "enter Subscribe") if !c.IsConnected() { token.err = ErrNotConnected token.flowComplete() return token } sub := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket) if err := validateTopicAndQos(topic, qos); err != nil { token.err = err return token } sub.Topics = append(sub.Topics, topic) sub.Qoss = append(sub.Qoss, qos) DEBUG.Println(sub.String()) if callback != nil { c.msgRouter.addRoute(topic, callback) } token.subs = append(token.subs, topic) c.oboundP <- &PacketAndToken{p: sub, t: token} DEBUG.Println(CLI, "exit Subscribe") return token }
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to // be executed when a message is published on one of the topics provided. func (c *Client) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token { var err error token := newToken(packets.Subscribe).(*SubscribeToken) DEBUG.Println(CLI, "enter SubscribeMultiple") if !c.IsConnected() { token.err = ErrNotConnected token.flowComplete() return token } sub := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket) if sub.Topics, sub.Qoss, err = validateSubscribeMap(filters); err != nil { token.err = err return token } if callback != nil { for topic := range filters { c.msgRouter.addRoute(topic, callback) } } token.subs = make([]string, len(sub.Topics)) copy(token.subs, sub.Topics) c.oboundP <- &PacketAndToken{p: sub, t: token} DEBUG.Println(CLI, "exit SubscribeMultiple") return token }
func keepalive(c *Client) { DEBUG.Println(PNG, "keepalive starting") c.pingOutstanding = false for { select { case <-c.stop: DEBUG.Println(PNG, "keepalive stopped") c.workers.Done() return default: last := uint(time.Since(c.lastContact.get()).Seconds()) //DEBUG.Printf("%s last contact: %d (timeout: %d)", PNG, last, uint(c.options.KeepAlive.Seconds())) if last > uint(c.options.KeepAlive.Seconds()) { if !c.pingOutstanding { DEBUG.Println(PNG, "keepalive sending ping") ping := packets.NewControlPacket(packets.Pingreq).(*packets.PingreqPacket) //We don't want to wait behind large messages being sent, the Write call //will block until it it able to send the packet. ping.Write(c.conn) c.pingOutstanding = true } else { CRITICAL.Println(PNG, "pingresp not received, disconnecting") c.workers.Done() c.internalConnLost(errors.New("pingresp not received, disconnecting")) return } } time.Sleep(1 * time.Second) } } }
func newConnectMsgFromOptions(options *ClientOptions) *packets.ConnectPacket { m := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket) m.CleanSession = options.CleanSession m.WillFlag = options.WillEnabled m.WillRetain = options.WillRetained m.ClientIdentifier = options.ClientID if options.WillEnabled { m.WillQos = options.WillQos m.WillTopic = options.WillTopic m.WillMessage = options.WillPayload } if options.Username != "" { m.UsernameFlag = true m.Username = options.Username //mustn't have password without user as well if options.Password != "" { m.PasswordFlag = true m.Password = []byte(options.Password) } } m.KeepaliveTimer = uint16(options.KeepAlive) return m }
func Test_MatchAndDispatch(t *testing.T) { calledback := make(chan bool) cb := func(c *Client, m Message) { calledback <- true } pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pub.Qos = 2 pub.TopicName = "a" pub.Payload = []byte("foo") msgs := make(chan *packets.PublishPacket) router, stopper := newRouter() router.addRoute("a", cb) router.matchAndDispatch(msgs, true, nil) msgs <- pub <-calledback stopper <- true select { case msgs <- pub: t.Errorf("msgs should not have a listener") default: } }
func Test_NewPingReqMessage(t *testing.T) { pr := packets.NewControlPacket(packets.Pingreq).(*packets.PingreqPacket) if pr.MessageType != packets.Pingreq { t.Errorf("NewPingReqMessage bad msg type: %v", pr.MessageType) } if pr.RemainingLength != 0 { t.Errorf("NewPingReqMessage bad remlen, expected 0, got %d", pr.RemainingLength) } exp := []byte{ 0xC0, 0x00, } var buf bytes.Buffer pr.Write(&buf) bs := buf.Bytes() if len(bs) != 2 { t.Errorf("NewPingReqMessage.Bytes() wrong length: %d", len(bs)) } if exp[0] != bs[0] || exp[1] != bs[1] { t.Errorf("NewPingMessage.Bytes() wrong") } }
// Publish will publish a message with the specified QoS // and content to the specified topic. // Returns a read only channel used to track // the delivery of the message. func (c *Client) Publish(topic string, qos byte, retained bool, payload interface{}) Token { token := newToken(packets.Publish).(*PublishToken) DEBUG.Println(CLI, "enter Publish") if !c.IsConnected() { token.err = ErrNotConnected token.flowComplete() return token } pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pub.Qos = qos pub.TopicName = topic pub.Retain = retained switch payload.(type) { case string: pub.Payload = []byte(payload.(string)) case []byte: pub.Payload = payload.([]byte) default: token.err = errors.New("Unknown payload type") token.flowComplete() return token } DEBUG.Println(CLI, "sending publish message, topic:", topic) c.obound <- &PacketAndToken{p: pub, t: token} return token }
func Test_FileStore_Get(t *testing.T) { storedir := "/tmp/TestStore/_get" f := NewFileStore(storedir) f.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 1 pm.TopicName = "/a/b/c" pm.Payload = []byte{0xBE, 0xEF, 0xED} pm.MessageID = 120 key := outboundKeyFromMID(pm.MessageID) f.Put(key, pm) if !exists(storedir + "/o.120.msg") { t.Fatalf("message not in store") } exp := []byte{ /* msg type */ 0x32, // qos 1 /* remlen */ 0x0d, /* topic, msg id in varheader */ 0x00, // length of topic 0x06, 0x2F, // / 0x61, // a 0x2F, // / 0x62, // b 0x2F, // / 0x63, // c /* msg id (is always 2 bytes) */ 0x00, 0x78, /*payload */ 0xBE, 0xEF, 0xED, } m := f.Get(key) if m == nil { t.Fatalf("message not retreived from store") } var msg bytes.Buffer m.Write(&msg) if !bytes.Equal(exp, msg.Bytes()) { t.Fatal("message from store not same as what went in", msg.Bytes()) } }
func Test_MemoryStore_Get(t *testing.T) { m := NewMemoryStore() m.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 1 pm.TopicName = "/a/b/c" pm.Payload = []byte{0xBE, 0xEF, 0xED} pm.MessageID = 120 key := outboundKeyFromMID(pm.MessageID) m.Put(key, pm) if len(m.messages) != 1 { t.Fatalf("message not in store") } exp := []byte{ /* msg type */ 0x32, // qos 1 /* remlen */ 0x0d, /* topic, msg id in varheader */ 0x00, // length of topic 0x06, 0x2F, // / 0x61, // a 0x2F, // / 0x62, // b 0x2F, // / 0x63, // c /* msg id (is always 2 bytes) */ 0x00, 0x78, /*payload */ 0xBE, 0xEF, 0xED, } msg := m.Get(key) if msg == nil { t.Fatalf("message not retreived from store") } var buf bytes.Buffer msg.Write(&buf) if !bytes.Equal(exp, buf.Bytes()) { t.Fatalf("message from store not same as what went in") } }
func (c *client) process(in, out chan packets.ControlPacket) { conn := c.conn for { cp, err := packets.ReadPacket(conn) if err != nil { log.Printf("%s\n", err.Error()) if err == io.EOF { return } } switch cp.(type) { case *packets.PublishPacket: log.Printf("%s\n", cp.String()) p := cp.(*packets.PublishPacket) log.Printf("%s\n", p.TopicName) c.hms.Publish(p.TopicName, p.Payload) if p.Qos == 1 { pa := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket) pa.MessageID = p.MessageID pa.Write(conn) } case *packets.ConnectPacket: log.Printf("%s\n", cp.String()) p := cp.(*packets.ConnectPacket) log.Printf("%s\n", p.ProtocolName) c.id = p.ClientIdentifier ca := packets.NewControlPacket(packets.Connack).(*packets.ConnackPacket) ca.Write(conn) case *packets.SubscribePacket: p := cp.(*packets.SubscribePacket) c.hms.Subscribe(p.Topics[0], c.id) if p.Qos > 0 { pa := packets.NewControlPacket(packets.Suback).(*packets.SubackPacket) pa.MessageID = p.MessageID pa.Write(conn) } } } }
func Test_MemoryStore_write(t *testing.T) { m := NewMemoryStore() m.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 1 pm.TopicName = "/a/b/c" pm.Payload = []byte{0xBE, 0xEF, 0xED} pm.MessageID = 91 key := inboundKeyFromMID(pm.MessageID) m.Put(key, pm) if len(m.messages) != 1 { t.Fatalf("message not in store") } }
// Disconnect will end the connection with the server, but not before waiting // the specified number of milliseconds to wait for existing work to be // completed. func (c *Client) Disconnect(quiesce uint) { if !c.IsConnected() { WARN.Println(CLI, "already disconnected") return } DEBUG.Println(CLI, "disconnecting") c.setConnected(false) dm := packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket) dt := newToken(packets.Disconnect) c.oboundP <- &PacketAndToken{p: dm, t: dt} // wait for work to finish, or quiesce time consumed dt.WaitTimeout(time.Duration(quiesce) * time.Millisecond) c.disconnect() }
func Test_persistInbound_connack(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Connack) persistInbound(ts, m) if len(ts.mput) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 0 { t.Fatalf("persistInbound in bad state") } }
func Test_persistOutbound_disconnect(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Disconnect) persistOutbound(ts, m) if len(ts.mput) != 0 { t.Fatalf("persistOutbound put message it should not have") } if len(ts.mget) != 0 { t.Fatalf("persistOutbound get message it should not have") } if len(ts.mdel) != 0 { t.Fatalf("persistOutbound del message it should not have") } }
func Test_persistOutbound_pubrel(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket) m.MessageID = 43 persistOutbound(ts, m) if len(ts.mput) != 1 || ts.mput[0] != 43 { t.Fatalf("persistOutbound put message it should not have") } if len(ts.mget) != 0 { t.Fatalf("persistOutbound get message it should not have") } if len(ts.mdel) != 0 { t.Fatalf("persistOutbound del message it should not have") } }
func Test_persistOutbound_unsubscribe(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Unsubscribe).(*packets.UnsubscribePacket) m.Topics = []string{"/posub"} m.MessageID = 45 persistOutbound(ts, m) if len(ts.mput) != 1 || ts.mput[0] != 45 { t.Fatalf("persistOutbound put message it should not have") } if len(ts.mget) != 0 { t.Fatalf("persistOutbound get message it should not have") } if len(ts.mdel) != 0 { t.Fatalf("persistOutbound del message it should not have") } }
func Test_FileStore_write(t *testing.T) { storedir := "/tmp/TestStore/_write" f := NewFileStore(storedir) f.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 1 pm.TopicName = "a/b/c" pm.Payload = []byte{0xBE, 0xEF, 0xED} pm.MessageID = 91 key := inboundKeyFromMID(pm.MessageID) f.Put(key, pm) if !exists(storedir + "/i.91.msg") { t.Fatalf("message not in store") } }
// Unsubscribe will end the subscription from each of the topics provided. // Messages published to those topics from other clients will no longer be // received. func (c *Client) Unsubscribe(topics ...string) Token { token := newToken(packets.Unsubscribe).(*UnsubscribeToken) DEBUG.Println(CLI, "enter Unsubscribe") if !c.IsConnected() { token.err = ErrNotConnected token.flowComplete() return token } unsub := packets.NewControlPacket(packets.Unsubscribe).(*packets.UnsubscribePacket) unsub.Topics = make([]string, len(topics)) copy(unsub.Topics, topics) c.oboundP <- &PacketAndToken{p: unsub, t: token} for _, topic := range topics { c.msgRouter.deleteRoute(topic) } DEBUG.Println(CLI, "exit Unsubscribe") return token }
func Test_persistInbound_unsuback(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Unsuback).(*packets.UnsubackPacket) m.MessageID = 58 persistInbound(ts, m) if len(ts.mput) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 1 || ts.mdel[0] != 58 { t.Fatalf("persistInbound in bad state") } }
func Test_persistInbound_pubcomp(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket) m.MessageID = 56 persistInbound(ts, m) if len(ts.mput) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 1 || ts.mdel[0] != 56 { t.Fatalf("persistInbound in bad state") } }
func Test_persistOutbound_publish_2(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) m.Qos = 2 m.TopicName = "/popub2" m.Payload = []byte{0xBB, 0x00} m.MessageID = 42 persistOutbound(ts, m) if len(ts.mput) != 1 || ts.mput[0] != 42 { t.Fatalf("persistOutbound put message it should not have") } if len(ts.mget) != 0 { t.Fatalf("persistOutbound get message it should not have") } if len(ts.mdel) != 0 { t.Fatalf("persistOutbound del message it should not have") } }
func Test_persistInbound_publish_2(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) m.Qos = 2 m.TopicName = "/pipub2" m.Payload = []byte{0xCC, 0x03} m.MessageID = 52 persistInbound(ts, m) if len(ts.mput) != 1 || ts.mput[0] != 52 { t.Fatalf("persistInbound in bad state") } if len(ts.mget) != 0 { t.Fatalf("persistInbound in bad state") } if len(ts.mdel) != 0 { t.Fatalf("persistInbound in bad state") } }
func Test_FileStore_All(t *testing.T) { storedir := "/tmp/TestStore/_all" f := NewFileStore(storedir) f.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 2 pm.TopicName = "/t/r/v" pm.Payload = []byte{0x01, 0x02} pm.MessageID = 121 key := outboundKeyFromMID(pm.MessageID) f.Put(key, pm) keys := f.All() if len(keys) != 1 { t.Fatalf("FileStore.All does not have the messages") } if keys[0] != "o.121" { t.Fatalf("FileStore.All has wrong key") } }
func Test_persistOutbound_connect(t *testing.T) { ts := &TestStore{} m := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket) m.Qos = 0 m.Username = "******" m.Password = []byte("pass") m.ClientIdentifier = "cid" //m := newConnectMsg(false, false, QOS_ZERO, false, "", nil, "cid", "user", "pass", 10) persistOutbound(ts, m) if len(ts.mput) != 0 { t.Fatalf("persistOutbound put message it should not have") } if len(ts.mget) != 0 { t.Fatalf("persistOutbound get message it should not have") } if len(ts.mdel) != 0 { t.Fatalf("persistOutbound del message it should not have") } }
func Test_MemoryStore_Reset(t *testing.T) { m := NewMemoryStore() m.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 2 pm.TopicName = "/f/r/s" pm.Payload = []byte{0xAB} pm.MessageID = 81 key := outboundKeyFromMID(pm.MessageID) m.Put(key, pm) if len(m.messages) != 1 { t.Fatalf("message not in memstore") } m.Reset() if len(m.messages) != 0 { t.Fatalf("reset did not clear memstore") } }
func Test_MemoryStore_Del(t *testing.T) { m := NewMemoryStore() m.Open() pm := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) pm.Qos = 1 pm.TopicName = "/a/b/c" pm.Payload = []byte{0xBE, 0xEF, 0xED} pm.MessageID = 17 key := outboundKeyFromMID(pm.MessageID) m.Put(key, pm) if len(m.messages) != 1 { t.Fatalf("message not in store") } m.Del(key) if len(m.messages) != 1 { t.Fatalf("message still exists after deletion") } }
// receive Message objects on ibound // store messages if necessary // send replies on obound // delete messages from store if necessary func alllogic(c *Client) { DEBUG.Println(NET, "logic started") for { DEBUG.Println(NET, "logic waiting for msg on ibound") select { case msg := <-c.ibound: DEBUG.Println(NET, "logic got msg on ibound") //persist_ibound(c.persist, msg) switch msg.(type) { case *packets.PingrespPacket: DEBUG.Println(NET, "received pingresp") c.pingOutstanding = false case *packets.SubackPacket: sa := msg.(*packets.SubackPacket) DEBUG.Println(NET, "received suback, id:", sa.MessageID) token := c.getToken(sa.MessageID).(*SubscribeToken) DEBUG.Println(NET, "granted qoss", sa.GrantedQoss) for i, qos := range sa.GrantedQoss { token.subResult[token.subs[i]] = qos } token.flowComplete() go c.freeID(sa.MessageID) case *packets.UnsubackPacket: ua := msg.(*packets.UnsubackPacket) DEBUG.Println(NET, "received unsuback, id:", ua.MessageID) token := c.getToken(ua.MessageID).(*UnsubscribeToken) token.flowComplete() go c.freeID(ua.MessageID) case *packets.PublishPacket: pp := msg.(*packets.PublishPacket) DEBUG.Println(NET, "received publish, msgId:", pp.MessageID) DEBUG.Println(NET, "putting msg on onPubChan") switch pp.Qos { case 2: c.incomingPubChan <- pp DEBUG.Println(NET, "done putting msg on incomingPubChan") pr := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket) pr.MessageID = pp.MessageID DEBUG.Println(NET, "putting pubrec msg on obound") c.oboundP <- &PacketAndToken{p: pr, t: nil} DEBUG.Println(NET, "done putting pubrec msg on obound") case 1: c.incomingPubChan <- pp DEBUG.Println(NET, "done putting msg on incomingPubChan") pa := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket) pa.MessageID = pp.MessageID DEBUG.Println(NET, "putting puback msg on obound") c.oboundP <- &PacketAndToken{p: pa, t: nil} DEBUG.Println(NET, "done putting puback msg on obound") case 0: select { case c.incomingPubChan <- pp: DEBUG.Println(NET, "done putting msg on incomingPubChan") case err, ok := <-c.errors: DEBUG.Println(NET, "error while putting msg on pubChanZero") // We are unblocked, but need to put the error back on so the outer // select can handle it appropriately. if ok { go func(errVal error, errChan chan error) { errChan <- errVal }(err, c.errors) } } } case *packets.PubackPacket: pa := msg.(*packets.PubackPacket) DEBUG.Println(NET, "received puback, id:", pa.MessageID) // c.receipts.get(msg.MsgId()) <- Receipt{} // c.receipts.end(msg.MsgId()) c.getToken(pa.MessageID).flowComplete() c.freeID(pa.MessageID) case *packets.PubrecPacket: prec := msg.(*packets.PubrecPacket) DEBUG.Println(NET, "received pubrec, id:", prec.MessageID) prel := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket) prel.MessageID = prec.MessageID select { case c.oboundP <- &PacketAndToken{p: prel, t: nil}: case <-time.After(time.Second): } case *packets.PubrelPacket: pr := msg.(*packets.PubrelPacket) DEBUG.Println(NET, "received pubrel, id:", pr.MessageID) pc := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket) pc.MessageID = pr.MessageID select { case c.oboundP <- &PacketAndToken{p: pc, t: nil}: case <-time.After(time.Second): } case *packets.PubcompPacket: pc := msg.(*packets.PubcompPacket) DEBUG.Println(NET, "received pubcomp, id:", pc.MessageID) c.getToken(pc.MessageID).flowComplete() c.freeID(pc.MessageID) } case <-c.stop: WARN.Println(NET, "logic stopped") return case err := <-c.errors: ERROR.Println(NET, "logic got error") c.internalConnLost(err) return } c.lastContact.update() } }