func DeleteDeviceSession(ctx context.Context, devEUI lorawan.EUI64) error {
	key := GetRedisKey(deviceSessionKeyTempl, devEUI)
	val, err := RedisClient().Del(ctx, key).Result()
	if err != nil {
		return errors.Wrap(err, "delete error")
	}
	if val == 0 {
		return ErrDoesNotExist
	}
	log.WithFields(log.Fields{
		"dev_eui": devEUI,
		"ctx_id":  ctx.Value(logging.ContextIDKey),
	}).Info("device-session deleted")
	return nil
}
DeleteDeviceSession deletes the device-session matching the given DevEUI.
package storage
import (
	"context"
	"crypto/rand"
	"fmt"
	"strings"
	"time"
	"github.com/go-redis/redis/v8"
	"github.com/gofrs/uuid"
	proto "github.com/golang/protobuf/proto"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/brocaar/chirpstack-api/go/v3/common"
	"github.com/brocaar/chirpstack-network-server/v3/internal/band"
	"github.com/brocaar/chirpstack-network-server/v3/internal/logging"
	"github.com/brocaar/lorawan"
	loraband "github.com/brocaar/lorawan/band"
)
const (
	devAddrKeyTempl                = "lora:ns:devaddr:%s"      
	deviceSessionKeyTempl          = "lora:ns:device:%s"       
	deviceGatewayRXInfoSetKeyTempl = "lora:ns:device:%s:gwrx"  
)
const UplinkHistorySize = 20
type RXWindow int8
const (
	RX1 = iota
	RX2
)
type DeviceGatewayRXInfoSet struct {
	DevEUI lorawan.EUI64
	DR     int
	Items  []DeviceGatewayRXInfo
}
type DeviceGatewayRXInfo struct {
	GatewayID lorawan.EUI64
	RSSI      int
	LoRaSNR   float64
	Antenna   uint32
	Board     uint32
	Context   []byte
}
type UplinkHistory struct {
	FCnt         uint32
	MaxSNR       float64
	TXPowerIndex int
	GatewayCount int
}
type KeyEnvelope struct {
	KEKLabel string
	AESKey   []byte
}
type DeviceSession struct {
	MACVersion string
	DeviceProfileID  uuid.UUID
	ServiceProfileID uuid.UUID
	RoutingProfileID uuid.UUID
	DevAddr        lorawan.DevAddr
	DevEUI         lorawan.EUI64
	JoinEUI        lorawan.EUI64
	FNwkSIntKey    lorawan.AES128Key
	SNwkSIntKey    lorawan.AES128Key
	NwkSEncKey     lorawan.AES128Key
	AppSKeyEvelope *KeyEnvelope
	FCntUp         uint32
	NFCntDown      uint32
	AFCntDown      uint32
	ConfFCnt       uint32
	SkipFCntValidation bool
	RXWindow     RXWindow
	RXDelay      uint8
	RX1DROffset  uint8
	RX2DR        uint8
	RX2Frequency uint32
	TXPowerIndex int
	DR int
	ADR bool
	MinSupportedTXPowerIndex int
	MaxSupportedTXPowerIndex int
	NbTrans uint8
	EnabledChannels       []int                     
	EnabledUplinkChannels []int                     
	ExtraUplinkChannels   map[int]loraband.Channel  
	ChannelFrequencies    []uint32                  
	UplinkHistory         []UplinkHistory           
	LastDevStatusRequested time.Time
	BeaconLocked      bool
	PingSlotNb        int
	PingSlotDR        int
	PingSlotFrequency uint32
	RejoinRequestEnabled bool
	RejoinRequestMaxCountN int
	RejoinRequestMaxTimeN int
	RejoinCount0               uint16
	PendingRejoinDeviceSession *DeviceSession
	ReferenceAltitude float64
	UplinkDwellTime400ms   bool
	DownlinkDwellTime400ms bool
	UplinkMaxEIRPIndex uint8
	MACCommandErrorCount map[lorawan.CID]int
	IsDisabled bool
}
func (s *DeviceSession) AppendUplinkHistory(up UplinkHistory) {
	if count := len(s.UplinkHistory); count > 0 {
		if s.UplinkHistory[count-1].FCnt == up.FCnt {
			return
		}
	}
	s.UplinkHistory = append(s.UplinkHistory, up)
	if count := len(s.UplinkHistory); count > UplinkHistorySize {
		s.UplinkHistory = s.UplinkHistory[count-UplinkHistorySize : count]
	}
}
func (s DeviceSession) GetPacketLossPercentage() float64 {
	if len(s.UplinkHistory) < UplinkHistorySize {
		return 0
	}
	var lostPackets uint32
	var previousFCnt uint32
	for i, uh := range s.UplinkHistory {
		if i == 0 {
			previousFCnt = uh.FCnt
			continue
		}
		lostPackets += uh.FCnt - previousFCnt - 1  
		previousFCnt = uh.FCnt
	}
	return float64(lostPackets) / float64(len(s.UplinkHistory)) * 100
}
func (s DeviceSession) GetMACVersion() lorawan.MACVersion {
	if strings.HasPrefix(s.MACVersion, "1.1") {
		return lorawan.LoRaWAN1_1
	}
	return lorawan.LoRaWAN1_0
}
func (s *DeviceSession) ResetToBootParameters(dp DeviceProfile) {
	if dp.SupportsJoin {
		return
	}
	var channelFrequencies []uint32
	for _, f := range dp.FactoryPresetFreqs {
		channelFrequencies = append(channelFrequencies, f)
	}
	s.TXPowerIndex = 0
	s.MinSupportedTXPowerIndex = 0
	s.MaxSupportedTXPowerIndex = 0
	s.ExtraUplinkChannels = make(map[int]loraband.Channel)
	s.RXDelay = uint8(dp.RXDelay1)
	s.RX1DROffset = uint8(dp.RXDROffset1)
	s.RX2DR = uint8(dp.RXDataRate2)
	s.RX2Frequency = dp.RXFreq2
	s.EnabledUplinkChannels = band.Band().GetStandardUplinkChannelIndices()  
	s.ChannelFrequencies = channelFrequencies
	s.PingSlotDR = dp.PingSlotDR
	s.PingSlotFrequency = dp.PingSlotFreq
	s.NbTrans = 1
	if dp.PingSlotPeriod != 0 {
		s.PingSlotNb = (1 << 12) / dp.PingSlotPeriod
	}
	if len(dp.FactoryPresetFreqs) > len(s.EnabledUplinkChannels) {
		for _, f := range dp.FactoryPresetFreqs[len(s.EnabledUplinkChannels):] {
			i, err := band.Band().GetUplinkChannelIndex(f, false)
			if err != nil {
				continue
			}
			s.EnabledUplinkChannels = append(s.EnabledUplinkChannels, i)
			c, err := band.Band().GetUplinkChannel(i)
			if err != nil {
				continue
			}
			s.ExtraUplinkChannels[i] = c
		}
	}
}
func GetRandomDevAddr(netID lorawan.NetID) (lorawan.DevAddr, error) {
	var d lorawan.DevAddr
	b := make([]byte, len(d))
	if _, err := rand.Read(b); err != nil {
		return d, errors.Wrap(err, "read random bytes error")
	}
	copy(d[:], b)
	d.SetAddrPrefix(netID)
	return d, nil
}
func GetFullFCntUp(nextExpectedFullFCnt, truncatedFCntUp uint32) uint32 {
	if truncatedFCntUp == uint32(uint16(nextExpectedFullFCnt%(1<<16))-1) {
		return nextExpectedFullFCnt - 1
	}
	gap := uint32(uint16(truncatedFCntUp) - uint16(nextExpectedFullFCnt%(1<<16)))
	return nextExpectedFullFCnt + gap
}
func SaveDeviceSession(ctx context.Context, s DeviceSession) error {
	devAddrKey := GetRedisKey(devAddrKeyTempl, s.DevAddr)
	devSessKey := GetRedisKey(deviceSessionKeyTempl, s.DevEUI)
	dsPB := deviceSessionToPB(s)
	b, err := proto.Marshal(dsPB)
	if err != nil {
		return errors.Wrap(err, "protobuf encode error")
	}
	pipe := RedisClient().TxPipeline()
	pipe.SAdd(ctx, devAddrKey, s.DevEUI[:])
	pipe.PExpire(ctx, devAddrKey, deviceSessionTTL)
	if _, err := pipe.Exec(ctx); err != nil {
		return errors.Wrap(err, "exec error")
	}
	if s.PendingRejoinDeviceSession != nil {
		pendingDevAddrKey := GetRedisKey(devAddrKeyTempl, s.PendingRejoinDeviceSession.DevAddr)
		pipe = RedisClient().TxPipeline()
		pipe.SAdd(ctx, pendingDevAddrKey, s.DevEUI[:])
		pipe.PExpire(ctx, pendingDevAddrKey, deviceSessionTTL)
		if _, err := pipe.Exec(ctx); err != nil {
			return errors.Wrap(err, "exec error")
		}
	}
	err = RedisClient().Set(ctx, devSessKey, b, deviceSessionTTL).Err()
	if err != nil {
		return errors.Wrap(err, "set error")
	}
	log.WithFields(log.Fields{
		"dev_eui":  s.DevEUI,
		"dev_addr": s.DevAddr,
		"ctx_id":   ctx.Value(logging.ContextIDKey),
	}).Info("device-session saved")
	return nil
}
func GetDeviceSession(ctx context.Context, devEUI lorawan.EUI64) (DeviceSession, error) {
	key := GetRedisKey(deviceSessionKeyTempl, devEUI)
	var dsPB DeviceSessionPB
	val, err := RedisClient().Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return DeviceSession{}, ErrDoesNotExist
		}
		return DeviceSession{}, errors.Wrap(err, "get error")
	}
	err = proto.Unmarshal(val, &dsPB)
	if err != nil {
		return DeviceSession{}, errors.Wrap(err, "unmarshal protobuf error")
	}
	return deviceSessionFromPB(&dsPB), nil
}