/* * Copyright (c) 2014 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Allan Stockdill-Mander */ package mqtt import ( "sync" "time" "github.com/eclipse/paho.mqtt.golang/packets" ) // PacketAndToken is a struct that contains both a ControlPacket and a // Token. This struct is passed via channels between the client interface // code and the underlying code responsible for sending and receiving // MQTT messages. type PacketAndToken struct { p packets.ControlPacket t tokenCompletor } // Token defines the interface for the tokens used to indicate when // actions have completed. type Token interface { Wait() bool WaitTimeout(time.Duration) bool Error() error } type TokenErrorSetter interface { setError(error) } type tokenCompletor interface { Token TokenErrorSetter flowComplete() } type baseToken struct { m sync.RWMutex complete chan struct{} ready bool err error } // Wait will wait indefinitely for the Token to complete, ie the Publish // to be sent and confirmed receipt from the broker func (b *baseToken) Wait() bool { b.m.Lock() defer b.m.Unlock() if !b.ready { <-b.complete b.ready = true } return b.ready } // WaitTimeout takes a time.Duration to wait for the flow associated with the // Token to complete, returns true if it returned before the timeout or // returns false if the timeout occurred. In the case of a timeout the Token // does not have an error set in case the caller wishes to wait again func (b *baseToken) WaitTimeout(d time.Duration) bool { b.m.Lock() defer b.m.Unlock() if !b.ready { timer := time.NewTimer(d) select { case <-b.complete: b.ready = true if !timer.Stop() { <-timer.C } case <-timer.C: } } return b.ready } func (b *baseToken) flowComplete() { select { case <-b.complete: default: close(b.complete) } } func (b *baseToken) Error() error { b.m.RLock() defer b.m.RUnlock() return b.err } func (b *baseToken) setError(e error) { b.err = e b.flowComplete() } func newToken(tType byte) tokenCompletor { switch tType { case packets.Connect: return &ConnectToken{baseToken: baseToken{complete: make(chan struct{})}} case packets.Subscribe: return &SubscribeToken{baseToken: baseToken{complete: make(chan struct{})}, subResult: make(map[string]byte)} case packets.Publish: return &PublishToken{baseToken: baseToken{complete: make(chan struct{})}} case packets.Unsubscribe: return &UnsubscribeToken{baseToken: baseToken{complete: make(chan struct{})}} case packets.Disconnect: return &DisconnectToken{baseToken: baseToken{complete: make(chan struct{})}} } return nil } // ConnectToken is an extension of Token containing the extra fields // required to provide information about calls to Connect() type ConnectToken struct { baseToken returnCode byte sessionPresent bool } // ReturnCode returns the acknowlegement code in the connack sent // in response to a Connect() func (c *ConnectToken) ReturnCode() byte { c.m.RLock() defer c.m.RUnlock() return c.returnCode } // SessionPresent returns a bool representing the value of the // session present field in the connack sent in response to a Connect() func (c *ConnectToken) SessionPresent() bool { c.m.RLock() defer c.m.RUnlock() return c.sessionPresent } // PublishToken is an extension of Token containing the extra fields // required to provide information about calls to Publish() type PublishToken struct { baseToken messageID uint16 } // MessageID returns the MQTT message ID that was assigned to the // Publish packet when it was sent to the broker func (p *PublishToken) MessageID() uint16 { return p.messageID } // SubscribeToken is an extension of Token containing the extra fields // required to provide information about calls to Subscribe() type SubscribeToken struct { baseToken subs []string subResult map[string]byte } // Result returns a map of topics that were subscribed to along with // the matching return code from the broker. This is either the Qos // value of the subscription or an error code. func (s *SubscribeToken) Result() map[string]byte { s.m.RLock() defer s.m.RUnlock() return s.subResult } // UnsubscribeToken is an extension of Token containing the extra fields // required to provide information about calls to Unsubscribe() type UnsubscribeToken struct { baseToken } // DisconnectToken is an extension of Token containing the extra fields // required to provide information about calls to Disconnect() type DisconnectToken struct { baseToken }