Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: transport5GSMMessage method #371

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions consumer/sm_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,26 @@
var smfUri string

ue.GmmLog.Infof("Select SMF [snssai: %+v, dnn: %+v]", snssai, dnn)
if snssai.Sst == 0 || dnn == "" {
return nil, nasMessage.Cause5GMMPayloadWasNotForwarded, fmt.Errorf("invalid SNSSAI or DNN parameters")
}

nrfUri := ue.ServingAMF.NrfUri // default NRF URI is pre-configured by AMF

nsiInformation := ue.GetNsiInformationFromSnssai(anType, snssai)
if nsiInformation == nil {
// TODO: Set a timeout of NSSF Selection or will starvation here
for {
const maxRetries = 10
for i := 0; i < maxRetries; i++ {
if err := SearchNssfNSSelectionInstance(ue, nrfUri, models.NfType_NSSF,
models.NfType_AMF, nil); err != nil {
ue.GmmLog.Errorf("AMF can not select an NSSF Instance by NRF[Error: %+v]", err)
time.Sleep(2 * time.Second)
} else {
break
}
if i == maxRetries-1 {
return nil, nasMessage.Cause5GMMPayloadWasNotForwarded, fmt.Errorf("NSSF selection instance timed out")
}
}

response, problemDetails, err := NSSelectionGetForPduSession(ue, snssai)
Expand All @@ -110,7 +116,7 @@
smContext.SetNsInstance(nsiInformation.NsiId)
nrfApiUri, err := url.Parse(nsiInformation.NrfId)
if err != nil {
ue.GmmLog.Errorf("Parse NRF URI error, use default NRF[%s]", nrfUri)
return nil, nasMessage.Cause5GMMPayloadWasNotForwarded, fmt.Errorf("failed to parse NRF URI: %v", err)
} else {
nrfUri = fmt.Sprintf("%s://%s", nrfApiUri.Scheme, nrfApiUri.Host)
}
Expand All @@ -129,12 +135,12 @@

result, err := SendSearchNFInstances(nrfUri, models.NfType_SMF, models.NfType_AMF, &param)
if err != nil {
return nil, nasMessage.Cause5GMMPayloadWasNotForwarded, err
return nil, nasMessage.Cause5GMMPayloadWasNotForwarded, fmt.Errorf("failed to search SMF instances: %v", err)
}

if len(result.NfInstances) == 0 {
err = fmt.Errorf("DNN[%s] is not supported or not subscribed in the slice[Snssai: %+v]", dnn, snssai)
return nil, nasMessage.Cause5GMMDNNNotSupportedOrNotSubscribedInTheSlice, err
if result.NfInstances == nil || len(result.NfInstances) == 0 {

Check failure on line 141 in consumer/sm_context.go

View workflow job for this annotation

GitHub Actions / lint

S1009: should omit nil check; len() for []github.com/omec-project/openapi/models.NfProfile is defined as zero (gosimple)

Check failure on line 141 in consumer/sm_context.go

View workflow job for this annotation

GitHub Actions / staticcheck

should omit nil check; len() for []github.com/omec-project/openapi/models.NfProfile is defined as zero (S1009)
return nil, nasMessage.Cause5GMMDNNNotSupportedOrNotSubscribedInTheSlice,
fmt.Errorf("no SMF instances found for DNN[%s] and SNSSAI[%+v]", dnn, snssai)
}

// select the first SMF, TODO: select base on other info
Expand Down
310 changes: 118 additions & 192 deletions gmm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,214 +86,140 @@ func HandleULNASTransport(ue *context.AmfUe, anType models.AccessType,
return nil
}

func transport5GSMMessage(ue *context.AmfUe, anType models.AccessType,
ulNasTransport *nasMessage.ULNASTransport,
) error {
var pduSessionID int32

func transport5GSMMessage(ue *context.AmfUe, anType models.AccessType, ulNasTransport *nasMessage.ULNASTransport) error {
ue.GmmLog.Info("Transport 5GSM Message to SMF")

smMessage := ulNasTransport.PayloadContainer.GetPayloadContainerContents()
// Extract PDU Session ID or return error
pduSessionID, err := getPduSessionID(ulNasTransport)
if err != nil {
return err
}

if id := ulNasTransport.PduSessionID2Value; id != nil {
pduSessionID = int32(id.GetPduSessionID2Value())
} else {
return errors.New("PDU Session ID is nil")
smContext, smContextExists := ue.SmContextFindByPDUSessionID(pduSessionID)
requestTypeValue, err := getRequestTypeValue(ulNasTransport.RequestType)
if err != nil {
return err
}

// case 1): looks up a PDU session routing context for the UE and the PDU session ID IE in case the Old PDU
// session ID IE is not included
// Handle case where Old PDU Session ID is not included
if ulNasTransport.OldPDUSessionID == nil {
smContext, smContextExist := ue.SmContextFindByPDUSessionID(pduSessionID)
requestType := ulNasTransport.RequestType

if requestType != nil {
switch requestType.GetRequestTypeValue() {
case nasMessage.ULNASTransportRequestTypeInitialEmergencyRequest:
fallthrough
case nasMessage.ULNASTransportRequestTypeExistingEmergencyPduSession:
ue.GmmLog.Warnf("emergency PDU Session is not supported")
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
smMessage, pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded, nil, 0)
return nil
}
if err := processOldPduSessionNotIncluded(ue, anType, pduSessionID, smContext, smContextExists, requestTypeValue, ulNasTransport); err != nil {
return err
}
} else {
// TODO: Additional cases for Old PDU Session ID can be managed here if required
ue.GmmLog.Warn("Old PDU Session ID included but not processed in this logic")
}

if smContextExist && requestType != nil {
/* AMF releases context locally as this is duplicate pdu session */
if requestType.GetRequestTypeValue() == nasMessage.ULNASTransportRequestTypeInitialRequest {
ue.SmContextList.Delete(pduSessionID)
smContextExist = false
}
}
return nil
}

if !smContextExist {
msg := new(nas.Message)
if err := msg.PlainNasDecode(&smMessage); err != nil {
ue.GmmLog.Errorf("could not decode Nas message: %v", err)
}
if msg.GsmMessage != nil && msg.GsmMessage.Status5GSM != nil {
ue.GmmLog.Warnf("SmContext doesn't exist, 5GSM Status message received from UE with cause %v", msg.GsmMessage.Status5GSM.Cause5GSM)
return nil
}
}
// AMF has a PDU session routing context for the PDU session ID and the UE
if smContextExist {
// case i) Request type IE is either not included
if requestType == nil {
return forward5GSMMessageToSMF(ue, anType, pduSessionID, smContext, smMessage)
}
// Extract PDU Session ID or return error
func getPduSessionID(ulNasTransport *nasMessage.ULNASTransport) (int32, error) {
if id := ulNasTransport.PduSessionID2Value; id != nil {
return int32(id.GetPduSessionID2Value()), nil
}
return 0, errors.New("PDU Session ID is nil")
}

switch requestType.GetRequestTypeValue() {
case nasMessage.ULNASTransportRequestTypeInitialRequest:
smContext.StoreULNASTransport(ulNasTransport)
// perform a local release of the PDU session identified by the PDU session ID and shall request
// the SMF to perform a local release of the PDU session
updateData := models.SmContextUpdateData{
Release: true,
Cause: models.Cause_REL_DUE_TO_DUPLICATE_SESSION_ID,
SmContextStatusUri: fmt.Sprintf("%s/namf-callback/v1/smContextStatus/%s/%d",
ue.ServingAMF.GetIPv4Uri(), ue.Guti, pduSessionID),
}
ue.GmmLog.Warnf("duplicated PDU session ID[%d]", pduSessionID)
smContext.SetDuplicatedPduSessionID(true)
response, _, _, err := consumer.SendUpdateSmContextRequest(smContext, updateData, nil, nil)
if err != nil {
return err
}
if response == nil {
err := fmt.Errorf("PDU Session ID[%d] can't be released in DUPLICATE_SESSION_ID case", pduSessionID)
ue.GmmLog.Errorln(err)
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
smMessage, pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded, nil, 0)
} else {
smContext.SetUserLocation(ue.Location)
responseData := response.JsonData
n2Info := response.BinaryDataN2SmInformation
if n2Info != nil {
switch responseData.N2SmInfoType {
case models.N2SmInfoType_PDU_RES_REL_CMD:
ue.GmmLog.Debugln("AMF Transfer NGAP PDU Session Resource Release Command from SMF")
list := ngapType.PDUSessionResourceToReleaseListRelCmd{}
ngap_message.AppendPDUSessionResourceToReleaseListRelCmd(&list, pduSessionID, n2Info)
ngap_message.SendPDUSessionResourceReleaseCommand(ue.RanUe[anType], nil, list)
}
}
}
// Extract request type value or return error
func getRequestTypeValue(requestType *nasType.RequestType) (uint8, error) {
if requestType != nil {
return requestType.GetRequestTypeValue(), nil
}
return 0, errors.New("requestType is nil")
}

// case ii) AMF has a PDU session routing context, and Request type is "existing PDU session"
case nasMessage.ULNASTransportRequestTypeExistingPduSession:
if ue.InAllowedNssai(smContext.Snssai(), anType) {
return forward5GSMMessageToSMF(ue, anType, pduSessionID, smContext, smMessage)
} else {
ue.GmmLog.Errorf("S-NSSAI[%v] is not allowed for access type[%s] (PDU Session ID: %d)",
smContext.Snssai(), anType, pduSessionID)
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
smMessage, pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded, nil, 0)
}
// other requestType: AMF forward the 5GSM message, and the PDU session ID IE towards the SMF identified
// by the SMF ID of the PDU session routing context
default:
return forward5GSMMessageToSMF(ue, anType, pduSessionID, smContext, smMessage)
}
} else { // AMF does not have a PDU session routing context for the PDU session ID and the UE
switch requestType.GetRequestTypeValue() {
// case iii) if the AMF does not have a PDU session routing context for the PDU session ID and the UE
// and the Request type IE is included and is set to "initial request"
case nasMessage.ULNASTransportRequestTypeInitialRequest:
var (
snssai models.Snssai
dnn string
)
// A) AMF shall select an SMF

// If the S-NSSAI IE is not included and the user's subscription context obtained from UDM. AMF shall
// select a default snssai
if ulNasTransport.SNSSAI != nil {
snssai = nasConvert.SnssaiToModels(ulNasTransport.SNSSAI)
} else {
if allowedNssai, ok := ue.AllowedNssai[anType]; ok {
snssai = *allowedNssai[0].AllowedSnssai
} else {
return errors.New("ue doesn't have allowedNssai")
}
}
// Handle logic when Old PDU Session ID is not included
func processOldPduSessionNotIncluded(ue *context.AmfUe, anType models.AccessType, pduSessionID int32, smContext *context.SmContext, smContextExists bool, requestTypeValue uint8, ulNasTransport *nasMessage.ULNASTransport) error {
// Handle emergency request types
if isEmergencyRequestType(requestTypeValue) {
ue.GmmLog.Warnf("emergency PDU Session is not supported")
sendDlNasTransport(ue, anType, ulNasTransport.PayloadContainer.GetPayloadContainerContents(), pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded)
return nil
}

if ulNasTransport.DNN != nil {
dnn = string(ulNasTransport.DNN.GetDNN())
} else {
// if user's subscription context obtained from UDM does not contain the default DNN for the,
// S-NSSAI, the AMF shall use a locally configured DNN as the DNN
dnn = ue.ServingAMF.SupportDnnLists[0]

if ue.SmfSelectionData != nil {
snssaiStr := util.SnssaiModelsToHex(snssai)
if snssaiInfo, ok := ue.SmfSelectionData.SubscribedSnssaiInfos[snssaiStr]; ok {
for _, dnnInfo := range snssaiInfo.DnnInfos {
if dnnInfo.DefaultDnnIndicator {
dnn = dnnInfo.Dnn
}
}
}
}
}
// Check for duplicate PDU Session ID
if smContextExists && requestTypeValue == nasMessage.ULNASTransportRequestTypeInitialRequest {
ue.SmContextList.Delete(pduSessionID)
smContextExists = false
}

if newSmContext, cause, err := consumer.SelectSmf(ue, anType, pduSessionID, snssai, dnn); err != nil {
ue.GmmLog.Errorf("Select SMF failed: %+v", err)
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
smMessage, pduSessionID, cause, nil, 0)
} else {
_, smContextRef, errResponse, problemDetail, err := consumer.SendCreateSmContextRequest(ue, newSmContext, nil, smMessage)
if err != nil {
ue.GmmLog.Errorf("CreateSmContextRequest Error: %+v", err)
return nil
} else if problemDetail != nil {
// TODO: error handling
return fmt.Errorf("failed to Create smContext[pduSessionID: %d], Error[%v]", pduSessionID, problemDetail)
} else if errResponse != nil {
ue.GmmLog.Warnf("PDU Session Establishment Request is rejected by SMF[pduSessionId:%d]",
pduSessionID)
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
errResponse.BinaryDataN1SmMessage, pduSessionID, 0, nil, 0)
} else {
newSmContext.SetSmContextRef(smContextRef)
newSmContext.SetUserLocation(deepcopy.Copy(ue.Location).(models.UserLocation))
ue.StoreSmContext(pduSessionID, newSmContext)
ue.GmmLog.Infof("create smContext[pduSessionID: %d] Success", pduSessionID)
// TODO: handle response(response N2SmInfo to RAN if exists)
ue.PublishUeCtxtInfo()
}
}
case nasMessage.ULNASTransportRequestTypeModificationRequest:
fallthrough
case nasMessage.ULNASTransportRequestTypeExistingPduSession:
if ue.UeContextInSmfData != nil {
// TS 24.501 5.4.5.2.5 case a) 3)
pduSessionIDStr := fmt.Sprintf("%d", pduSessionID)
if ueContextInSmf, ok := ue.UeContextInSmfData.PduSessions[pduSessionIDStr]; !ok {
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
smMessage, pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded, nil, 0)
} else {
// TS 24.501 5.4.5.2.3 case a) 1) iv)
smContext = context.NewSmContext(pduSessionID)
smContext.SetAccessType(anType)
smContext.SetSmfID(ueContextInSmf.SmfInstanceId)
smContext.SetDnn(ueContextInSmf.Dnn)
smContext.SetPlmnID(*ueContextInSmf.PlmnId)
ue.StoreSmContext(pduSessionID, smContext)
return forward5GSMMessageToSMF(ue, anType, pduSessionID, smContext, smMessage)
}
} else {
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo,
smMessage, pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded, nil, 0)
}
default:
}
// Handle case where SM Context does not exist
if !smContextExists {
if is5GSMStatusMessage(ue, ulNasTransport.PayloadContainer.GetPayloadContainerContents()) {
return nil
}
} else {
// TODO: implement SSC mode3 Op
return fmt.Errorf("SSC mode3 operation has not been implemented yet")
// Forward 5GSM message based on request type
return handleSmContextExists(ue, anType, pduSessionID, smContext, ulNasTransport, requestTypeValue)
}

return nil
}

// Check if the request type is emergency-related
func isEmergencyRequestType(requestTypeValue uint8) bool {
return requestTypeValue == nasMessage.ULNASTransportRequestTypeInitialEmergencyRequest ||
requestTypeValue == nasMessage.ULNASTransportRequestTypeExistingEmergencyPduSession
}

// Send DL NAS Transport
func sendDlNasTransport(ue *context.AmfUe, anType models.AccessType, smMessage []byte, pduSessionID int32, cause uint8) {
gmm_message.SendDLNASTransport(ue.RanUe[anType], nasMessage.PayloadContainerTypeN1SMInfo, smMessage, pduSessionID, cause, nil, 0)
}

// Check if 5GSM status message is present
func is5GSMStatusMessage(ue *context.AmfUe, smMessage []byte) bool {
msg := new(nas.Message)
if err := msg.PlainNasDecode(&smMessage); err != nil {
ue.GmmLog.Errorf("could not decode Nas message: %v", err)
return false
}
if msg.GsmMessage != nil && msg.GsmMessage.Status5GSM != nil {
ue.GmmLog.Warnf("5GSM Status message received with cause %v", msg.GsmMessage.Status5GSM.Cause5GSM)
return true
}
return false
}

// Handle logic when SM Context exists
func handleSmContextExists(ue *context.AmfUe, anType models.AccessType, pduSessionID int32, smContext *context.SmContext, ulNasTransport *nasMessage.ULNASTransport, requestTypeValue uint8) error {
switch requestTypeValue {
case nasMessage.ULNASTransportRequestTypeInitialRequest:
return handleInitialRequestType(ue, smContext, ulNasTransport, pduSessionID, anType)
case nasMessage.ULNASTransportRequestTypeExistingPduSession:
if ue.InAllowedNssai(smContext.Snssai(), anType) {
return forward5GSMMessageToSMF(ue, anType, pduSessionID, smContext, ulNasTransport.PayloadContainer.GetPayloadContainerContents())
}
ue.GmmLog.Errorf("S-NSSAI[%v] is not allowed for access type[%s] (PDU Session ID: %d)", smContext.Snssai(), anType, pduSessionID)
sendDlNasTransport(ue, anType, ulNasTransport.PayloadContainer.GetPayloadContainerContents(), pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded)
}

return forward5GSMMessageToSMF(ue, anType, pduSessionID, smContext, ulNasTransport.PayloadContainer.GetPayloadContainerContents())
}

// Handle "Initial Request Type" specifically
func handleInitialRequestType(ue *context.AmfUe, smContext *context.SmContext, ulNasTransport *nasMessage.ULNASTransport, pduSessionID int32, anType models.AccessType) error {
smContext.StoreULNASTransport(ulNasTransport)
// Perform duplicate session release
updateData := models.SmContextUpdateData{
Release: true,
Cause: models.Cause_REL_DUE_TO_DUPLICATE_SESSION_ID,
SmContextStatusUri: fmt.Sprintf("%s/namf-callback/v1/smContextStatus/%s/%d", ue.ServingAMF.GetIPv4Uri(), ue.Guti, pduSessionID),
}
ue.GmmLog.Warnf("Duplicated PDU session ID[%d]", pduSessionID)
smContext.SetDuplicatedPduSessionID(true)
response, _, _, err := consumer.SendUpdateSmContextRequest(smContext, updateData, nil, nil)
if err != nil {
return err
}
if response == nil {
err := fmt.Errorf("PDU Session ID[%d] can't be released in DUPLICATE_SESSION_ID case", pduSessionID)
ue.GmmLog.Errorln(err)
sendDlNasTransport(ue, anType, ulNasTransport.PayloadContainer.GetPayloadContainerContents(), pduSessionID, nasMessage.Cause5GMMPayloadWasNotForwarded)
}

return nil
}

Expand Down
Loading