func (updf *UplinkDataFrame) FromUplinkMessage(up *ttnpb.UplinkMessage, bandID string) error {
	var payload ttnpb.Message
	err := lorawan.UnmarshalMessage(up.RawPayload, &payload)
	if err != nil {
		return errUplinkMessage.New()
	}
	updf.MHdr = (uint(payload.MHDR.GetMType()) << 5) | uint(payload.MHDR.GetMajor())
	macPayload := payload.GetMacPayload()
	if macPayload == nil {
		return errUplinkMessage.New()
	}
	updf.FPort = int(macPayload.GetFPort())
	updf.DevAddr = int32(macPayload.DevAddr.MarshalNumber())
	updf.FOpts = hex.EncodeToString(macPayload.GetFOpts())
	updf.FCtrl = util.GetFCtrlAsUint(macPayload.FCtrl)
	updf.FCnt = uint(macPayload.GetFCnt())
	updf.FRMPayload = hex.EncodeToString(macPayload.GetFrmPayload())
	updf.MIC = int32(binary.LittleEndian.Uint32(payload.Mic[:]))
	dr, err := util.GetDataRateIndexFromDataRate(bandID, up.Settings.GetDataRate())
	if err != nil {
		return err
	}
	rxMetadata := up.RxMetadata[0]
	var rxTime float64
	if rxMetadata.Time != nil {
		rxTime = float64(rxMetadata.Time.Unix()) + float64(rxMetadata.Time.Nanosecond())/(1e9)
	}
	updf.RadioMetaData = RadioMetaData{
		DataRate:  dr,
		Frequency: up.Settings.GetFrequency(),
		UpInfo: UpInfo{
			RCtx:   int64(rxMetadata.AntennaIndex),
			XTime:  int64(rxMetadata.Timestamp),
			RSSI:   rxMetadata.Rssi,
			SNR:    rxMetadata.Snr,
			RxTime: rxTime,
		},
	}
	return nil
}
FromUplinkMessage extracts fields from ttnpb.UplinkMessage and creates the LoRa Basics Station UplinkDataFrame.
package lbslns
import (
	"context"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"math"
	"time"
	"go.thethings.network/lorawan-stack/v3/pkg/basicstation"
	"go.thethings.network/lorawan-stack/v3/pkg/encoding/lorawan"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/gatewayserver/io"
	"go.thethings.network/lorawan-stack/v3/pkg/gatewayserver/io/ws"
	"go.thethings.network/lorawan-stack/v3/pkg/gatewayserver/io/ws/util"
	"go.thethings.network/lorawan-stack/v3/pkg/gatewayserver/scheduling"
	"go.thethings.network/lorawan-stack/v3/pkg/log"
	"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
	"go.thethings.network/lorawan-stack/v3/pkg/types"
)
var (
	errJoinRequestMessage = errors.Define("join_request_message", "invalid join-request message received")
	errUplinkDataFrame    = errors.Define("uplink_data_Frame", "invalid uplink data frame received")
	errUplinkMessage      = errors.Define("uplink_message", "invalid uplink message received")
	errMDHR               = errors.Define("mhdr", "invalid MHDR `{mhdr}` received")
)
type UpInfo struct {
	RxTime  float64 `json:"rxtime"`
	RCtx    int64   `json:"rtcx"`
	XTime   int64   `json:"xtime"`
	GPSTime int64   `json:"gpstime"`
	RSSI    float32 `json:"rssi"`
	SNR     float32 `json:"snr"`
}
type RadioMetaData struct {
	DataRate  int    `json:"DR"`
	Frequency uint64 `json:"Freq"`
	UpInfo    UpInfo `json:"upinfo"`
}
type JoinRequest struct {
	MHdr     uint             `json:"MHdr"`
	JoinEUI  basicstation.EUI `json:"JoinEui"`
	DevEUI   basicstation.EUI `json:"DevEui"`
	DevNonce uint             `json:"DevNonce"`
	MIC      int32            `json:"MIC"`
	RefTime  float64          `json:"RefTime"`
	RadioMetaData
}
func (req JoinRequest) MarshalJSON() ([]byte, error) {
	type Alias JoinRequest
	return json.Marshal(struct {
		Type string `json:"msgtype"`
		Alias
	}{
		Type:  TypeUpstreamJoinRequest,
		Alias: Alias(req),
	})
}
type UplinkDataFrame struct {
	MHdr       uint    `json:"MHdr"`
	DevAddr    int32   `json:"DevAddr"`
	FCtrl      uint    `json:"FCtrl"`
	FCnt       uint    `json:"Fcnt"`
	FOpts      string  `json:"FOpts"`
	FPort      int     `json:"FPort"`
	FRMPayload string  `json:"FRMPayload"`
	MIC        int32   `json:"MIC"`
	RefTime    float64 `json:"RefTime"`
	RadioMetaData
}
func (updf UplinkDataFrame) MarshalJSON() ([]byte, error) {
	type Alias UplinkDataFrame
	return json.Marshal(struct {
		Type string `json:"msgtype"`
		Alias
	}{
		Type:  TypeUpstreamUplinkDataFrame,
		Alias: Alias(updf),
	})
}
type TxConfirmation struct {
	Diid    int64            `json:"diid"`
	DevEUI  basicstation.EUI `json:"DevEui"`
	RCtx    int64            `json:"rctx"`
	XTime   int64            `json:"xtime"`
	TxTime  float64          `json:"txtime"`
	GpsTime int64            `json:"gpstime"`
	RefTime float64          `json:"RefTime,omitempty"`
}
func (conf TxConfirmation) MarshalJSON() ([]byte, error) {
	type Alias TxConfirmation
	return json.Marshal(struct {
		Type string `json:"msgtype"`
		Alias
	}{
		Type:  TypeUpstreamTxConfirmation,
		Alias: Alias(conf),
	})
}
func (req *JoinRequest) toUplinkMessage(ids ttnpb.GatewayIdentifiers, bandID string, receivedAt time.Time) (*ttnpb.UplinkMessage, error) {
	var up ttnpb.UplinkMessage
	up.ReceivedAt = receivedAt
	var parsedMHDR ttnpb.MHDR
	if err := lorawan.UnmarshalMHDR([]byte{byte(req.MHdr)}, &parsedMHDR); err != nil {
		return nil, errMDHR.WithAttributes(`mhdr`, parsedMHDR)
	}
	micBytes, err := util.GetInt32AsByteSlice(req.MIC)
	if err != nil {
		return nil, errJoinRequestMessage.WithCause(err)
	}
	up.Payload = &ttnpb.Message{
		MHDR: parsedMHDR,
		Mic:  micBytes,
		Payload: &ttnpb.Message_JoinRequestPayload{JoinRequestPayload: &ttnpb.JoinRequestPayload{
			JoinEui:  req.JoinEUI.EUI64,
			DevEui:   req.DevEUI.EUI64,
			DevNonce: [2]byte{byte(req.DevNonce >> 8), byte(req.DevNonce)},
		}},
	}
	up.RawPayload, err = lorawan.MarshalMessage(*up.Payload)
	if err != nil {
		return nil, errJoinRequestMessage.WithCause(err)
	}
	timestamp := uint32(req.RadioMetaData.UpInfo.XTime & 0xFFFFFFFF)
	var rxTime *time.Time
	sec, nsec := math.Modf(req.RadioMetaData.UpInfo.RxTime)
	if sec != 0 {
		val := time.Unix(int64(sec), int64(nsec*(1e9)))
		rxTime = &val
	}
	rxMetadata := &ttnpb.RxMetadata{
		GatewayIdentifiers: ids,
		Time:               rxTime,
		Timestamp:          timestamp,
		Rssi:               req.RadioMetaData.UpInfo.RSSI,
		ChannelRssi:        req.RadioMetaData.UpInfo.RSSI,
		Snr:                req.RadioMetaData.UpInfo.SNR,
		AntennaIndex:       uint32(req.RadioMetaData.UpInfo.RCtx),
	}
	up.RxMetadata = append(up.RxMetadata, rxMetadata)
	dataRate, codingRate, err := util.GetDataRateFromIndex(bandID, req.RadioMetaData.DataRate)
	if err != nil {
		return nil, errJoinRequestMessage.WithCause(err)
	}
	up.Settings = ttnpb.TxSettings{
		Frequency:  req.RadioMetaData.Frequency,
		DataRate:   dataRate,
		CodingRate: codingRate,
		Timestamp:  timestamp,
		Time:       rxTime,
	}
	return &up, nil
}
func (req *JoinRequest) FromUplinkMessage(up *ttnpb.UplinkMessage, bandID string) error {
	var payload ttnpb.Message
	err := lorawan.UnmarshalMessage(up.RawPayload, &payload)
	if err != nil {
		return errUplinkMessage.New()
	}
	req.MHdr = (uint(payload.MHDR.GetMType()) << 5) | uint(payload.MHDR.GetMajor())
	req.MIC = int32(binary.LittleEndian.Uint32(payload.Mic[:]))
	jreqPayload := payload.GetJoinRequestPayload()
	if jreqPayload == nil {
		return errUplinkMessage.New()
	}
	req.DevEUI = basicstation.EUI{
		EUI64: jreqPayload.DevEui,
	}
	req.JoinEUI = basicstation.EUI{
		EUI64: jreqPayload.JoinEui,
	}
	devNonce, err := jreqPayload.DevNonce.Marshal()
	if err != nil {
		return err
	}
	req.DevNonce = uint(binary.BigEndian.Uint16(devNonce[:]))
	dr, err := util.GetDataRateIndexFromDataRate(bandID, up.Settings.GetDataRate())
	if err != nil {
		return err
	}
	rxMetadata := up.RxMetadata[0]
	var rxTime float64
	if rxMetadata.Time != nil {
		rxTime = float64(rxMetadata.Time.Unix()) + float64(rxMetadata.Time.Nanosecond())/(1e9)
	}
	req.RadioMetaData = RadioMetaData{
		DataRate:  dr,
		Frequency: up.Settings.GetFrequency(),
		UpInfo: UpInfo{
			RCtx:   int64(rxMetadata.AntennaIndex),
			XTime:  int64(rxMetadata.Timestamp),
			RSSI:   rxMetadata.Rssi,
			SNR:    rxMetadata.Snr,
			RxTime: rxTime,
		},
	}
	return nil
}
func (updf *UplinkDataFrame) toUplinkMessage(ids ttnpb.GatewayIdentifiers, bandID string, receivedAt time.Time) (*ttnpb.UplinkMessage, error) {
	var up ttnpb.UplinkMessage
	up.ReceivedAt = receivedAt
	var parsedMHDR ttnpb.MHDR
	if err := lorawan.UnmarshalMHDR([]byte{byte(updf.MHdr)}, &parsedMHDR); err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	if parsedMHDR.MType != ttnpb.MType_UNCONFIRMED_UP && parsedMHDR.MType != ttnpb.MType_CONFIRMED_UP {
		return nil, errMDHR.WithAttributes(`mhdr`, parsedMHDR)
	}
	micBytes, err := util.GetInt32AsByteSlice(updf.MIC)
	if err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	var fPort uint32
	if updf.FPort == -1 {
		fPort = 0
	} else {
		fPort = uint32(updf.FPort)
	}
	var devAddr types.DevAddr
	devAddr.UnmarshalNumber(uint32(updf.DevAddr))
	var fctrl ttnpb.FCtrl
	if err := lorawan.UnmarshalFCtrl([]byte{byte(updf.FCtrl)}, &fctrl, true); err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	decFRMPayload, err := hex.DecodeString(updf.FRMPayload)
	if err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	decFOpts, err := hex.DecodeString(updf.FOpts)
	if err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	up.Payload = &ttnpb.Message{
		MHDR: parsedMHDR,
		Mic:  micBytes,
		Payload: &ttnpb.Message_MacPayload{MacPayload: &ttnpb.MACPayload{
			FPort:      fPort,
			FrmPayload: decFRMPayload,
			FHDR: ttnpb.FHDR{
				DevAddr: devAddr,
				FCtrl:   fctrl,
				FCnt:    uint32(updf.FCnt),
				FOpts:   decFOpts,
			},
		}},
	}
	up.RawPayload, err = lorawan.MarshalMessage(*up.Payload)
	if err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	timestamp := uint32(updf.RadioMetaData.UpInfo.XTime & 0xFFFFFFFF)
	var rxTime *time.Time
	sec, nsec := math.Modf(updf.RadioMetaData.UpInfo.RxTime)
	if sec != 0 {
		val := time.Unix(int64(sec), int64(nsec*(1e9)))
		rxTime = &val
	}
	rxMetadata := &ttnpb.RxMetadata{
		GatewayIdentifiers: ids,
		Time:               rxTime,
		Timestamp:          timestamp,
		Rssi:               updf.RadioMetaData.UpInfo.RSSI,
		ChannelRssi:        updf.RadioMetaData.UpInfo.RSSI,
		Snr:                updf.RadioMetaData.UpInfo.SNR,
		AntennaIndex:       uint32(updf.RadioMetaData.UpInfo.RCtx),
	}
	up.RxMetadata = append(up.RxMetadata, rxMetadata)
	dataRate, codingRate, err := util.GetDataRateFromIndex(bandID, updf.RadioMetaData.DataRate)
	if err != nil {
		return nil, errUplinkDataFrame.WithCause(err)
	}
	up.Settings = ttnpb.TxSettings{
		Frequency:  updf.RadioMetaData.Frequency,
		DataRate:   dataRate,
		CodingRate: codingRate,
		Timestamp:  timestamp,
		Time:       rxTime,
	}
	return &up, nil
}