func HandleDownlinkTXAcks(wg *sync.WaitGroup) {
	for downlinkTXAck := range gwbackend.Backend().DownlinkTXAckChan() {
		go func(downlinkTXAck gw.DownlinkTXAck) {
			wg.Add(1)
			defer wg.Done()
			var ctxID uuid.UUID
			if downlinkTXAck.DownlinkId != nil {
				copy(ctxID[:], downlinkTXAck.DownlinkId)
			}
			ctx := context.Background()
			ctx = context.WithValue(ctx, logging.ContextIDKey, ctxID)
			if err := ack.HandleDownlinkTXAck(ctx, &downlinkTXAck); err != nil {
				log.WithFields(log.Fields{
					"gateway_id": hex.EncodeToString(downlinkTXAck.GatewayId),
					"token":      downlinkTXAck.Token,
					"ctx_id":     ctxID,
				}).WithError(err).Error("uplink: handle downlink tx ack error")
			}
		}(downlinkTXAck)
	}
}
HandleDownlinkTXAcks consumes received downlink tx acknowledgements from
the gateway.
package uplink
import (
	"context"
	"encoding/hex"
	"fmt"
	"sync"
	"time"
	"github.com/gofrs/uuid"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/brocaar/chirpstack-api/go/v3/common"
	"github.com/brocaar/chirpstack-api/go/v3/gw"
	"github.com/brocaar/chirpstack-api/go/v3/nc"
	"github.com/brocaar/chirpstack-api/go/v3/ns"
	"github.com/brocaar/chirpstack-network-server/v3/internal/backend/controller"
	gwbackend "github.com/brocaar/chirpstack-network-server/v3/internal/backend/gateway"
	"github.com/brocaar/chirpstack-network-server/v3/internal/config"
	"github.com/brocaar/chirpstack-network-server/v3/internal/downlink/ack"
	"github.com/brocaar/chirpstack-network-server/v3/internal/framelog"
	"github.com/brocaar/chirpstack-network-server/v3/internal/gateway"
	"github.com/brocaar/chirpstack-network-server/v3/internal/helpers"
	"github.com/brocaar/chirpstack-network-server/v3/internal/logging"
	"github.com/brocaar/chirpstack-network-server/v3/internal/models"
	"github.com/brocaar/chirpstack-network-server/v3/internal/storage"
	"github.com/brocaar/chirpstack-network-server/v3/internal/uplink/data"
	"github.com/brocaar/chirpstack-network-server/v3/internal/uplink/join"
	"github.com/brocaar/chirpstack-network-server/v3/internal/uplink/proprietary"
	"github.com/brocaar/chirpstack-network-server/v3/internal/uplink/rejoin"
	"github.com/brocaar/lorawan"
)
var (
	deduplicationDelay time.Duration
)
func Setup(conf config.Config) error {
	if err := data.Setup(conf); err != nil {
		return errors.Wrap(err, "configure uplink/data error")
	}
	if err := join.Setup(conf); err != nil {
		return errors.Wrap(err, "configure uplink/join error")
	}
	if err := rejoin.Setup(conf); err != nil {
		return errors.Wrap(err, "configure uplink/rejoin error")
	}
	deduplicationDelay = conf.NetworkServer.DeduplicationDelay
	return nil
}
type Server struct {
	wg sync.WaitGroup
}
func NewServer() *Server {
	return &Server{}
}
func (s *Server) Start() error {
	go func() {
		s.wg.Add(1)
		defer s.wg.Done()
		HandleUplinkFrames(&s.wg)
	}()
	go func() {
		s.wg.Add(1)
		defer s.wg.Done()
		HandleDownlinkTXAcks(&s.wg)
	}()
	return nil
}
func (s *Server) Stop() error {
	if err := gwbackend.Backend().Close(); err != nil {
		return fmt.Errorf("close gateway backend error: %s", err)
	}
	log.Info("uplink: waiting for pending actions to complete")
	s.wg.Wait()
	return nil
}
func HandleUplinkFrames(wg *sync.WaitGroup) {
	for uplinkFrame := range gwbackend.Backend().RXPacketChan() {
		go func(uplinkFrame gw.UplinkFrame) {
			wg.Add(1)
			defer wg.Done()
			ctxID, err := uuid.NewV4()
			if err != nil {
				log.WithError(err).Error("uplink: get new uuid error")
			}
			ctx := context.Background()
			ctx = context.WithValue(ctx, logging.ContextIDKey, ctxID)
			if err := HandleUplinkFrame(ctx, uplinkFrame); err != nil {
				log.WithFields(log.Fields{
					"ctx_id": ctxID,
				}).WithError(err).Error("uplink: processing uplink frame error")
			}
		}(uplinkFrame)
	}
}
func HandleUplinkFrame(ctx context.Context, uplinkFrame gw.UplinkFrame) error {
	return collectUplinkFrames(ctx, uplinkFrame)
}