From 79c3b8fe562e001e22f705cb3a0ef659519a9fe6 Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 27 Dec 2023 23:01:22 +0000 Subject: [PATCH] Add support for NGAP Handover Signed-off-by: Valentin --- cmd/packetrusher.go | 5 +- internal/common/tools/tools.go | 31 ++- .../gnb/context/context.go | 24 +- .../control_test_engine/gnb/context/msg.go | 1 + .../control_test_engine/gnb/context/ue.go | 9 + .../gnb/nas/service/service.go | 41 ++- .../gnb/ngap/dispatcher.go | 10 + .../gnb/ngap/handler/handler.go | 190 +++++++++++++ .../nas_transport/initial-ue-message.go | 20 +- .../nas_transport/uplink-nas-transport.go | 21 +- .../ue_mobility_management/handover-notify.go | 104 +++++++ .../handover-request-acknowledge.go | 174 ++++++++++++ .../handover-required.go | 257 ++++++++++++++++++ .../path-switch-request.go | 15 +- .../gnb/ngap/trigger/trigger.go | 65 ++++- .../ue/gtp/service/service.go | 4 + .../test-attach-ue-with-configuration.go | 2 +- internal/templates/test-multi-ues-in-queue.go | 7 +- test/pr_test.go | 6 +- 19 files changed, 920 insertions(+), 66 deletions(-) create mode 100644 internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-notify.go create mode 100644 internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-request-acknowledge.go create mode 100644 internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-required.go diff --git a/cmd/packetrusher.go b/cmd/packetrusher.go index e3ec9176..e076c516 100644 --- a/cmd/packetrusher.go +++ b/cmd/packetrusher.go @@ -89,7 +89,8 @@ func main() { &cli.IntFlag{Name: "number-of-ues", Value: 1, Aliases: []string{"n"}}, &cli.IntFlag{Name: "timeBetweenRegistration", Value: 500, Aliases: []string{"tr"}, Usage: "The time in ms, between UE registration."}, &cli.IntFlag{Name: "timeBeforeDeregistration", Value: 0, Aliases: []string{"td"}, Usage: "The time in ms, before a UE deregisters once it has been registered. 0 to disable auto-deregistration."}, - &cli.IntFlag{Name: "timeBeforeHandover", Value: 0, Aliases: []string{"th"}, Usage: "The time in ms, before triggering a UE handover. 0 to disable handover. This requires at least two gNodeB, eg: two N2/N3 IPs."}, + &cli.IntFlag{Name: "timeBeforeNgapHandover", Value: 0, Aliases: []string{"ngh"}, Usage: "The time in ms, before triggering a UE handover using NGAP Handover. 0 to disable handover. This requires at least two gNodeB, eg: two N2/N3 IPs."}, + &cli.IntFlag{Name: "timeBeforeXnHandover", Value: 0, Aliases: []string{"xnh"}, Usage: "The time in ms, before triggering a UE handover using Xn Handover. 0 to disable handover. This requires at least two gNodeB, eg: two N2/N3 IPs."}, &cli.IntFlag{Name: "numPduSessions", Value: 1, Aliases: []string{"nPdu"}, Usage: "The number of PDU Sessions to create"}, &cli.BoolFlag{Name: "loop", Aliases: []string{"l"}, Usage: "Enable the creation of the GTP-U tunnel interface."}, &cli.BoolFlag{Name: "tunnel", Aliases: []string{"t"}, Usage: "Enable the creation of the GTP-U tunnel interface."}, @@ -120,7 +121,7 @@ func main() { pcap.CaptureTraffic(c.Path("pcap")) } - templates.TestMultiUesInQueue(numUes, c.Bool("tunnel"), c.Bool("dedicatedGnb"), c.Bool("loop"), c.Int("timeBetweenRegistration"), c.Int("timeBeforeDeregistration"), c.Int("timeBeforeHandover"), c.Int("numPduSessions")) + templates.TestMultiUesInQueue(numUes, c.Bool("tunnel"), c.Bool("dedicatedGnb"), c.Bool("loop"), c.Int("timeBetweenRegistration"), c.Int("timeBeforeDeregistration"), c.Int("timeBeforeNgapHandover"), c.Int("timeBeforeXnHandover"), c.Int("numPduSessions")) return nil }, diff --git a/internal/common/tools/tools.go b/internal/common/tools/tools.go index 3b46c911..c2a2c1e0 100644 --- a/internal/common/tools/tools.go +++ b/internal/common/tools/tools.go @@ -103,7 +103,8 @@ type UESimulationConfig struct { Cfg config.Config ScenarioChan chan procedures.UeTesterMessage TimeBeforeDeregistration int - TimeBeforeHandover int + TimeBeforeNgapHandover int + TimeBeforeXnHandover int NumPduSessions int } @@ -113,7 +114,9 @@ func SimulateSingleUE(simConfig UESimulationConfig, wg *sync.WaitGroup) { ueCfg.Ue.Msin = IncrementMsin(simConfig.UeId, simConfig.Cfg.Ue.Msin) log.Info("[TESTER] TESTING REGISTRATION USING IMSI ", ueCfg.Ue.Msin, " UE") - gnbId := gnbIdGenerator(simConfig.UeId%numGnb, ueCfg.GNodeB.PlmnList.GnbId) + gnbIdGen := func(index int) string { + return gnbIdGenerator((simConfig.UeId+index)%numGnb, ueCfg.GNodeB.PlmnList.GnbId) + } // Launch a coroutine to handle UE's individual scenario go func(scenarioChan chan procedures.UeTesterMessage, ueId int) { @@ -123,7 +126,7 @@ func SimulateSingleUE(simConfig UESimulationConfig, wg *sync.WaitGroup) { // Create a new UE coroutine // ue.NewUE returns context of the new UE - ueTx := ue.NewUE(ueCfg, ueId, ueRx, simConfig.Gnbs[gnbId], wg) + ueTx := ue.NewUE(ueCfg, ueId, ueRx, simConfig.Gnbs[gnbIdGen(0)], wg) // We tell the UE to perform a registration ueRx <- procedures.UeTesterMessage{Type: procedures.Registration} @@ -132,11 +135,19 @@ func SimulateSingleUE(simConfig UESimulationConfig, wg *sync.WaitGroup) { if simConfig.TimeBeforeDeregistration != 0 { deregistrationChannel = time.After(time.Duration(simConfig.TimeBeforeDeregistration) * time.Millisecond) } - var handoverChannel <-chan time.Time = nil - if simConfig.TimeBeforeHandover != 0 { - handoverChannel = time.After(time.Duration(simConfig.TimeBeforeHandover) * time.Millisecond) + + nextHandoverId := 0 + var ngapHandoverChannel <-chan time.Time = nil + if simConfig.TimeBeforeNgapHandover != 0 { + ngapHandoverChannel = time.After(time.Duration(simConfig.TimeBeforeNgapHandover) * time.Millisecond) + } + var xnHandoverChannel <-chan time.Time = nil + if simConfig.TimeBeforeXnHandover != 0 { + xnHandoverChannel = time.After(time.Duration(simConfig.TimeBeforeXnHandover) * time.Millisecond) } + log.Error(simConfig.TimeBeforeNgapHandover) + log.Error(simConfig.TimeBeforeXnHandover) loop := true state := ueCtx.MM5G_NULL for loop { @@ -146,8 +157,12 @@ func SimulateSingleUE(simConfig UESimulationConfig, wg *sync.WaitGroup) { ueRx <- procedures.UeTesterMessage{Type: procedures.Terminate} ueRx = nil } - case <-handoverChannel: - trigger.TriggerHandover(simConfig.Gnbs[gnbId], simConfig.Gnbs[gnbIdGenerator((ueId+1)%numGnb, ueCfg.GNodeB.PlmnList.GnbId)], int64(ueId)) + case <-ngapHandoverChannel: + trigger.TriggerNgapHandover(simConfig.Gnbs[gnbIdGen(nextHandoverId)], simConfig.Gnbs[gnbIdGen(nextHandoverId+1)], int64(ueId)) + nextHandoverId++ + case <-xnHandoverChannel: + trigger.TriggerXnHandover(simConfig.Gnbs[gnbIdGen(nextHandoverId)], simConfig.Gnbs[gnbIdGen(nextHandoverId+1)], int64(ueId)) + nextHandoverId++ case msg := <-scenarioChan: if ueRx != nil { ueRx <- msg diff --git a/internal/control_test_engine/gnb/context/context.go b/internal/control_test_engine/gnb/context/context.go index 867bdb2a..4afe7ec5 100644 --- a/internal/control_test_engine/gnb/context/context.go +++ b/internal/control_test_engine/gnb/context/context.go @@ -9,6 +9,10 @@ import ( "fmt" "sync" + "github.com/free5gc/aper" + "github.com/free5gc/ngap/ngapConvert" + "github.com/free5gc/ngap/ngapType" + "github.com/free5gc/openapi/models" "github.com/ishidawataru/sctp" log "github.com/sirupsen/logrus" gtpv1 "github.com/wmnsk/go-gtp/gtpv1" @@ -99,7 +103,9 @@ func (gnb *GNBContext) NewGnBUe(gnbTx chan UEMessage, gnbRx chan UEMessage, prUe // store UE in the UE Pool of GNB. gnb.uePool.Store(ranId, ue) - gnb.prUePool.Store(prUeId, ue) + if prUeId != 0 { + gnb.prUePool.Store(prUeId, ue) + } // select AMF with Capacity is more than 0. amf := gnb.selectAmFByActive() @@ -399,6 +405,22 @@ func (gnb *GNBContext) GetSliceInBytes() ([]byte, []byte) { return sstBytes, nil } +func (gnb *GNBContext) GetPLMNIdentity() ngapType.PLMNIdentity { + return ngapConvert.PlmnIdToNgap(models.PlmnId{Mcc: gnb.controlInfo.mcc, Mnc: gnb.controlInfo.mnc}) +} + +func (gnb *GNBContext) GetNRCellIdentity() ngapType.NRCellIdentity { + nci := gnb.GetGnbIdInBytes() + var slice = make([]byte, 2) + + return ngapType.NRCellIdentity{ + Value: aper.BitString{ + Bytes: append(nci, slice...), + BitLength: 36, + }, + } +} + func (gnb *GNBContext) GetMccAndMnc() (string, string) { return gnb.controlInfo.mcc, gnb.controlInfo.mnc } diff --git a/internal/control_test_engine/gnb/context/msg.go b/internal/control_test_engine/gnb/context/msg.go index 91dd57a1..d80333f3 100644 --- a/internal/control_test_engine/gnb/context/msg.go +++ b/internal/control_test_engine/gnb/context/msg.go @@ -16,4 +16,5 @@ type UEMessage struct { Mcc string Mnc string UEContext *GNBUe + IsHandover bool } diff --git a/internal/control_test_engine/gnb/context/ue.go b/internal/control_test_engine/gnb/context/ue.go index 80b78e62..d9a51de2 100644 --- a/internal/control_test_engine/gnb/context/ue.go +++ b/internal/control_test_engine/gnb/context/ue.go @@ -29,6 +29,7 @@ type GNBUe struct { pRueId int64 // PacketRusher unique UE ID context Context lock sync.Mutex + newGnb *GNBContext } type Context struct { @@ -204,6 +205,14 @@ func (ue *GNBUe) SetStateDown() { ue.state = Down } +func (ue *GNBUe) SetHandoverGnodeB(gnb *GNBContext) { + ue.newGnb = gnb +} + +func (ue *GNBUe) GetHandoverGnodeB() *GNBContext { + return ue.newGnb +} + func (ue *GNBUe) GetGnbRx() chan UEMessage { return ue.gnbRx } diff --git a/internal/control_test_engine/gnb/nas/service/service.go b/internal/control_test_engine/gnb/nas/service/service.go index 88c2b853..6dbe9607 100644 --- a/internal/control_test_engine/gnb/nas/service/service.go +++ b/internal/control_test_engine/gnb/nas/service/service.go @@ -1,12 +1,14 @@ /** * SPDX-License-Identifier: Apache-2.0 * © Copyright 2023 Hewlett Packard Enterprise Development LP + * © Copyright 2023 Valentin D'Emmanuele */ package service import ( "my5G-RANTester/internal/control_test_engine/gnb/context" "my5G-RANTester/internal/control_test_engine/gnb/nas" + "my5G-RANTester/internal/control_test_engine/gnb/nas/message/sender" "my5G-RANTester/internal/control_test_engine/gnb/ngap/trigger" log "github.com/sirupsen/logrus" @@ -29,17 +31,36 @@ func gnbListen(gnb *context.GNBContext) { // store UE connection // select AMF and get sctp association // make a tun interface - ue := gnb.NewGnBUe(message.GNBTx, message.GNBRx, message.PrUeId) - if message.UEContext == nil { - log.Info("[GNB] Received incoming connection from new UE") - mcc, mnc := gnb.GetMccAndMnc() - message.GNBTx <- context.UEMessage{Mcc: mcc, Mnc: mnc} - ue.SetPduSessions(message.GNBPduSessions) - } else { - log.Info("[GNB] Received incoming handover for UE from another gNodeB") + ue, _ := gnb.GetGnbUeByPrUeId(message.PrUeId) + if ue != nil && message.IsHandover { + // We already have a context for this UE since it was sent to us by the AMF from a NGAP Handover + // Notify the AMF that the UE has succesfully been handed over to US + ue.SetGnbRx(message.GNBRx) + ue.SetGnbTx(message.GNBTx) + + // We enable the new PDU Session handed over to us + msg := context.UEMessage{GNBPduSessions: ue.GetPduSessions(), GnbIp: gnb.GetN3GnbIp()} + sender.SendMessageToUe(ue, msg) + ue.SetStateReady() - ue.CopyFromPreviousContext(message.UEContext) - trigger.SendPathSwitchRequest(gnb, ue) + + trigger.SendHandoverNotify(gnb, ue) + } else { + ue = gnb.NewGnBUe(message.GNBTx, message.GNBRx, message.PrUeId) + if message.UEContext != nil && message.IsHandover { + // Xn Handover + log.Info("[GNB] Received incoming handover for UE from another gNodeB") + ue.SetStateReady() + ue.CopyFromPreviousContext(message.UEContext) + trigger.SendPathSwitchRequest(gnb, ue) + + } else { + // Usual first UE connection to a gNodeB + log.Info("[GNB] Received incoming connection from new UE") + mcc, mnc := gnb.GetMccAndMnc() + message.GNBTx <- context.UEMessage{Mcc: mcc, Mnc: mnc} + ue.SetPduSessions(message.GNBPduSessions) + } } if ue == nil { diff --git a/internal/control_test_engine/gnb/ngap/dispatcher.go b/internal/control_test_engine/gnb/ngap/dispatcher.go index a772e04a..b52267a6 100644 --- a/internal/control_test_engine/gnb/ngap/dispatcher.go +++ b/internal/control_test_engine/gnb/ngap/dispatcher.go @@ -66,6 +66,11 @@ func Dispatch(amf *context.GNBAmf, gnb *context.GNBContext, message []byte) { log.Info("[GNB][NGAP] Receive AMF Configuration Update") handler.HandlerAmfConfigurationUpdate(amf, gnb, ngapMsg) + case ngapType.ProcedureCodeHandoverResourceAllocation: + // handler NGAP Handover REquest + log.Info("[GNB][NGAP] Receive Handover Request") + handler.HandlerHandoverRequest(amf, gnb, ngapMsg) + case ngapType.ProcedureCodeErrorIndication: // handler Error Indicator log.Error("[GNB][NGAP] Receive Error Indication") @@ -89,6 +94,11 @@ func Dispatch(amf *context.GNBAmf, gnb *context.GNBContext, message []byte) { log.Info("[GNB][NGAP] Receive PathSwitchRequestAcknowledge") handler.HandlerPathSwitchRequestAcknowledge(gnb, ngapMsg) + case ngapType.ProcedureCodeHandoverPreparation: + // handler NGAP AMF Handover Command + log.Info("[GNB][NGAP] Receive Handover Command") + handler.HandlerHandoverCommand(amf, gnb, ngapMsg) + default: log.Info("[GNB][NGAP] Received unknown NGAP message") } diff --git a/internal/control_test_engine/gnb/ngap/handler/handler.go b/internal/control_test_engine/gnb/ngap/handler/handler.go index 6076de59..c2fc79fc 100644 --- a/internal/control_test_engine/gnb/ngap/handler/handler.go +++ b/internal/control_test_engine/gnb/ngap/handler/handler.go @@ -1,6 +1,7 @@ /** * SPDX-License-Identifier: Apache-2.0 * © Copyright 2023 Hewlett Packard Enterprise Development LP + * © Copyright 2023 Valentin D'Emmanuele */ package handler @@ -687,6 +688,195 @@ func HandlerPathSwitchRequestAcknowledge(gnb *context.GNBContext, message *ngapT log.Info("[GNB] Handover completed successfully for UE ", ue.GetRanUeId()) } +func HandlerHandoverRequest(amf *context.GNBAmf, gnb *context.GNBContext, message *ngapType.NGAPPDU) { + var ueSecurityCapabilities *ngapType.UESecurityCapabilities + var sst []string + var sd []string + var maskedImeisv string + var sourceToTargetContainer *ngapType.SourceToTargetTransparentContainer + var pDUSessionResourceSetupListHOReq *ngapType.PDUSessionResourceSetupListHOReq + var amfUeId int64 + + valueMessage := message.InitiatingMessage.Value.HandoverRequest + + for _, ies := range valueMessage.ProtocolIEs.List { + switch ies.Id.Value { + + case ngapType.ProtocolIEIDAMFUENGAPID: + if ies.Value.AMFUENGAPID == nil { + log.Fatal("[GNB][NGAP] AMF UE ID is missing") + } + amfUeId = ies.Value.AMFUENGAPID.Value + + case ngapType.ProtocolIEIDAllowedNSSAI: + if ies.Value.AllowedNSSAI == nil { + log.Fatal("[GNB][NGAP] Allowed NSSAI is missing") + } + + valor := len(ies.Value.AllowedNSSAI.List) + sst = make([]string, valor) + sd = make([]string, valor) + + // list S-NSSAI(Single – Network Slice Selection Assistance Information). + for i, items := range ies.Value.AllowedNSSAI.List { + + if items.SNSSAI.SST.Value != nil { + sst[i] = fmt.Sprintf("%x", items.SNSSAI.SST.Value) + } else { + sst[i] = "not informed" + } + + if items.SNSSAI.SD != nil { + sd[i] = fmt.Sprintf("%x", items.SNSSAI.SD.Value) + } else { + sd[i] = "not informed" + } + } + + case ngapType.ProtocolIEIDMaskedIMEISV: + // that field is not mandatory. + // TODO using for mapping UE context + if ies.Value.MaskedIMEISV == nil { + log.Info("[GNB][NGAP] Masked IMEISV is missing") + maskedImeisv = "not informed" + } else { + maskedImeisv = fmt.Sprintf("%x", ies.Value.MaskedIMEISV.Value.Bytes) + } + + case ngapType.ProtocolIEIDPDUSessionResourceSwitchedList: + ueSecurityCapabilities = ies.Value.UESecurityCapabilities + if ueSecurityCapabilities == nil { + log.Fatal("[GNB][NGAP] UESecurityCapabilities is missing") + // TODO SEND ERROR INDICATION + } + + case ngapType.ProtocolIEIDSourceToTargetTransparentContainer: + sourceToTargetContainer = ies.Value.SourceToTargetTransparentContainer + if sourceToTargetContainer == nil { + log.Fatal("[GNB][NGAP] sourceToTargetContainer is missing") + // TODO SEND ERROR INDICATION + } + + case ngapType.ProtocolIEIDPDUSessionResourceSetupListHOReq: + pDUSessionResourceSetupListHOReq = ies.Value.PDUSessionResourceSetupListHOReq + if pDUSessionResourceSetupListHOReq == nil { + log.Fatal("[GNB][NGAP] pDUSessionResourceSetupListHOReq is missing") + // TODO SEND ERROR INDICATION + } + + case ngapType.ProtocolIEIDUESecurityCapabilities: + if ies.Value.UESecurityCapabilities == nil { + log.Fatal("[GNB][NGAP] UE Security Capabilities is missing") + } + ueSecurityCapabilities = ies.Value.UESecurityCapabilities + } + } + + if sourceToTargetContainer == nil { + log.Error("[GNB] HandoverRequest message from AMF is missing mandatory SourceToTargetTransparentContainer") + return + } + + sourceToTargetContainerBytes := sourceToTargetContainer.Value + sourceToTargetContainerNgap := &ngapType.SourceNGRANNodeToTargetNGRANNodeTransparentContainer{} + err := aper.UnmarshalWithParams(sourceToTargetContainerBytes, sourceToTargetContainerNgap, "valueExt") + if err != nil { + log.Error("[GNB] Unable to unmarshall SourceToTargetTransparentContainer: ", err) + return + } + if sourceToTargetContainerNgap.IndexToRFSP == nil { + log.Error("[GNB] SourceToTargetTransparentContainer from source gNodeB is missing IndexToRFSP") + return + } + prUeId := sourceToTargetContainerNgap.IndexToRFSP.Value + + ue := gnb.NewGnBUe(nil, nil, prUeId) + ue.SetAmfUeId(amfUeId) + + ue.CreateUeContext("not informed", maskedImeisv, sst, sd, ueSecurityCapabilities) + + for _, pDUSessionResourceSetupItemHOReq := range pDUSessionResourceSetupListHOReq.List { + pduSessionId := pDUSessionResourceSetupItemHOReq.PDUSessionID.Value + sst := fmt.Sprintf("%x", pDUSessionResourceSetupItemHOReq.SNSSAI.SST.Value) + sd := "not informed" + if pDUSessionResourceSetupItemHOReq.SNSSAI.SD != nil { + sd = fmt.Sprintf("%x", pDUSessionResourceSetupItemHOReq.SNSSAI.SD.Value) + } + + handOverRequestTransferBytes := pDUSessionResourceSetupItemHOReq.HandoverRequestTransfer + handOverRequestTransfer := &ngapType.PDUSessionResourceSetupRequestTransfer{} + err := aper.UnmarshalWithParams(handOverRequestTransferBytes, handOverRequestTransfer, "valueExt") + if err != nil { + log.Error("[GNB] Unable to unmarshall HandOverRequestTransfer: ", err) + continue + } + + var gtpTunnel *ngapType.GTPTunnel + var upfIp string + var teidUplink aper.OctetString + for _, ie := range handOverRequestTransfer.ProtocolIEs.List { + switch ie.Id.Value { + + case ngapType.ProtocolIEIDULNGUUPTNLInformation: + uLNGUUPTNLInformation := ie.Value.ULNGUUPTNLInformation + + gtpTunnel = uLNGUUPTNLInformation.GTPTunnel + upfIp, _ = ngapConvert.IPAddressToString(gtpTunnel.TransportLayerAddress) + teidUplink = gtpTunnel.GTPTEID.Value + } + } + + _, err = ue.CreatePduSession(pduSessionId, upfIp, sst, sd, 0, 1, 0, 0, binary.BigEndian.Uint32(teidUplink), gnb.GetUeTeid(ue)) + if err != nil { + log.Error("[GNB] ", err) + } + } + + trigger.SendHandoverRequestAcknowledge(gnb, ue) +} + +func HandlerHandoverCommand(amf *context.GNBAmf, gnb *context.GNBContext, message *ngapType.NGAPPDU) { + valueMessage := message.SuccessfulOutcome.Value.HandoverCommand + + var amfUeId, ranUeId int64 + + for _, ies := range valueMessage.ProtocolIEs.List { + switch ies.Id.Value { + + case ngapType.ProtocolIEIDAMFUENGAPID: + + if ies.Value.AMFUENGAPID == nil { + log.Fatal("[GNB][NGAP] AMF UE ID is missing") + } + amfUeId = ies.Value.AMFUENGAPID.Value + + case ngapType.ProtocolIEIDRANUENGAPID: + + if ies.Value.RANUENGAPID == nil { + log.Fatal("[GNB][NGAP] RAN UE ID is missing") + // TODO SEND ERROR INDICATION + } + ranUeId = ies.Value.RANUENGAPID.Value + } + + } + ue := getUeFromContext(gnb, ranUeId, amfUeId) + newGnb := ue.GetHandoverGnodeB() + if newGnb == nil { + log.Error("[GNB] AMF is sending a Handover Command for an UE we did not send a Handover Required message") + // TODO SEND ERROR INDICATION + return + } + + newGnbRx := make(chan context.UEMessage, 1) + newGnbTx := make(chan context.UEMessage, 1) + newGnb.GetInboundChannel() <- context.UEMessage{GNBRx: newGnbRx, GNBTx: newGnbTx, PrUeId: ue.GetPrUeId(), IsHandover: true} + + msg := context.UEMessage{GNBRx: newGnbRx, GNBTx: newGnbTx} + + sender.SendMessageToUe(ue, msg) +} + func HandlerErrorIndication(gnb *context.GNBContext, message *ngapType.NGAPPDU) { valueMessage := message.InitiatingMessage.Value.ErrorIndication diff --git a/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/initial-ue-message.go b/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/initial-ue-message.go index ff28b1e4..91998f03 100644 --- a/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/initial-ue-message.go +++ b/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/initial-ue-message.go @@ -23,12 +23,12 @@ func init() { TestPlmn.Value = aper.OctetString("\x02\xf8\x39") } -func GetInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string, plmn []byte, tac []byte) ([]byte, error) { - message := BuildInitialUEMessage(ranUeNgapID, nasPdu, fiveGSTmsi, plmn, tac) +func GetInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string, gnb *context.GNBContext) ([]byte, error) { + message := BuildInitialUEMessage(ranUeNgapID, nasPdu, fiveGSTmsi, gnb) return ngap.Encoder(message) } -func BuildInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string, plmn []byte, tac []byte) (pdu ngapType.NGAPPDU) { +func BuildInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string, gnb *context.GNBContext) (pdu ngapType.NGAPPDU) { pdu.Present = ngapType.NGAPPDUPresentInitiatingMessage pdu.InitiatingMessage = new(ngapType.InitiatingMessage) @@ -80,15 +80,11 @@ func BuildInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string, userLocationInformation.UserLocationInformationNR = new(ngapType.UserLocationInformationNR) userLocationInformationNR := userLocationInformation.UserLocationInformationNR - userLocationInformationNR.NRCGI.PLMNIdentity.Value = plmn - userLocationInformationNR.NRCGI.NRCellIdentity.Value = aper.BitString{ - Bytes: []byte{0x00, 0x00, 0x00, 0x00, 0x10}, - BitLength: 36, - } + userLocationInformationNR.NRCGI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.NRCGI.NRCellIdentity = gnb.GetNRCellIdentity() - userLocationInformationNR.TAI.PLMNIdentity.Value = plmn - // userLocationInformationNR.TAI.TAC.Value = aper.OctetString("\x00\x00\x01") - userLocationInformationNR.TAI.TAC.Value = tac + userLocationInformationNR.TAI.PLMNIdentity.Value = gnb.GetMccAndMncInOctets() + userLocationInformationNR.TAI.TAC.Value = gnb.GetTacInBytes() initialUEMessageIEs.List = append(initialUEMessageIEs.List, ie) @@ -144,7 +140,7 @@ func BuildInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string, } func SendInitialUeMessage(registrationRequest []byte, ue *context.GNBUe, gnb *context.GNBContext) ([]byte, error) { - sendMsg, err := GetInitialUEMessage(ue.GetRanUeId(), registrationRequest, "", gnb.GetMccAndMncInOctets(), gnb.GetTacInBytes()) + sendMsg, err := GetInitialUEMessage(ue.GetRanUeId(), registrationRequest, "", gnb) if err != nil { return nil, fmt.Errorf("Error in %d ue initial message", ue.GetRanUeId()) } diff --git a/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/uplink-nas-transport.go b/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/uplink-nas-transport.go index bd0e2ecd..0f0c7640 100644 --- a/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/uplink-nas-transport.go +++ b/internal/control_test_engine/gnb/ngap/message/ngap_control/nas_transport/uplink-nas-transport.go @@ -10,17 +10,15 @@ import ( "github.com/free5gc/ngap" - "github.com/free5gc/aper" - "github.com/free5gc/ngap/ngapType" ) -func getUplinkNASTransport(amfUeNgapID, ranUeNgapID int64, nasPdu []byte, plmn []byte, tac []byte) ([]byte, error) { - message := buildUplinkNasTransport(amfUeNgapID, ranUeNgapID, nasPdu, plmn, tac) +func getUplinkNASTransport(amfUeNgapID, ranUeNgapID int64, nasPdu []byte, gnb *context.GNBContext) ([]byte, error) { + message := buildUplinkNasTransport(amfUeNgapID, ranUeNgapID, nasPdu, gnb) return ngap.Encoder(message) } -func buildUplinkNasTransport(amfUeNgapID, ranUeNgapID int64, nasPdu []byte, plmn []byte, tac []byte) (pdu ngapType.NGAPPDU) { +func buildUplinkNasTransport(amfUeNgapID, ranUeNgapID int64, nasPdu []byte, gnb *context.GNBContext) (pdu ngapType.NGAPPDU) { pdu.Present = ngapType.NGAPPDUPresentInitiatingMessage pdu.InitiatingMessage = new(ngapType.InitiatingMessage) @@ -84,14 +82,11 @@ func buildUplinkNasTransport(amfUeNgapID, ranUeNgapID int64, nasPdu []byte, plmn userLocationInformation.UserLocationInformationNR = new(ngapType.UserLocationInformationNR) userLocationInformationNR := userLocationInformation.UserLocationInformationNR - userLocationInformationNR.NRCGI.PLMNIdentity.Value = plmn - userLocationInformationNR.NRCGI.NRCellIdentity.Value = aper.BitString{ - Bytes: []byte{0x00, 0x00, 0x00, 0x00, 0x10}, - BitLength: 36, - } + userLocationInformationNR.NRCGI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.NRCGI.NRCellIdentity = gnb.GetNRCellIdentity() - userLocationInformationNR.TAI.PLMNIdentity.Value = plmn - userLocationInformationNR.TAI.TAC.Value = tac + userLocationInformationNR.TAI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.TAI.TAC.Value = gnb.GetTacInBytes() uplinkNasTransportIEs.List = append(uplinkNasTransportIEs.List, ie) @@ -100,7 +95,7 @@ func buildUplinkNasTransport(amfUeNgapID, ranUeNgapID int64, nasPdu []byte, plmn func SendUplinkNasTransport(message []byte, ue *context.GNBUe, gnb *context.GNBContext) ([]byte, error) { - sendMsg, err := getUplinkNASTransport(ue.GetAmfUeId(), ue.GetRanUeId(), message, gnb.GetMccAndMncInOctets(), gnb.GetTacInBytes()) + sendMsg, err := getUplinkNASTransport(ue.GetAmfUeId(), ue.GetRanUeId(), message, gnb) if err != nil { return nil, fmt.Errorf("Error getting UE Id %d NAS Authentication Response", ue.GetRanUeId()) } diff --git a/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-notify.go b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-notify.go new file mode 100644 index 00000000..4da1f571 --- /dev/null +++ b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-notify.go @@ -0,0 +1,104 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2023 Valentin D'Emmanuele + */ +package ue_mobility_management + +import ( + "my5G-RANTester/internal/control_test_engine/gnb/context" + + "github.com/free5gc/ngap" + + "github.com/free5gc/ngap/ngapType" +) + +type HandoverNotifyBuilder struct { + pdu ngapType.NGAPPDU + ies *ngapType.ProtocolIEContainerHandoverNotifyIEs +} + +func HandoverNotify(gnb *context.GNBContext, ue *context.GNBUe) ([]byte, error) { + return NewHandoverNotifyBuilder(). + SetAmfUeNgapId(ue.GetAmfUeId()).SetRanUeNgapId(ue.GetRanUeId()). + SetUserLocation(gnb). + Build() +} + +func NewHandoverNotifyBuilder() *HandoverNotifyBuilder { + pdu := ngapType.NGAPPDU{} + + pdu.Present = ngapType.NGAPPDUPresentInitiatingMessage + pdu.InitiatingMessage = new(ngapType.InitiatingMessage) + + initiatingMessage := pdu.InitiatingMessage + initiatingMessage.ProcedureCode.Value = ngapType.ProcedureCodeHandoverNotification + initiatingMessage.Criticality.Value = ngapType.CriticalityPresentReject + + initiatingMessage.Value.Present = ngapType.InitiatingMessagePresentHandoverNotify + initiatingMessage.Value.HandoverNotify = new(ngapType.HandoverNotify) + + handoverNotify := initiatingMessage.Value.HandoverNotify + ies := &handoverNotify.ProtocolIEs + + return &HandoverNotifyBuilder{pdu, ies} +} + +func (builder *HandoverNotifyBuilder) SetAmfUeNgapId(amfUeNgapID int64) *HandoverNotifyBuilder { + // AMF UE NGAP ID + ie := ngapType.HandoverNotifyIEs{} + ie.Id.Value = ngapType.ProtocolIEIDAMFUENGAPID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentAMFUENGAPID + ie.Value.AMFUENGAPID = new(ngapType.AMFUENGAPID) + + aMFUENGAPID := ie.Value.AMFUENGAPID + aMFUENGAPID.Value = amfUeNgapID + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverNotifyBuilder) SetRanUeNgapId(ranUeNgapID int64) *HandoverNotifyBuilder { + // RAN UE NGAP ID + ie := ngapType.HandoverNotifyIEs{} + ie.Id.Value = ngapType.ProtocolIEIDRANUENGAPID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentRANUENGAPID + ie.Value.RANUENGAPID = new(ngapType.RANUENGAPID) + + rANUENGAPID := ie.Value.RANUENGAPID + rANUENGAPID.Value = ranUeNgapID + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverNotifyBuilder) SetUserLocation(gnb *context.GNBContext) *HandoverNotifyBuilder { + // User Location Information + ie := ngapType.HandoverNotifyIEs{} + ie.Id.Value = ngapType.ProtocolIEIDUserLocationInformation + ie.Criticality.Value = ngapType.CriticalityPresentIgnore + ie.Value.Present = ngapType.HandoverNotifyIEsPresentUserLocationInformation + ie.Value.UserLocationInformation = new(ngapType.UserLocationInformation) + + userLocationInformation := ie.Value.UserLocationInformation + userLocationInformation.Present = ngapType.UserLocationInformationPresentUserLocationInformationNR + userLocationInformation.UserLocationInformationNR = new(ngapType.UserLocationInformationNR) + + userLocationInformationNR := userLocationInformation.UserLocationInformationNR + userLocationInformationNR.NRCGI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.NRCGI.NRCellIdentity = gnb.GetNRCellIdentity() + + userLocationInformationNR.TAI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.TAI.TAC.Value = gnb.GetTacInBytes() + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverNotifyBuilder) Build() ([]byte, error) { + return ngap.Encoder(builder.pdu) +} diff --git a/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-request-acknowledge.go b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-request-acknowledge.go new file mode 100644 index 00000000..031291e1 --- /dev/null +++ b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-request-acknowledge.go @@ -0,0 +1,174 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2023 Valentin D'Emmanuele + */ +package ue_mobility_management + +import ( + "encoding/binary" + "my5G-RANTester/internal/control_test_engine/gnb/context" + + "github.com/free5gc/aper" + "github.com/free5gc/ngap" + log "github.com/sirupsen/logrus" + + "github.com/free5gc/ngap/ngapConvert" + "github.com/free5gc/ngap/ngapType" +) + +type HandoverRequestAcknowledgeBuilder struct { + pdu ngapType.NGAPPDU + ies *ngapType.ProtocolIEContainerHandoverRequestAcknowledgeIEs +} + +func HandoverRequestAcknowledge(gnb *context.GNBContext, ue *context.GNBUe) ([]byte, error) { + return NewHandoverRequestAcknowledgeBuilder(). + SetAmfUeNgapId(ue.GetAmfUeId()).SetRanUeNgapId(ue.GetRanUeId()). + SetPduSessionResourceAdmittedList(gnb, ue.GetPduSessions()). + SetTargetToSourceContainer(). + Build() +} + +func NewHandoverRequestAcknowledgeBuilder() *HandoverRequestAcknowledgeBuilder { + pdu := ngapType.NGAPPDU{} + + pdu.Present = ngapType.NGAPPDUPresentSuccessfulOutcome + pdu.SuccessfulOutcome = new(ngapType.SuccessfulOutcome) + + successfulOutcome := pdu.SuccessfulOutcome + successfulOutcome.ProcedureCode.Value = ngapType.ProcedureCodeHandoverResourceAllocation + successfulOutcome.Criticality.Value = ngapType.CriticalityPresentReject + + successfulOutcome.Value.Present = ngapType.SuccessfulOutcomePresentHandoverRequestAcknowledge + successfulOutcome.Value.HandoverRequestAcknowledge = new(ngapType.HandoverRequestAcknowledge) + + handoverRequestAcknowledge := successfulOutcome.Value.HandoverRequestAcknowledge + ies := &handoverRequestAcknowledge.ProtocolIEs + + return &HandoverRequestAcknowledgeBuilder{pdu, ies} +} + +func (builder *HandoverRequestAcknowledgeBuilder) SetAmfUeNgapId(amfUeNgapID int64) *HandoverRequestAcknowledgeBuilder { + // AMF UE NGAP ID + ie := ngapType.HandoverRequestAcknowledgeIEs{} + ie.Id.Value = ngapType.ProtocolIEIDAMFUENGAPID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentAMFUENGAPID + ie.Value.AMFUENGAPID = new(ngapType.AMFUENGAPID) + + aMFUENGAPID := ie.Value.AMFUENGAPID + aMFUENGAPID.Value = amfUeNgapID + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequestAcknowledgeBuilder) SetRanUeNgapId(ranUeNgapID int64) *HandoverRequestAcknowledgeBuilder { + // RAN UE NGAP ID + ie := ngapType.HandoverRequestAcknowledgeIEs{} + ie.Id.Value = ngapType.ProtocolIEIDRANUENGAPID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentRANUENGAPID + ie.Value.RANUENGAPID = new(ngapType.RANUENGAPID) + + rANUENGAPID := ie.Value.RANUENGAPID + rANUENGAPID.Value = ranUeNgapID + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequestAcknowledgeBuilder) SetPduSessionResourceAdmittedList(gnb *context.GNBContext, pduSessions [16]*context.GnbPDUSession) *HandoverRequestAcknowledgeBuilder { + ie := ngapType.HandoverRequestAcknowledgeIEs{} + ie.Id.Value = ngapType.ProtocolIEIDPDUSessionResourceAdmittedList + ie.Criticality.Value = ngapType.CriticalityPresentIgnore + ie.Value.Present = ngapType.HandoverRequestAcknowledgeIEsPresentPDUSessionResourceAdmittedList + ie.Value.PDUSessionResourceAdmittedList = new(ngapType.PDUSessionResourceAdmittedList) + + pDUSessionResourceAdmittedList := ie.Value.PDUSessionResourceAdmittedList + + for _, pduSession := range pduSessions { + if pduSession == nil { + continue + } + //PDU SessionResource Admittedy Item + pDUSessionResourceAdmittedItem := ngapType.PDUSessionResourceAdmittedItem{} + pDUSessionResourceAdmittedItem.PDUSessionID.Value = pduSession.GetPduSessionId() + pDUSessionResourceAdmittedItem.HandoverRequestAcknowledgeTransfer = GetHandoverRequestAcknowledgeTransfer(gnb, pduSession) + + pDUSessionResourceAdmittedList.List = append(pDUSessionResourceAdmittedList.List, pDUSessionResourceAdmittedItem) + } + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequestAcknowledgeBuilder) SetTargetToSourceContainer() *HandoverRequestAcknowledgeBuilder { + // Target To Source TransparentContainer + ie := ngapType.HandoverRequestAcknowledgeIEs{} + ie.Id.Value = ngapType.ProtocolIEIDTargetToSourceTransparentContainer + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequestAcknowledgeIEsPresentTargetToSourceTransparentContainer + ie.Value.TargetToSourceTransparentContainer = new(ngapType.TargetToSourceTransparentContainer) + + targetToSourceTransparentContainer := ie.Value.TargetToSourceTransparentContainer + targetToSourceTransparentContainer.Value = GetTargetToSourceTransparentTransfer() + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequestAcknowledgeBuilder) Build() ([]byte, error) { + return ngap.Encoder(builder.pdu) +} + +func GetHandoverRequestAcknowledgeTransfer(gnb *context.GNBContext, pduSession *context.GnbPDUSession) []byte { + data := buildHandoverRequestAcknowledgeTransfer(gnb, pduSession) + encodeData, err := aper.MarshalWithParams(data, "valueExt") + if err != nil { + log.Fatalf("aper MarshalWithParams error in GetHandoverRequestAcknowledgeTransfer: %+v", err) + } + return encodeData +} + +func buildHandoverRequestAcknowledgeTransfer(gnb *context.GNBContext, pduSession *context.GnbPDUSession) (data ngapType.HandoverRequestAcknowledgeTransfer) { + + // DL NG-U UP TNL information + dlTransportLayerInformation := &data.DLNGUUPTNLInformation + dlTransportLayerInformation.Present = ngapType.UPTransportLayerInformationPresentGTPTunnel + dlTransportLayerInformation.GTPTunnel = new(ngapType.GTPTunnel) + downlinkTeid := make([]byte, 4) + binary.BigEndian.PutUint32(downlinkTeid, pduSession.GetTeidDownlink()) + dlTransportLayerInformation.GTPTunnel.GTPTEID.Value = downlinkTeid + dlTransportLayerInformation.GTPTunnel.TransportLayerAddress = ngapConvert.IPAddressToNgap(gnb.GetN3GnbIp(), "") + + // Qos Flow Setup Response List + qosFlowSetupResponseItem := ngapType.QosFlowItemWithDataForwarding{ + QosFlowIdentifier: ngapType.QosFlowIdentifier{ + Value: 1, + }, + } + + data.QosFlowSetupResponseList.List = append(data.QosFlowSetupResponseList.List, qosFlowSetupResponseItem) + + return data +} + +func GetTargetToSourceTransparentTransfer() []byte { + data := buildTargetToSourceTransparentTransfer() + encodeData, err := aper.MarshalWithParams(data, "valueExt") + if err != nil { + log.Fatalf("aper MarshalWithParams error in GetTargetToSourceTransparentTransfer: %+v", err) + } + return encodeData +} + +func buildTargetToSourceTransparentTransfer() (data ngapType.TargetNGRANNodeToSourceNGRANNodeTransparentContainer) { + // RRC Container + data.RRCContainer.Value = aper.OctetString("\x00\x00\x11") + + return data +} diff --git a/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-required.go b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-required.go new file mode 100644 index 00000000..f4ae20e9 --- /dev/null +++ b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/handover-required.go @@ -0,0 +1,257 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2023 Valentin D'Emmanuele + */ +package ue_mobility_management + +import ( + "my5G-RANTester/internal/control_test_engine/gnb/context" + + "github.com/free5gc/ngap" + log "github.com/sirupsen/logrus" + + "github.com/free5gc/aper" + + "github.com/free5gc/ngap/ngapType" +) + +type HandoverRequiredBuilder struct { + pdu ngapType.NGAPPDU + ies *ngapType.ProtocolIEContainerHandoverRequiredIEs +} + +func HandoverRequired(sourceGnb *context.GNBContext, targetGnb *context.GNBContext, ue *context.GNBUe) ([]byte, error) { + return NewHandoverRequiredBuilder(). + SetAmfUeNgapId(ue.GetAmfUeId()).SetRanUeNgapId(ue.GetRanUeId()). + SetHandoverType(ngapType.HandoverTypePresentIntra5gs). + SetCause(ngapType.CauseRadioNetworkPresentHandoverDesirableForRadioReason). + SetPduSessionResourceList(ue.GetPduSessions()). + SetTargetGnodeB(targetGnb). + SetSourceToTargetContainer(sourceGnb, targetGnb, ue.GetPduSessions(), ue.GetPrUeId()). + Build() +} + +func NewHandoverRequiredBuilder() *HandoverRequiredBuilder { + pdu := ngapType.NGAPPDU{} + + pdu.Present = ngapType.NGAPPDUPresentInitiatingMessage + pdu.InitiatingMessage = new(ngapType.InitiatingMessage) + + initiatingMessage := pdu.InitiatingMessage + initiatingMessage.ProcedureCode.Value = ngapType.ProcedureCodeHandoverPreparation + initiatingMessage.Criticality.Value = ngapType.CriticalityPresentReject + + initiatingMessage.Value.Present = ngapType.InitiatingMessagePresentHandoverRequired + initiatingMessage.Value.HandoverRequired = new(ngapType.HandoverRequired) + + handoverRequired := initiatingMessage.Value.HandoverRequired + ies := &handoverRequired.ProtocolIEs + + return &HandoverRequiredBuilder{pdu, ies} +} + +func (builder *HandoverRequiredBuilder) SetAmfUeNgapId(amfUeNgapID int64) *HandoverRequiredBuilder { + // AMF UE NGAP ID + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDAMFUENGAPID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentAMFUENGAPID + ie.Value.AMFUENGAPID = new(ngapType.AMFUENGAPID) + + aMFUENGAPID := ie.Value.AMFUENGAPID + aMFUENGAPID.Value = amfUeNgapID + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequiredBuilder) SetRanUeNgapId(ranUeNgapID int64) *HandoverRequiredBuilder { + // RAN UE NGAP ID + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDRANUENGAPID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentRANUENGAPID + ie.Value.RANUENGAPID = new(ngapType.RANUENGAPID) + + rANUENGAPID := ie.Value.RANUENGAPID + rANUENGAPID.Value = ranUeNgapID + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequiredBuilder) SetHandoverType(handoverTypeValue aper.Enumerated) *HandoverRequiredBuilder { + // Handover Type + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDHandoverType + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentHandoverType + ie.Value.HandoverType = new(ngapType.HandoverType) + + handoverType := ie.Value.HandoverType + handoverType.Value = handoverTypeValue + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequiredBuilder) SetCause(causeValue aper.Enumerated) *HandoverRequiredBuilder { + // Cause + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDCause + ie.Criticality.Value = ngapType.CriticalityPresentIgnore + ie.Value.Present = ngapType.HandoverRequiredIEsPresentCause + ie.Value.Cause = new(ngapType.Cause) + + cause := ie.Value.Cause + cause.Present = ngapType.CausePresentRadioNetwork + cause.RadioNetwork = new(ngapType.CauseRadioNetwork) + cause.RadioNetwork.Value = causeValue + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequiredBuilder) SetTargetGnodeB(targetGnb *context.GNBContext) *HandoverRequiredBuilder { + // Target ID + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDTargetID + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentTargetID + ie.Value.TargetID = new(ngapType.TargetID) + + targetID := ie.Value.TargetID + targetID.Present = ngapType.TargetIDPresentTargetRANNodeID + targetID.TargetRANNodeID = new(ngapType.TargetRANNodeID) + + targetRANNodeID := targetID.TargetRANNodeID + targetRANNodeID.GlobalRANNodeID.Present = ngapType.GlobalRANNodeIDPresentGlobalGNBID + targetRANNodeID.GlobalRANNodeID.GlobalGNBID = new(ngapType.GlobalGNBID) + + globalRANNodeID := targetRANNodeID.GlobalRANNodeID + globalRANNodeID.GlobalGNBID.PLMNIdentity = targetGnb.GetPLMNIdentity() + globalRANNodeID.GlobalGNBID.GNBID.Present = ngapType.GNBIDPresentGNBID + + globalRANNodeID.GlobalGNBID.GNBID.GNBID = new(aper.BitString) + + gNBID := globalRANNodeID.GlobalGNBID.GNBID.GNBID + + *gNBID = aper.BitString{ + Bytes: targetGnb.GetGnbIdInBytes(), + BitLength: uint64(len(targetGnb.GetGnbIdInBytes()) * 8), + } + globalRANNodeID.GlobalGNBID.PLMNIdentity = targetGnb.GetPLMNIdentity() + + targetRANNodeID.SelectedTAI.PLMNIdentity = targetGnb.GetPLMNIdentity() + targetRANNodeID.SelectedTAI.TAC.Value = targetGnb.GetTacInBytes() + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequiredBuilder) SetPduSessionResourceList(pduSessions [16]*context.GnbPDUSession) *HandoverRequiredBuilder { + // PDU Session Resource List + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDPDUSessionResourceListHORqd + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentPDUSessionResourceListHORqd + ie.Value.PDUSessionResourceListHORqd = new(ngapType.PDUSessionResourceListHORqd) + + pDUSessionResourceListHORqd := ie.Value.PDUSessionResourceListHORqd + + for _, pduSession := range pduSessions { + if pduSession == nil { + continue + } + res, _ := aper.MarshalWithParams(ngapType.HandoverRequiredTransfer{}, "valueExt") + + pDUSessionResourceItem := ngapType.PDUSessionResourceItemHORqd{ + PDUSessionID: ngapType.PDUSessionID{ + Value: pduSession.GetPduSessionId(), + }, + HandoverRequiredTransfer: res, + } + + pDUSessionResourceListHORqd.List = append(pDUSessionResourceListHORqd.List, pDUSessionResourceItem) + } + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func (builder *HandoverRequiredBuilder) SetSourceToTargetContainer(sourceGnb *context.GNBContext, targetGnb *context.GNBContext, pduSessions [16]*context.GnbPDUSession, prUeId int64) *HandoverRequiredBuilder { + // Source to Target Transparent Container + ie := ngapType.HandoverRequiredIEs{} + ie.Id.Value = ngapType.ProtocolIEIDSourceToTargetTransparentContainer + ie.Criticality.Value = ngapType.CriticalityPresentReject + ie.Value.Present = ngapType.HandoverRequiredIEsPresentSourceToTargetTransparentContainer + ie.Value.SourceToTargetTransparentContainer = new(ngapType.SourceToTargetTransparentContainer) + + ie.Value.SourceToTargetTransparentContainer.Value = GetSourceToTargetTransparentTransfer(sourceGnb, targetGnb, pduSessions, prUeId) + + builder.ies.List = append(builder.ies.List, ie) + + return builder +} + +func GetSourceToTargetTransparentTransfer(sourceGnb *context.GNBContext, targetGnb *context.GNBContext, pduSessions [16]*context.GnbPDUSession, prUeId int64) []byte { + data := buildSourceToTargetTransparentTransfer(sourceGnb, targetGnb, pduSessions, prUeId) + encodeData, err := aper.MarshalWithParams(data, "valueExt") + if err != nil { + log.Fatalf("aper MarshalWithParams error in GetSourceToTargetTransparentTransfer: %+v", err) + } + return encodeData +} + +func buildSourceToTargetTransparentTransfer(sourceGnb *context.GNBContext, targetGnb *context.GNBContext, pduSessions [16]*context.GnbPDUSession, prUeId int64) (data ngapType.SourceNGRANNodeToTargetNGRANNodeTransparentContainer) { + + // RRC Container + data.RRCContainer.Value = aper.OctetString("\x00\x00\x11") + + data.IndexToRFSP = &ngapType.IndexToRFSP{Value: prUeId} + + // PDU Session Resource Information List + data.PDUSessionResourceInformationList = new(ngapType.PDUSessionResourceInformationList) + for _, pduSession := range pduSessions { + if pduSession == nil { + continue + } + infoItem := ngapType.PDUSessionResourceInformationItem{} + infoItem.PDUSessionID.Value = pduSession.GetPduSessionId() + qosItem := ngapType.QosFlowInformationItem{} + qosItem.QosFlowIdentifier.Value = 1 + infoItem.QosFlowInformationList.List = append(infoItem.QosFlowInformationList.List, qosItem) + data.PDUSessionResourceInformationList.List = append(data.PDUSessionResourceInformationList.List, infoItem) + } + + // Target Cell ID + data.TargetCellID.Present = ngapType.TargetIDPresentTargetRANNodeID + data.TargetCellID.NRCGI = new(ngapType.NRCGI) + data.TargetCellID.NRCGI.PLMNIdentity = targetGnb.GetPLMNIdentity() + data.TargetCellID.NRCGI.NRCellIdentity = targetGnb.GetNRCellIdentity() + + // UE History Information + lastVisitedCellItem := ngapType.LastVisitedCellItem{} + lastVisitedCellInfo := &lastVisitedCellItem.LastVisitedCellInformation + lastVisitedCellInfo.Present = ngapType.LastVisitedCellInformationPresentNGRANCell + lastVisitedCellInfo.NGRANCell = new(ngapType.LastVisitedNGRANCellInformation) + ngRanCell := lastVisitedCellInfo.NGRANCell + ngRanCell.GlobalCellID.Present = ngapType.NGRANCGIPresentNRCGI + ngRanCell.GlobalCellID.NRCGI = new(ngapType.NRCGI) + ngRanCell.GlobalCellID.NRCGI.PLMNIdentity = sourceGnb.GetPLMNIdentity() + ngRanCell.GlobalCellID.NRCGI.NRCellIdentity = sourceGnb.GetNRCellIdentity() + ngRanCell.CellType.CellSize.Value = ngapType.CellSizePresentVerysmall + ngRanCell.TimeUEStayedInCell.Value = 10 + + data.UEHistoryInformation.List = append(data.UEHistoryInformation.List, lastVisitedCellItem) + return data +} + +func (builder *HandoverRequiredBuilder) Build() ([]byte, error) { + return ngap.Encoder(builder.pdu) +} diff --git a/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/path-switch-request.go b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/path-switch-request.go index e80b931a..e0ec1b29 100644 --- a/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/path-switch-request.go +++ b/internal/control_test_engine/gnb/ngap/message/ngap_control/ue_mobility_management/path-switch-request.go @@ -27,7 +27,7 @@ func PathSwitchRequest(gnb *context.GNBContext, ue *context.GNBUe) ([]byte, erro SetSourceAmfUeNgapId(ue.GetAmfUeId()). SetRanUeNgapId(ue.GetRanUeId()). PathSwitchRequestTransfer(gnb.GetN3GnbIp(), ue.GetPduSessions()). - SetUserLocation(gnb.GetMccAndMncInOctets(), gnb.GetTacInBytes()). + SetUserLocation(gnb). SetUserSecurityCapabilities(ue.GetUESecurityCapabilities()). Build() } @@ -82,7 +82,7 @@ func (builder *PathSwitchRequestBuilder) SetRanUeNgapId(ranUeNgapID int64) *Path return builder } -func (builder *PathSwitchRequestBuilder) SetUserLocation(plmn []byte, tac []byte) *PathSwitchRequestBuilder { +func (builder *PathSwitchRequestBuilder) SetUserLocation(gnb *context.GNBContext) *PathSwitchRequestBuilder { // User Location Information ie := ngapType.PathSwitchRequestIEs{} ie.Id.Value = ngapType.ProtocolIEIDUserLocationInformation @@ -95,14 +95,11 @@ func (builder *PathSwitchRequestBuilder) SetUserLocation(plmn []byte, tac []byte userLocationInformation.UserLocationInformationNR = new(ngapType.UserLocationInformationNR) userLocationInformationNR := userLocationInformation.UserLocationInformationNR - userLocationInformationNR.NRCGI.PLMNIdentity.Value = plmn - userLocationInformationNR.NRCGI.NRCellIdentity.Value = aper.BitString{ - Bytes: []byte{0x00, 0x00, 0x00, 0x00, 0x10}, - BitLength: 36, - } + userLocationInformationNR.NRCGI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.NRCGI.NRCellIdentity = gnb.GetNRCellIdentity() - userLocationInformationNR.TAI.PLMNIdentity.Value = plmn - userLocationInformationNR.TAI.TAC.Value = tac + userLocationInformationNR.TAI.PLMNIdentity = gnb.GetPLMNIdentity() + userLocationInformationNR.TAI.TAC.Value = gnb.GetTacInBytes() builder.ies.List = append(builder.ies.List, ie) diff --git a/internal/control_test_engine/gnb/ngap/trigger/trigger.go b/internal/control_test_engine/gnb/ngap/trigger/trigger.go index 5f9e2581..8cffa540 100644 --- a/internal/control_test_engine/gnb/ngap/trigger/trigger.go +++ b/internal/control_test_engine/gnb/ngap/trigger/trigger.go @@ -125,7 +125,7 @@ func SendNgSetupRequest(gnb *context.GNBContext, amf *context.GNBAmf) { } func SendPathSwitchRequest(gnb *context.GNBContext, ue *context.GNBUe) { - log.Info("[GNB] Initiating PDU Session Release Response") + log.Info("[GNB] Initiating Path Switch Request") // send NG setup response. ngapMsg, err := ue_mobility_management.PathSwitchRequest(gnb, ue) @@ -136,12 +136,44 @@ func SendPathSwitchRequest(gnb *context.GNBContext, ue *context.GNBUe) { conn := ue.GetSCTP() err = sender.SendToAmF(ngapMsg, conn) if err != nil { - log.Fatal("[GNB][NGAP] Error sending Path Switch Request.: ", err) + log.Fatal("[GNB][NGAP] Error sending Path Switch Request: ", err) + } +} + +func SendHandoverRequestAcknowledge(gnb *context.GNBContext, ue *context.GNBUe) { + log.Info("[GNB] Initiating Handover Request Acknowledge") + + // send NG setup response. + ngapMsg, err := ue_mobility_management.HandoverRequestAcknowledge(gnb, ue) + if err != nil { + log.Info("[GNB][NGAP] Error sending Handover Request Acknowledge: ", err) + } + + conn := ue.GetSCTP() + err = sender.SendToAmF(ngapMsg, conn) + if err != nil { + log.Fatal("[GNB][NGAP] Error sending Handover Request Acknowledge: ", err) } } -func TriggerHandover(oldGnb *context.GNBContext, newGnb *context.GNBContext, prUeId int64) { - log.Info("[GNB] Initiating UE Handover") +func SendHandoverNotify(gnb *context.GNBContext, ue *context.GNBUe) { + log.Info("[GNB] Initiating Handover Notify") + + // send NG setup response. + ngapMsg, err := ue_mobility_management.HandoverNotify(gnb, ue) + if err != nil { + log.Info("[GNB][NGAP] Error sending Handover Notify: ", err) + } + + conn := ue.GetSCTP() + err = sender.SendToAmF(ngapMsg, conn) + if err != nil { + log.Fatal("[GNB][NGAP] Error sending Handover Notify: ", err) + } +} + +func TriggerXnHandover(oldGnb *context.GNBContext, newGnb *context.GNBContext, prUeId int64) { + log.Info("[GNB] Initiating Xn UE Handover") gnbUeContext, err := oldGnb.GetGnbUeByPrUeId(prUeId) if err != nil { @@ -150,9 +182,32 @@ func TriggerHandover(oldGnb *context.GNBContext, newGnb *context.GNBContext, prU newGnbRx := make(chan context.UEMessage, 1) newGnbTx := make(chan context.UEMessage, 1) - newGnb.GetInboundChannel() <- context.UEMessage{GNBRx: newGnbRx, GNBTx: newGnbTx, UEContext: gnbUeContext} + newGnb.GetInboundChannel() <- context.UEMessage{GNBRx: newGnbRx, GNBTx: newGnbTx, PrUeId: gnbUeContext.GetPrUeId(), UEContext: gnbUeContext, IsHandover: true} msg := context.UEMessage{GNBRx: newGnbRx, GNBTx: newGnbTx} ueSender.SendMessageToUe(gnbUeContext, msg) } + +func TriggerNgapHandover(oldGnb *context.GNBContext, newGnb *context.GNBContext, prUeId int64) { + log.Info("[GNB] Initiating NGAP UE Handover") + + gnbUeContext, err := oldGnb.GetGnbUeByPrUeId(prUeId) + if err != nil { + log.Fatal("[GNB][NGAP] Error getting UE from PR UE ID: ", err) + } + + gnbUeContext.SetHandoverGnodeB(newGnb) + + // send NG setup response. + ngapMsg, err := ue_mobility_management.HandoverRequired(oldGnb, newGnb, gnbUeContext) + if err != nil { + log.Info("[GNB][NGAP] Error sending Handover Required ", err) + } + + conn := gnbUeContext.GetSCTP() + err = sender.SendToAmF(ngapMsg, conn) + if err != nil { + log.Fatal("[GNB][NGAP] Error sending Handover Required: ", err) + } +} diff --git a/internal/control_test_engine/ue/gtp/service/service.go b/internal/control_test_engine/ue/gtp/service/service.go index 7055c65d..75b4db9b 100644 --- a/internal/control_test_engine/ue/gtp/service/service.go +++ b/internal/control_test_engine/ue/gtp/service/service.go @@ -52,6 +52,10 @@ func SetupGtpInterface(ue *context.UEContext, msg gnbContext.UEMessage) { _ = gtpLink.CmdDel(nameInf) + if pduSession.GetStopSignal() != nil { + close(pduSession.GetStopSignal()) + } + go func() { // This function should not return as long as the GTP-U UDP socket is open if err := gtpLink.CmdAdd(nameInf, 1, ueGnbIp.String(), stopSignal); err != nil { diff --git a/internal/templates/test-attach-ue-with-configuration.go b/internal/templates/test-attach-ue-with-configuration.go index 0c1c5966..ae43f4fe 100644 --- a/internal/templates/test-attach-ue-with-configuration.go +++ b/internal/templates/test-attach-ue-with-configuration.go @@ -5,5 +5,5 @@ package templates func TestAttachUeWithConfiguration(tunnelEnabled bool) { - TestMultiUesInQueue(1, tunnelEnabled, true, false, 500, 0, 0, 1) + TestMultiUesInQueue(1, tunnelEnabled, true, false, 500, 0, 0, 0, 1) } diff --git a/internal/templates/test-multi-ues-in-queue.go b/internal/templates/test-multi-ues-in-queue.go index e5a42f92..d7cef183 100644 --- a/internal/templates/test-multi-ues-in-queue.go +++ b/internal/templates/test-multi-ues-in-queue.go @@ -16,7 +16,7 @@ import ( log "github.com/sirupsen/logrus" ) -func TestMultiUesInQueue(numUes int, tunnelEnabled bool, dedicatedGnb bool, loop bool, timeBetweenRegistration int, timeBeforeDeregistration int, timeBeforeHandover int, numPduSessions int) { +func TestMultiUesInQueue(numUes int, tunnelEnabled bool, dedicatedGnb bool, loop bool, timeBetweenRegistration int, timeBeforeDeregistration int, timeBeforeNgapHandover int, timeBeforeXnHandover int, numPduSessions int) { if tunnelEnabled && !dedicatedGnb { log.Fatal("You cannot use the --tunnel option, without using the --dedicatedGnb option") } @@ -38,7 +38,7 @@ func TestMultiUesInQueue(numUes int, tunnelEnabled bool, dedicatedGnb bool, loop } else { numGnb = 1 } - if numGnb <= 1 && timeBeforeHandover != 0 { + if numGnb <= 1 && (timeBeforeXnHandover != 0 || timeBeforeNgapHandover != 0) { log.Warn("[TESTER] We are increasing the number of gNodeB to two for handover test cases. Make you sure you fill the requirements for having two gNodeBs.") numGnb++ } @@ -59,7 +59,8 @@ func TestMultiUesInQueue(numUes int, tunnelEnabled bool, dedicatedGnb bool, loop Gnbs: gnbs, Cfg: cfg, TimeBeforeDeregistration: timeBeforeDeregistration, - TimeBeforeHandover: timeBeforeHandover, + TimeBeforeNgapHandover: timeBeforeNgapHandover, + TimeBeforeXnHandover: timeBeforeXnHandover, NumPduSessions: numPduSessions, } diff --git a/test/pr_test.go b/test/pr_test.go index 41746247..2fb23aef 100644 --- a/test/pr_test.go +++ b/test/pr_test.go @@ -103,7 +103,8 @@ func TestRegistrationToCtxReleaseWithPDUSession(t *testing.T) { Gnbs: gnbs, Cfg: conf, TimeBeforeDeregistration: 400, - TimeBeforeHandover: 0, + TimeBeforeNgapHandover: 0, + TimeBeforeXnHandover: 0, NumPduSessions: 1, } @@ -198,7 +199,8 @@ func TestUERegistrationLoop(t *testing.T) { Gnbs: gnbs, Cfg: conf, TimeBeforeDeregistration: 3000, - TimeBeforeHandover: 0, + TimeBeforeNgapHandover: 0, + TimeBeforeXnHandover: 0, NumPduSessions: 1, }