github.com/futurehomeno/fimpgo@v1.14.0/mqtt_transport.go (about)

     1  package fimpgo
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	MQTT "github.com/eclipse/paho.mqtt.golang"
    14  	"github.com/pkg/errors"
    15  	log "github.com/sirupsen/logrus"
    16  
    17  	"github.com/futurehomeno/fimpgo/utils"
    18  )
    19  
    20  type MessageCh chan *Message
    21  
    22  type MqttConnectionConfigs struct {
    23  	ServerURI           string
    24  	ClientID            string
    25  	Username            string
    26  	Password            string
    27  	CleanSession        bool
    28  	SubQos              byte
    29  	PubQos              byte
    30  	GlobalTopicPrefix   string // Should be set for communicating one single hub via cloud
    31  	StartFailRetryCount int
    32  	CertDir             string // full path to directory where all certificates are stored. Cert dir should contains all CA root certificates .
    33  	PrivateKeyFileName  string //
    34  	CertFileName        string //
    35  	ReceiveChTimeout    int
    36  	IsAws               bool // Should be set to true if cloud broker is AwS IoT platform .
    37  
    38  	connectionLostHandler MQTT.ConnectionLostHandler
    39  }
    40  
    41  type Message struct {
    42  	Topic   string
    43  	Addr    *Address
    44  	Payload *FimpMessage
    45  	RawPayload []byte
    46  }
    47  
    48  type FimpFilter struct {
    49  	Topic     string
    50  	Service   string
    51  	Interface string
    52  }
    53  
    54  type FilterFunc func(topic string, addr *Address, iotMsg *FimpMessage) bool
    55  
    56  type MqttTransport struct {
    57  	client         MQTT.Client
    58  	msgHandler     MessageHandler
    59  	subQos         byte
    60  	pubQos         byte
    61  	subs           map[string]byte
    62  	subChannels    map[string]MessageCh
    63  	subFilters     map[string]FimpFilter
    64  	subFilterFuncs map[string]FilterFunc
    65  
    66  	globalTopicPrefixMux sync.RWMutex
    67  	globalTopicPrefix    string
    68  	defaultSourceLock    sync.RWMutex
    69  	defaultSource        string
    70  	startFailRetryCount  int
    71  	certDir              string
    72  	mqttOptions          *MQTT.ClientOptions
    73  	receiveChTimeout     int
    74  	syncPublishTimeout   time.Duration
    75  	channelRegMux        sync.Mutex
    76  	subMutex             sync.Mutex
    77  	compressor           *MsgCompressor
    78  }
    79  
    80  func (mh *MqttTransport) SetReceiveChTimeout(receiveChTimeout int) {
    81  	mh.receiveChTimeout = receiveChTimeout
    82  }
    83  
    84  func (mh *MqttTransport) SetCertDir(certDir string) {
    85  	mh.certDir = certDir
    86  }
    87  
    88  func (mh *MqttTransport) Options() *MQTT.ClientOptions {
    89  	return mh.mqttOptions
    90  }
    91  
    92  func (mh *MqttTransport) SetOptions(options *MQTT.ClientOptions) {
    93  	mh.client = MQTT.NewClient(options)
    94  }
    95  
    96  type MessageHandler func(topic string, addr *Address, iotMsg *FimpMessage, rawPayload []byte)
    97  
    98  // NewMqttTransport constructor. serverUri="tcp://localhost:1883"
    99  func NewMqttTransport(serverURI, clientID, username, password string, cleanSession bool, subQos byte, pubQos byte) *MqttTransport {
   100  	mh := MqttTransport{}
   101  	mh.mqttOptions = MQTT.NewClientOptions().AddBroker(serverURI)
   102  	mh.mqttOptions.SetClientID(clientID)
   103  	mh.mqttOptions.SetUsername(username)
   104  	mh.mqttOptions.SetPassword(password)
   105  	mh.mqttOptions.SetDefaultPublishHandler(mh.onMessage)
   106  	mh.mqttOptions.SetCleanSession(cleanSession)
   107  	mh.mqttOptions.SetAutoReconnect(true)
   108  	mh.mqttOptions.SetConnectionLostHandler(mh.onConnectionLost)
   109  	mh.mqttOptions.SetOnConnectHandler(mh.onConnect)
   110  	mh.mqttOptions.SetWriteTimeout(time.Second*30)
   111  	//create and start a client using the above ClientOptions
   112  	mh.client = MQTT.NewClient(mh.mqttOptions)
   113  	mh.pubQos = pubQos
   114  	mh.subQos = subQos
   115  	mh.subs = make(map[string]byte)
   116  	mh.subChannels = make(map[string]MessageCh)
   117  	mh.subFilters = make(map[string]FimpFilter)
   118  	mh.subFilterFuncs = make(map[string]FilterFunc)
   119  	mh.startFailRetryCount = 10
   120  	mh.receiveChTimeout = 10
   121  	mh.syncPublishTimeout = time.Second * 5
   122  	mh.compressor = NewMsgCompressor("","")
   123  	return &mh
   124  }
   125  
   126  func NewMqttTransportFromConnection(client MQTT.Client, subQos byte, pubQos byte) *MqttTransport {
   127  	mh := MqttTransport{}
   128  	mh.client = client
   129  	mh.pubQos = pubQos
   130  	mh.subQos = subQos
   131  	mh.subs = make(map[string]byte)
   132  	mh.subChannels = make(map[string]MessageCh)
   133  	mh.subFilters = make(map[string]FimpFilter)
   134  	mh.subFilterFuncs = make(map[string]FilterFunc)
   135  	mh.startFailRetryCount = 10
   136  	mh.receiveChTimeout = 10
   137  	mh.syncPublishTimeout = time.Second * 5
   138  	mh.compressor = NewMsgCompressor("","")
   139  	return &mh
   140  }
   141  
   142  func NewMqttTransportFromConfigs(configs MqttConnectionConfigs, options ...Option) *MqttTransport {
   143  
   144  	applyDefaults(&configs)
   145  
   146  	// apply extra options
   147  	for _, o := range options {
   148  		o.apply(&configs)
   149  	}
   150  
   151  	mh := MqttTransport{}
   152  	mh.mqttOptions = MQTT.NewClientOptions().AddBroker(configs.ServerURI)
   153  	mh.mqttOptions.SetClientID(configs.ClientID)
   154  	mh.mqttOptions.SetUsername(configs.Username)
   155  	mh.mqttOptions.SetPassword(configs.Password)
   156  	mh.mqttOptions.SetDefaultPublishHandler(mh.onMessage)
   157  	mh.mqttOptions.SetCleanSession(configs.CleanSession)
   158  	mh.mqttOptions.SetAutoReconnect(true)
   159  	mh.mqttOptions.SetConnectionLostHandler(configs.connectionLostHandler)
   160  	mh.mqttOptions.SetOnConnectHandler(mh.onConnect)
   161  
   162  	//create and start a client using the above ClientOptions
   163  	mh.client = MQTT.NewClient(mh.mqttOptions)
   164  	mh.pubQos = configs.PubQos
   165  	mh.subQos = configs.SubQos
   166  	mh.subs = make(map[string]byte)
   167  	mh.subChannels = make(map[string]MessageCh)
   168  	mh.subFilters = make(map[string]FimpFilter)
   169  	mh.subFilterFuncs = make(map[string]FilterFunc)
   170  	mh.startFailRetryCount = 10
   171  	mh.receiveChTimeout = 10
   172  	mh.syncPublishTimeout = time.Second * 5
   173  	mh.certDir = configs.CertDir
   174  	mh.globalTopicPrefix = configs.GlobalTopicPrefix
   175  	mh.compressor = NewMsgCompressor("","")
   176  	if configs.StartFailRetryCount == 0 {
   177  		mh.startFailRetryCount = 10
   178  	} else {
   179  		mh.startFailRetryCount = configs.StartFailRetryCount
   180  	}
   181  	if configs.ReceiveChTimeout == 0 {
   182  		mh.receiveChTimeout = 10
   183  	} else {
   184  		mh.receiveChTimeout = configs.ReceiveChTimeout
   185  	}
   186  
   187  	if configs.PrivateKeyFileName != "" && configs.CertFileName != "" {
   188  		err := mh.ConfigureTls(configs.PrivateKeyFileName, configs.CertFileName, configs.CertDir, configs.IsAws)
   189  		if err != nil {
   190  			log.Error("Certificate loading error :", err.Error())
   191  		}
   192  	}
   193  	return &mh
   194  }
   195  
   196  func (mh *MqttTransport) SetGlobalTopicPrefix(prefix string) {
   197  	mh.globalTopicPrefixMux.Lock()
   198  	mh.globalTopicPrefix = prefix
   199  	mh.globalTopicPrefixMux.Unlock()
   200  }
   201  
   202  func (mh *MqttTransport) getGlobalTopicPrefix() string {
   203  	mh.globalTopicPrefixMux.RLock()
   204  	defer mh.globalTopicPrefixMux.RUnlock()
   205  	return mh.globalTopicPrefix
   206  }
   207  
   208  // SetDefaultSource safely sets default source name for all outgoing messages.
   209  // Default source is used only if it was not set explicitly before.
   210  func (mh *MqttTransport) SetDefaultSource(source string) {
   211  	mh.defaultSourceLock.Lock()
   212  	defer mh.defaultSourceLock.Unlock()
   213  
   214  	mh.defaultSource = source
   215  }
   216  
   217  // ensureDefaultSource safely sets default source name for an outgoing message.
   218  // Default source is used only if it was not set explicitly before.
   219  func (mh *MqttTransport) ensureDefaultSource(message *FimpMessage) {
   220  	if message.Source != "" {
   221  		return
   222  	}
   223  
   224  	mh.defaultSourceLock.RLock()
   225  	defer mh.defaultSourceLock.RUnlock()
   226  
   227  	message.Source = mh.defaultSource
   228  }
   229  
   230  // SetStartAutoRetryCount Set number of retries transport will attempt on startup . Default value is 10
   231  func (mh *MqttTransport) SetStartAutoRetryCount(count int) {
   232  	mh.startFailRetryCount = count
   233  }
   234  
   235  // SetMessageHandler message handler setter
   236  func (mh *MqttTransport) SetMessageHandler(msgHandler MessageHandler) {
   237  	mh.msgHandler = msgHandler
   238  }
   239  
   240  // RegisterChannel should be used if new message has to be sent to channel instead of callback.
   241  // multiple channels can be registered , in that case a message bill be multicasted to all channels.
   242  func (mh *MqttTransport) RegisterChannel(channelId string, messageCh MessageCh) {
   243  	mh.channelRegMux.Lock()
   244  	mh.subChannels[channelId] = messageCh
   245  	mh.channelRegMux.Unlock()
   246  }
   247  
   248  // UnregisterChannel should be used to unregister channel
   249  func (mh *MqttTransport) UnregisterChannel(channelId string) {
   250  	mh.channelRegMux.Lock()
   251  	delete(mh.subChannels, channelId)
   252  	delete(mh.subFilters, channelId)
   253  	delete(mh.subFilterFuncs, channelId)
   254  	mh.channelRegMux.Unlock()
   255  }
   256  
   257  // RegisterChannelWithFilter should be used if new message has to be sent to channel instead of callback.
   258  // multiple channels can be registered , in that case a message bill be multicasted to all channels.
   259  func (mh *MqttTransport) RegisterChannelWithFilter(channelId string, messageCh MessageCh, filter FimpFilter) {
   260  	mh.channelRegMux.Lock()
   261  	mh.subChannels[channelId] = messageCh
   262  	mh.subFilters[channelId] = filter
   263  	mh.channelRegMux.Unlock()
   264  }
   265  
   266  // RegisterChannelWithFilterFunc should be used if new message has to be sent to channel instead of callback.
   267  // multiple channels can be registered , in that case a message bill be multicasted to all channels.
   268  func (mh *MqttTransport) RegisterChannelWithFilterFunc(channelId string, messageCh MessageCh, filterFunc FilterFunc) {
   269  	mh.channelRegMux.Lock()
   270  	mh.subChannels[channelId] = messageCh
   271  	mh.subFilterFuncs[channelId] = filterFunc
   272  	mh.channelRegMux.Unlock()
   273  }
   274  
   275  func (mh *MqttTransport) Client() MQTT.Client {
   276  	return mh.client
   277  }
   278  
   279  // Start , starts adapter async.
   280  func (mh *MqttTransport) Start() error {
   281  	log.Info("<MqttAd> Connecting to MQTT broker ")
   282  	var err error
   283  	var delay time.Duration
   284  	for i := 1; i < mh.startFailRetryCount; i++ {
   285  		if token := mh.client.Connect(); token.Wait() && token.Error() == nil {
   286  			return nil
   287  		} else {
   288  			err = token.Error()
   289  		}
   290  		delay = time.Duration(i) * time.Duration(i)
   291  		log.Infof("<MqttAd> Connection failed , retrying after %d sec.... ", delay)
   292  		time.Sleep(delay * time.Second)
   293  	}
   294  	return err
   295  }
   296  
   297  // Stop stops adapter . Adapter can't be started again using Start . In order to start adapter it has to be re-initialized
   298  func (mh *MqttTransport) Stop() {
   299  	mh.client.Disconnect(250)
   300  }
   301  
   302  // Subscribe - subscribing for topic
   303  func (mh *MqttTransport) Subscribe(topic string) error {
   304  	if strings.TrimSpace(topic) == "" {
   305  		return nil
   306  	}
   307  
   308  	mh.subMutex.Lock()
   309  	defer mh.subMutex.Unlock()
   310  
   311  	//subscribe to the topic /go-mqtt/sample and request messages to be delivered
   312  	//at a maximum qos of zero, wait for the receipt to confirm the subscription
   313  	topic = AddGlobalPrefixToTopic(mh.getGlobalTopicPrefix(), topic)
   314  	log.Debug("<MqttAd> Subscribing to topic:", topic)
   315  	token := mh.client.Subscribe(topic, mh.subQos, nil)
   316  	isInTime := token.WaitTimeout(time.Second * 20)
   317  	if token.Error() != nil {
   318  		log.Error("<MqttAd> Can't subscribe. Error :", token.Error())
   319  		return token.Error()
   320  	} else if !isInTime {
   321  		log.Error("<MqttAd> Subscribe operation timed out")
   322  		return errors.New("subscribe timed out")
   323  	}
   324  
   325  	mh.subs[topic] = mh.subQos
   326  
   327  	return nil
   328  }
   329  
   330  // Unsubscribe , unsubscribing from topic
   331  func (mh *MqttTransport) Unsubscribe(topic string) error {
   332  	mh.subMutex.Lock()
   333  	defer mh.subMutex.Unlock()
   334  	topic = AddGlobalPrefixToTopic(mh.getGlobalTopicPrefix(), topic)
   335  	log.Debug("<MqttAd> Unsubscribing from topic:", topic)
   336  	token := mh.client.Unsubscribe(topic)
   337  	isInTime := token.WaitTimeout(time.Second * 20)
   338  	if token.Error() != nil {
   339  		return token.Error()
   340  	} else if !isInTime {
   341  		log.Error("<MqttAd> Unsubscribe operation timed out")
   342  		return errors.New("unsubscribe timed out")
   343  	}
   344  	delete(mh.subs, topic)
   345  	return nil
   346  }
   347  
   348  func (mh *MqttTransport) UnsubscribeAll() {
   349  	var topics []string
   350  	mh.subMutex.Lock()
   351  	for i := range mh.subs {
   352  		topics = append(topics, i)
   353  	}
   354  	mh.subMutex.Unlock()
   355  	for _, t := range topics {
   356  		if err := mh.Unsubscribe(t); err != nil {
   357  			log.Error(errors.Wrap(err, "unsubscribing from topic"))
   358  		}
   359  	}
   360  }
   361  
   362  func (mh *MqttTransport) onConnectionLost(_ MQTT.Client, err error) {
   363  	log.Errorf("<MqttAd> Connection lost with MQTT broker . Error : %v", err)
   364  }
   365  
   366  func (mh *MqttTransport) onConnect(_ MQTT.Client) {
   367  	mh.subMutex.Lock()
   368  	defer mh.subMutex.Unlock()
   369  
   370  	log.Infof("<MqttAd> Connection established with MQTT broker .")
   371  	if len(mh.subs) > 0 {
   372  		if token := mh.client.SubscribeMultiple(mh.subs, nil); token.Wait() && token.Error() != nil {
   373  			log.Error("Can't subscribe. Error :", token.Error())
   374  		}
   375  	}
   376  }
   377  
   378  //onMessage default message handler
   379  func (mh *MqttTransport) onMessage(_ MQTT.Client, msg MQTT.Message) {
   380  	defer func() {
   381  		if r := recover(); r != nil {
   382  			log.Error("<MqttAd> onMessage CRASHED with error :", r)
   383  		}
   384  	}()
   385  	log.Tracef("<MqttAd> New msg from TOPIC: %s", msg.Topic())
   386  	var topic string
   387  	if strings.TrimSpace(mh.globalTopicPrefix) != "" {
   388  		_, topic = DetachGlobalPrefixFromTopic(msg.Topic())
   389  	} else {
   390  		topic = msg.Topic()
   391  	}
   392  
   393  	addr, err := NewAddressFromString(topic)
   394  	if err != nil {
   395  		log.Error("<MqttAd> Error processing address :", err)
   396  		return
   397  	}
   398  	var fimpMsg *FimpMessage
   399  
   400  	switch addr.PayloadType {
   401  	case DefaultPayload:
   402  		fimpMsg, err = NewMessageFromBytes(msg.Payload())
   403  	case CompressedJsonPayload:
   404  		fimpMsg,err = mh.compressor.DecompressFimpMsg(msg.Payload())
   405  	default:
   406  		// This means unknown binary payload , for instance compressed message
   407  		log.Trace("<MqttAd> Unknown binary payload :", addr.PayloadType)
   408  	}
   409  
   410  	if mh.msgHandler != nil {
   411  		if err == nil  {
   412  			mh.msgHandler(topic, addr, fimpMsg, msg.Payload())
   413  		} else {
   414  			log.Trace(string(msg.Payload()))
   415  			log.Error("<MqttAd> Error processing payload :", err)
   416  			return
   417  		}
   418  	}
   419  
   420  	mh.channelRegMux.Lock()
   421  	defer mh.channelRegMux.Unlock()
   422  
   423  	for i := range mh.subChannels {
   424  		if !mh.isChannelInterested(i, topic, addr, fimpMsg) {
   425  			continue
   426  		}
   427  		var fmsg Message
   428  		if addr.PayloadType == DefaultPayload || addr.PayloadType == CompressedJsonPayload {
   429  			fmsg = Message{Topic: topic, Addr: addr, Payload: fimpMsg}
   430  		}else {
   431  			// message receiver should do decompressions
   432  			fmsg = Message{Topic: topic, Addr: addr, RawPayload: msg.Payload()}
   433  		}
   434  		timer := time.NewTimer(time.Second * time.Duration(mh.receiveChTimeout))
   435  		select {
   436  		case mh.subChannels[i] <- &fmsg:
   437  			timer.Stop()
   438  			// send to channel
   439  		case <-timer.C:
   440  			log.Info("<MqttAd> Channel is not read for ", mh.receiveChTimeout)
   441  		}
   442  	}
   443  
   444  }
   445  
   446  // isChannelInterested validates if channel is interested in message. Filtering is executed against either static filters or filter function
   447  func (mh *MqttTransport) isChannelInterested(chanName string, topic string, addr *Address, msg *FimpMessage) bool {
   448  	defer func() {
   449  		if r := recover(); r != nil {
   450  			log.Error("<MqttAd> Filter CRASHED with error :", r)
   451  		}
   452  	}()
   453  
   454  	filterFunc, ok := mh.subFilterFuncs[chanName]
   455  	if ok {
   456  		return filterFunc(topic, addr, msg)
   457  	}
   458  	filter, ok := mh.subFilters[chanName]
   459  	if !ok {
   460  		// no filters has been set
   461  		return true
   462  	}
   463  	if msg != nil {
   464  		if utils.RouteIncludesTopic(filter.Topic, topic) &&
   465  			(msg.Service == filter.Service || filter.Service == "*") &&
   466  			(msg.Type == filter.Interface || filter.Interface == "*") {
   467  			return true
   468  		}
   469  	}else {
   470  		// It means binary payload , and message can't be parsed
   471  		if utils.RouteIncludesTopic(filter.Topic, topic) {
   472  			return true
   473  		}
   474  	}
   475  
   476  	return false
   477  }
   478  
   479  // Publish publishes message to FIMP address
   480  func (mh *MqttTransport) Publish(addr *Address, fimpMsg *FimpMessage) error {
   481  	mh.ensureDefaultSource(fimpMsg)
   482  
   483  	var bytm []byte
   484  	var err error
   485  	if addr.PayloadType == "" {
   486  		addr.PayloadType = DefaultPayload
   487  	}
   488  	switch addr.PayloadType {
   489  	case DefaultPayload:
   490  		bytm, err = fimpMsg.SerializeToJson()
   491  	case CompressedJsonPayload:
   492  		bytm, err = mh.compressor.CompressFimpMsg(fimpMsg)
   493  	default:
   494  		// This means unknown binary payload , for instance compressed message
   495  		log.Trace("<MqttAd> Publish - unknown binary payload :", addr.PayloadType)
   496  	}
   497  	if err != nil {
   498  		return err
   499  	}
   500  	topic := addr.Serialize()
   501  	if strings.TrimSpace(mh.globalTopicPrefix) != "" {
   502  		topic = AddGlobalPrefixToTopic(mh.getGlobalTopicPrefix(), topic)
   503  	}
   504  	if err == nil {
   505  		log.Trace("<MqttAd> Publishing msg to topic:", topic)
   506  		mh.client.Publish(topic, mh.pubQos, false, bytm)
   507  		return nil
   508  	}
   509  	return err
   510  }
   511  
   512  // PublishToTopic publishes iotMsg to string topic
   513  func (mh *MqttTransport) PublishToTopic(topic string, fimpMsg *FimpMessage) error {
   514  	mh.ensureDefaultSource(fimpMsg)
   515  
   516  	byteMessage, err := fimpMsg.SerializeToJson()
   517  	if err != nil {
   518  		return err
   519  	}
   520  	addr,err := NewAddressFromString(topic)
   521  	if err == nil {
   522  		if addr.PayloadType == CompressedJsonPayload {
   523  			byteMessage,err = mh.compressor.CompressBinMsg(byteMessage)
   524  			if err != nil {
   525  				return err
   526  			}
   527  		}
   528  	}
   529  
   530  	if strings.TrimSpace(mh.globalTopicPrefix) != "" {
   531  		topic = AddGlobalPrefixToTopic(mh.getGlobalTopicPrefix(), topic)
   532  	}
   533  
   534  	log.Trace("<MqttAd> Publishing msg to topic:", topic)
   535  	return mh.client.Publish(topic, mh.pubQos, false, byteMessage).Error()
   536  }
   537  
   538  // RespondToRequest should be used by a service to respond to request
   539  func (mh *MqttTransport) RespondToRequest(requestMsg *FimpMessage, responseMsg *FimpMessage) error {
   540  	if requestMsg.ResponseToTopic == "" {
   541  		return errors.New("empty response topic")
   542  	}
   543  	return mh.PublishToTopic(requestMsg.ResponseToTopic, responseMsg)
   544  }
   545  
   546  func (mh *MqttTransport) PublishSync(addr *Address, fimpMsg *FimpMessage) error {
   547  	mh.ensureDefaultSource(fimpMsg)
   548  
   549  	var bytm []byte
   550  	var err error
   551  	if addr.PayloadType == "" {
   552  		addr.PayloadType = DefaultPayload
   553  	}
   554  	switch addr.PayloadType {
   555  	case DefaultPayload:
   556  		bytm, err = fimpMsg.SerializeToJson()
   557  	case CompressedJsonPayload:
   558  		bytm, err = mh.compressor.CompressFimpMsg(fimpMsg)
   559  
   560  	}
   561  	topic := addr.Serialize()
   562  	if strings.TrimSpace(mh.globalTopicPrefix) != "" {
   563  		topic = AddGlobalPrefixToTopic(mh.getGlobalTopicPrefix(), topic)
   564  	}
   565  	if err == nil {
   566  		log.Trace("<MqttAd> Publishing msg to topic:", topic)
   567  		token := mh.client.Publish(topic, mh.pubQos, false, bytm)
   568  		if token.WaitTimeout(mh.syncPublishTimeout) && token.Error() == nil {
   569  			return nil
   570  		} else {
   571  			return token.Error()
   572  		}
   573  	}
   574  	return err
   575  }
   576  
   577  func (mh *MqttTransport) PublishRaw(topic string, bytem []byte) {
   578  	log.Trace("<MqttAd> Publishing msg to topic:", topic)
   579  	mh.client.Publish(topic, mh.pubQos, false, bytem)
   580  }
   581  
   582  func (mh *MqttTransport) PublishRawSync(topic string, bytem []byte) error {
   583  	log.Trace("<MqttAd> Publishing msg to topic:", topic)
   584  	token := mh.client.Publish(topic, mh.pubQos, false, bytem)
   585  	if token.WaitTimeout(mh.syncPublishTimeout) && token.Error() == nil {
   586  		return nil
   587  	} else {
   588  		return token.Error()
   589  	}
   590  
   591  }
   592  
   593  // AddGlobalPrefixToTopic , adds prefix to topic .
   594  func AddGlobalPrefixToTopic(domain string, topic string) string {
   595  	// Check if topic is already prefixed with  "/" if yes then concat without adding "/"
   596  	// 47 is code of "/"
   597  	if topic[0] == 47 {
   598  		return domain + topic
   599  	}
   600  
   601  	if strings.TrimSpace(domain) == "" {
   602  		return topic
   603  	}
   604  	return domain + "/" + topic
   605  }
   606  
   607  // DetachGlobalPrefixFromTopic detaches domain from topic
   608  func DetachGlobalPrefixFromTopic(topic string) (string, string) {
   609  	spt := strings.Split(topic, "/")
   610  	var resultTopic, globalPrefix string
   611  	for i := range spt {
   612  		if strings.Contains(spt[i], "pt:") {
   613  			//resultTopic= strings.Replace(topic, spt[0]+"/", "", 1)
   614  			resultTopic = strings.Join(spt[i:], "/")
   615  			globalPrefix = strings.Join(spt[:i], "/")
   616  			break
   617  		}
   618  	}
   619  
   620  	// returns domain , topic
   621  	return globalPrefix, resultTopic
   622  }
   623  
   624  // ConfigureTls The method should be used to configure mutual TLS , like AwS IoT core is using . Also it configures TLS protocol switch .
   625  // Cert dir should contains all CA root certificates .
   626  // IsAws flag controls AWS specific TLS protocol switch.
   627  func (mh *MqttTransport) ConfigureTls(privateKeyFileName, certFileName, certDir string, isAws bool) error {
   628  	mh.certDir = certDir
   629  	privateKeyFileName = filepath.Join(certDir, privateKeyFileName)
   630  	certFileName = filepath.Join(certDir, certFileName)
   631  	TLSConfig := &tls.Config{InsecureSkipVerify: false}
   632  	if isAws {
   633  		TLSConfig.NextProtos = []string{"x-amzn-mqtt-ca"}
   634  	}
   635  
   636  	certPool, err := mh.getCACertPool()
   637  	if err != nil {
   638  		return err
   639  	}
   640  	TLSConfig.RootCAs = certPool
   641  
   642  	if strings.TrimSpace(certFileName) != "" {
   643  		certPool, err := mh.getCertPool(certFileName)
   644  		if err != nil {
   645  			return err
   646  		}
   647  		TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
   648  		TLSConfig.ClientCAs = certPool
   649  	}
   650  	if privateKeyFileName != "" {
   651  		if certFileName == "" {
   652  			return fmt.Errorf("key specified but cert is not specified")
   653  		}
   654  		cert, err := tls.LoadX509KeyPair(certFileName, privateKeyFileName)
   655  		if err != nil {
   656  			return err
   657  		}
   658  		TLSConfig.Certificates = []tls.Certificate{cert}
   659  	}
   660  	mh.mqttOptions.SetTLSConfig(TLSConfig)
   661  	mh.client = MQTT.NewClient(mh.mqttOptions)
   662  	return nil
   663  
   664  }
   665  
   666  // configuring CA certificate pool
   667  func (mh *MqttTransport) getCACertPool() (*x509.CertPool, error) {
   668  	certs := x509.NewCertPool()
   669  	cafile := filepath.Join(mh.certDir, "root-ca-1.pem")
   670  	pemData, err := ioutil.ReadFile(cafile)
   671  	if err != nil {
   672  		return nil, err
   673  	}
   674  	certs.AppendCertsFromPEM(pemData)
   675  
   676  	cafile = filepath.Join(mh.certDir, "root-ca-2.pem")
   677  	pemData, err = ioutil.ReadFile(cafile)
   678  	certs.AppendCertsFromPEM(pemData)
   679  
   680  	cafile = filepath.Join(mh.certDir, "root-ca-3.pem")
   681  	pemData, err = ioutil.ReadFile(cafile)
   682  	certs.AppendCertsFromPEM(pemData)
   683  	log.Infof("CA certificates are loaded.")
   684  	return certs, nil
   685  }
   686  
   687  // configuring certificate pool
   688  func (mh *MqttTransport) getCertPool(certFile string) (*x509.CertPool, error) {
   689  	certs := x509.NewCertPool()
   690  	pemData, err := ioutil.ReadFile(certFile)
   691  	if err != nil {
   692  		return nil, err
   693  	}
   694  	certs.AppendCertsFromPEM(pemData)
   695  	log.Infof("Certificate is loaded.")
   696  	return certs, nil
   697  }