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 }