func (b *Backend) SendGatewayConfigPacket(configPacket gw.GatewayConfiguration) error {
	gatewayID := helpers.GetGatewayID(&configPacket)
	return b.publishCommand(log.Fields{}, gatewayID, "config", &configPacket)
}
SendGatewayConfigPacket sends the given GatewayConfigPacket to the gateway.
package mqtt
import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"strings"
	"sync"
	"text/template"
	"time"
	paho "github.com/eclipse/paho.mqtt.golang"
	"github.com/golang/protobuf/proto"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/brocaar/chirpstack-api/go/v3/gw"
	"github.com/brocaar/chirpstack-network-server/v3/internal/backend/gateway"
	"github.com/brocaar/chirpstack-network-server/v3/internal/backend/gateway/marshaler"
	"github.com/brocaar/chirpstack-network-server/v3/internal/config"
	"github.com/brocaar/chirpstack-network-server/v3/internal/helpers"
	"github.com/brocaar/chirpstack-network-server/v3/internal/storage"
	"github.com/brocaar/lorawan"
)
const deduplicationLockTTL = time.Millisecond * 500
const (
	marshalerV2JSON = iota
	marshalerProtobuf
	marshalerJSON
)
type Backend struct {
	sync.RWMutex
	wg sync.WaitGroup
	rxPacketChan      chan gw.UplinkFrame
	statsPacketChan   chan gw.GatewayStats
	downlinkTXAckChan chan gw.DownlinkTXAck
	conn                 paho.Client
	eventTopic           string
	commandTopicTemplate *template.Template
	qos                  uint8
	downMode         string
	gatewayMarshaler map[lorawan.EUI64]marshaler.Type
}
func NewBackend(c config.Config) (gateway.Gateway, error) {
	conf := c.NetworkServer.Gateway.Backend.MQTT
	var err error
	b := Backend{
		rxPacketChan:      make(chan gw.UplinkFrame),
		statsPacketChan:   make(chan gw.GatewayStats),
		downlinkTXAckChan: make(chan gw.DownlinkTXAck),
		gatewayMarshaler:  make(map[lorawan.EUI64]marshaler.Type),
		eventTopic:        conf.EventTopic,
		qos:               conf.QOS,
		downMode:          c.NetworkServer.Gateway.Backend.MultiDownlinkFeature,
	}
	b.commandTopicTemplate, err = template.New("command").Parse(conf.CommandTopicTemplate)
	if err != nil {
		return nil, errors.Wrap(err, "gateway/mqtt: parse command topic template error")
	}
	opts := paho.NewClientOptions()
	opts.AddBroker(conf.Server)
	opts.SetUsername(conf.Username)
	opts.SetPassword(conf.Password)
	opts.SetCleanSession(conf.CleanSession)
	opts.SetClientID(conf.ClientID)
	opts.SetOnConnectHandler(b.onConnected)
	opts.SetConnectionLostHandler(b.onConnectionLost)
	opts.SetMaxReconnectInterval(conf.MaxReconnectInterval)
	tlsconfig, err := newTLSConfig(conf.CACert, conf.TLSCert, conf.TLSKey)
	if err != nil {
		log.WithError(err).WithFields(log.Fields{
			"ca_cert":  conf.CACert,
			"tls_cert": conf.TLSCert,
			"tls_key":  conf.TLSKey,
		}).Fatal("gateway/mqtt: error loading mqtt certificate files")
	}
	if tlsconfig != nil {
		opts.SetTLSConfig(tlsconfig)
	}
	log.WithField("server", conf.Server).Info("gateway/mqtt: connecting to mqtt broker")
	b.conn = paho.NewClient(opts)
	for {
		if token := b.conn.Connect(); token.Wait() && token.Error() != nil {
			log.Errorf("gateway/mqtt: connecting to mqtt broker failed, will retry in 2s: %s", token.Error())
			time.Sleep(2 * time.Second)
		} else {
			break
		}
	}
	return &b, nil
}
func (b *Backend) Close() error {
	log.Info("gateway/mqtt: closing backend")
	log.WithField("topic", b.eventTopic).Info("gateway/mqtt: unsubscribing from event topic")
	if token := b.conn.Unsubscribe(b.eventTopic); token.Wait() && token.Error() != nil {
		return fmt.Errorf("gateway/mqtt: unsubscribe from %s error: %s", b.eventTopic, token.Error())
	}
	log.Info("backend/gateway: handling last messages")
	b.wg.Wait()
	close(b.rxPacketChan)
	close(b.statsPacketChan)
	close(b.downlinkTXAckChan)
	return nil
}
func (b *Backend) RXPacketChan() chan gw.UplinkFrame {
	return b.rxPacketChan
}
func (b *Backend) StatsPacketChan() chan gw.GatewayStats {
	return b.statsPacketChan
}
func (b *Backend) DownlinkTXAckChan() chan gw.DownlinkTXAck {
	return b.downlinkTXAckChan
}
func (b *Backend) SendTXPacket(txPacket gw.DownlinkFrame) error {
	gatewayID := helpers.GetGatewayID(&txPacket)
	downID := helpers.GetDownlinkID(&txPacket)
	if err := gateway.UpdateDownlinkFrame(b.downMode, &txPacket); err != nil {
		return errors.Wrap(err, "set downlink compatibility mode error")
	}
	return b.publishCommand(log.Fields{
		"downlink_id": downID,
	}, gatewayID, "down", &txPacket)
}