func (m MockNsJsServer) HandleJoin(ctx context.Context, req *ttnpb.JoinRequest) (*ttnpb.JoinResponse, error) {
	if m.HandleJoinFunc == nil {
		panic("HandleJoin called, but not set")
	}
	return m.HandleJoinFunc(ctx, req)
}
HandleJoin calls HandleJoinFunc if set and panics otherwise.
package networkserver
import (
	"bytes"
	"context"
	"fmt"
	"strings"
	"sync"
	"testing"
	"time"
	pbtypes "github.com/gogo/protobuf/types"
	"github.com/mohae/deepcopy"
	"github.com/smartystreets/assertions"
	clusterauth "go.thethings.network/lorawan-stack/v3/pkg/auth/cluster"
	"go.thethings.network/lorawan-stack/v3/pkg/cluster"
	"go.thethings.network/lorawan-stack/v3/pkg/component"
	componenttest "go.thethings.network/lorawan-stack/v3/pkg/component/test"
	"go.thethings.network/lorawan-stack/v3/pkg/encoding/lorawan"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/events"
	"go.thethings.network/lorawan-stack/v3/pkg/frequencyplans"
	"go.thethings.network/lorawan-stack/v3/pkg/log"
	. "go.thethings.network/lorawan-stack/v3/pkg/networkserver/internal"
	. "go.thethings.network/lorawan-stack/v3/pkg/networkserver/internal/test"
	"go.thethings.network/lorawan-stack/v3/pkg/networkserver/mac"
	"go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata"
	"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
	"go.thethings.network/lorawan-stack/v3/pkg/types"
	"go.thethings.network/lorawan-stack/v3/pkg/unique"
	"go.thethings.network/lorawan-stack/v3/pkg/util/test"
	"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
	"google.golang.org/grpc"
)
const (
	DownlinkProcessTaskName = downlinkProcessTaskName
	DownlinkRetryInterval   = downlinkRetryInterval
	InfrastructureDelay     = infrastructureDelay
	RecentDownlinkCount     = recentDownlinkCount
	RecentUplinkCount       = recentUplinkCount
)
var (
	AppendRecentDownlink                = appendRecentDownlink
	AppendRecentUplink                  = appendRecentUplink
	ApplicationJoinAcceptWithoutAppSKey = applicationJoinAcceptWithoutAppSKey
	ApplyCFList                         = applyCFList
	DownlinkPathsFromMetadata           = downlinkPathsFromMetadata
	JoinResponseWithoutKeys             = joinResponseWithoutKeys
	ErrABPJoinRequest             = errABPJoinRequest
	ErrApplicationDownlinkTooLong = errApplicationDownlinkTooLong
	ErrDecodePayload              = errDecodePayload
	ErrDeviceNotFound             = errDeviceNotFound
	ErrDuplicate                  = errDuplicate
	ErrInvalidAbsoluteTime        = errInvalidAbsoluteTime
	ErrOutdatedData               = errOutdatedData
	ErrRejoinRequest              = errRejoinRequest
	ErrUnsupportedLoRaWANVersion  = errUnsupportedLoRaWANVersion
	EvtClusterJoinAttempt          = evtClusterJoinAttempt
	EvtClusterJoinFail             = evtClusterJoinFail
	EvtClusterJoinSuccess          = evtClusterJoinSuccess
	EvtCreateEndDevice             = evtCreateEndDevice
	EvtDropDataUplink              = evtDropDataUplink
	EvtDropJoinRequest             = evtDropJoinRequest
	EvtForwardDataUplink           = evtForwardDataUplink
	EvtForwardJoinAccept           = evtForwardJoinAccept
	EvtInteropJoinAttempt          = evtInteropJoinAttempt
	EvtInteropJoinFail             = evtInteropJoinFail
	EvtInteropJoinSuccess          = evtInteropJoinSuccess
	EvtProcessDataUplink           = evtProcessDataUplink
	EvtProcessJoinRequest          = evtProcessJoinRequest
	EvtReceiveDataUplink           = evtReceiveDataUplink
	EvtReceiveJoinRequest          = evtReceiveJoinRequest
	EvtScheduleDataDownlinkAttempt = evtScheduleDataDownlinkAttempt
	EvtScheduleDataDownlinkFail    = evtScheduleDataDownlinkFail
	EvtScheduleDataDownlinkSuccess = evtScheduleDataDownlinkSuccess
	EvtScheduleJoinAcceptAttempt   = evtScheduleJoinAcceptAttempt
	EvtScheduleJoinAcceptFail      = evtScheduleJoinAcceptFail
	EvtScheduleJoinAcceptSuccess   = evtScheduleJoinAcceptSuccess
	EvtUpdateEndDevice             = evtUpdateEndDevice
	NewDeviceRegistry           func(context.Context) (DeviceRegistry, func())
	NewApplicationUplinkQueue   func(context.Context) (ApplicationUplinkQueue, func())
	NewDownlinkTaskQueue        func(context.Context) (DownlinkTaskQueue, func())
	NewUplinkDeduplicator       func(context.Context) (UplinkDeduplicator, func())
	NewScheduledDownlinkMatcher func(context.Context) (ScheduledDownlinkMatcher, func())
)
type DownlinkPath = downlinkPath
func NSScheduleWindow() time.Duration {
	return nsScheduleWindow()
}
var JoinRequestCorrelationIDs = [...]string{
	"join-request-correlation-id-1",
	"join-request-correlation-id-2",
	"join-request-correlation-id-3",
}
func MakeJoinRequestPHYPayload(joinEUI, devEUI types.EUI64, devNonce types.DevNonce, mic [4]byte) []byte {
	return []byte{
		0b000_000_00,
		joinEUI[7], joinEUI[6], joinEUI[5], joinEUI[4], joinEUI[3], joinEUI[2], joinEUI[1], joinEUI[0],
		devEUI[7], devEUI[6], devEUI[5], devEUI[4], devEUI[3], devEUI[2], devEUI[1], devEUI[0],
		devNonce[1], devNonce[0],
		mic[0], mic[1], mic[2], mic[3],
	}
}
func MakeJoinRequestDecodedPayload(joinEUI, devEUI types.EUI64, devNonce types.DevNonce, mic [4]byte) *ttnpb.Message {
	return &ttnpb.Message{
		MHDR: ttnpb.MHDR{
			MType: ttnpb.MType_JOIN_REQUEST,
			Major: ttnpb.Major_LORAWAN_R1,
		},
		Mic: CopyBytes(mic[:]),
		Payload: &ttnpb.Message_JoinRequestPayload{
			JoinRequestPayload: &ttnpb.JoinRequestPayload{
				JoinEui:  *joinEUI.Copy(&types.EUI64{}),
				DevEui:   *devEUI.Copy(&types.EUI64{}),
				DevNonce: deepcopy.Copy(devNonce).(types.DevNonce),
			},
		},
	}
}
type JoinRequestConfig struct {
	DecodePayload bool
	JoinEUI        types.EUI64
	DevEUI         types.EUI64
	DevNonce       types.DevNonce
	DataRate       ttnpb.DataRate
	DataRateIndex  ttnpb.DataRateIndex
	Frequency      uint64
	ChannelIndex   uint8
	ReceivedAt     time.Time
	RxMetadata     []*ttnpb.RxMetadata
	CorrelationIDs []string
	MIC            [4]byte
}
func MakeJoinRequest(conf JoinRequestConfig) *ttnpb.UplinkMessage {
	return MakeUplinkMessage(UplinkMessageConfig{
		RawPayload: MakeJoinRequestPHYPayload(conf.JoinEUI, conf.DevEUI, conf.DevNonce, conf.MIC),
		Payload: func() *ttnpb.Message {
			if conf.DecodePayload {
				return MakeJoinRequestDecodedPayload(conf.JoinEUI, conf.DevEUI, conf.DevNonce, conf.MIC)
			}
			return nil
		}(),
		DataRate:      conf.DataRate,
		DataRateIndex: conf.DataRateIndex,
		Frequency:     conf.Frequency,
		ChannelIndex:  conf.ChannelIndex,
		ReceivedAt:    conf.ReceivedAt,
		RxMetadata:    conf.RxMetadata,
		CorrelationIDs: func() []string {
			if len(conf.CorrelationIDs) == 0 {
				return JoinRequestCorrelationIDs[:]
			}
			return conf.CorrelationIDs
		}(),
	})
}
type NsJsJoinRequestConfig struct {
	JoinEUI            types.EUI64
	DevEUI             types.EUI64
	DevNonce           types.DevNonce
	MIC                [4]byte
	DevAddr            types.DevAddr
	SelectedMACVersion ttnpb.MACVersion
	NetID              types.NetID
	RX1DataRateOffset  ttnpb.DataRateOffset
	RX2DataRateIndex   ttnpb.DataRateIndex
	RXDelay            ttnpb.RxDelay
	FrequencyPlanID    string
	PHYVersion         ttnpb.PHYVersion
	CorrelationIDs     []string
}
func MakeNsJsJoinRequest(conf NsJsJoinRequestConfig) *ttnpb.JoinRequest {
	return &ttnpb.JoinRequest{
		RawPayload:         MakeJoinRequestPHYPayload(conf.JoinEUI, conf.DevEUI, conf.DevNonce, conf.MIC),
		Payload:            MakeJoinRequestDecodedPayload(conf.JoinEUI, conf.DevEUI, conf.DevNonce, conf.MIC),
		DevAddr:            *conf.DevAddr.Copy(&types.DevAddr{}),
		SelectedMacVersion: conf.SelectedMACVersion,
		NetId:              *conf.NetID.Copy(&types.NetID{}),
		DownlinkSettings: ttnpb.DLSettings{
			Rx1DrOffset: conf.RX1DataRateOffset,
			Rx2Dr:       conf.RX2DataRateIndex,
			OptNeg:      conf.SelectedMACVersion.Compare(ttnpb.MAC_V1_1) >= 0,
		},
		RxDelay: conf.RXDelay,
		CfList:  frequencyplans.CFList(*test.FrequencyPlan(conf.FrequencyPlanID), conf.PHYVersion),
		CorrelationIds: CopyStrings(func() []string {
			if len(conf.CorrelationIDs) == 0 {
				return JoinRequestCorrelationIDs[:]
			}
			return conf.CorrelationIDs
		}()),
	}
}
func NewISPeer(ctx context.Context, is interface {
	ttnpb.ApplicationAccessServer
}) cluster.Peer {
	return test.Must(test.NewGRPCServerPeer(ctx, is, ttnpb.RegisterApplicationAccessServer)).(cluster.Peer)
}
func NewGSPeer(ctx context.Context, gs interface {
	ttnpb.NsGsServer
}) cluster.Peer {
	return test.Must(test.NewGRPCServerPeer(ctx, gs, ttnpb.RegisterNsGsServer)).(cluster.Peer)
}
func NewJSPeer(ctx context.Context, js interface {
	ttnpb.NsJsServer
}) cluster.Peer {
	return test.Must(test.NewGRPCServerPeer(ctx, js, ttnpb.RegisterNsJsServer)).(cluster.Peer)
}
func NewASPeer(ctx context.Context, as interface {
	ttnpb.NsAsServer
}) cluster.Peer {
	return test.Must(test.NewGRPCServerPeer(ctx, as, ttnpb.RegisterNsAsServer)).(cluster.Peer)
}
var _ InteropClient = MockInteropClient{}
type MockInteropClient struct {
	HandleJoinRequestFunc func(context.Context, types.NetID, *ttnpb.JoinRequest) (*ttnpb.JoinResponse, error)
}
func (m MockInteropClient) HandleJoinRequest(ctx context.Context, netID types.NetID, req *ttnpb.JoinRequest) (*ttnpb.JoinResponse, error) {
	if m.HandleJoinRequestFunc == nil {
		panic("HandleJoinRequest called, but not set")
	}
	return m.HandleJoinRequestFunc(ctx, netID, req)
}
type InteropClientHandleJoinRequestResponse struct {
	Response *ttnpb.JoinResponse
	Error    error
}
type InteropClientHandleJoinRequestRequest struct {
	Context  context.Context
	NetID    types.NetID
	Request  *ttnpb.JoinRequest
	Response chan<- InteropClientHandleJoinRequestResponse
}
func MakeInteropClientHandleJoinRequestChFunc(reqCh chan<- InteropClientHandleJoinRequestRequest) func(context.Context, types.NetID, *ttnpb.JoinRequest) (*ttnpb.JoinResponse, error) {
	return func(ctx context.Context, netID types.NetID, msg *ttnpb.JoinRequest) (*ttnpb.JoinResponse, error) {
		respCh := make(chan InteropClientHandleJoinRequestResponse)
		reqCh <- InteropClientHandleJoinRequestRequest{
			Context:  ctx,
			NetID:    netID,
			Request:  msg,
			Response: respCh,
		}
		resp := <-respCh
		return resp.Response, resp.Error
	}
}
var _ ttnpb.NsJsServer = &MockNsJsServer{}
type MockNsJsServer struct {
	HandleJoinFunc  func(context.Context, *ttnpb.JoinRequest) (*ttnpb.JoinResponse, error)
	GetNwkSKeysFunc func(context.Context, *ttnpb.SessionKeyRequest) (*ttnpb.NwkSKeysResponse, error)
}