From ada3c13717baa7ec2a1440b21c65627842b37df9 Mon Sep 17 00:00:00 2001 From: bhagathkrishnacdac Date: Mon, 6 Apr 2026 09:56:53 +0530 Subject: [PATCH] Refactored code to reduce cognitive complexity and eliminate duplicated literals by introducing constants, improving maintainability and readability across multiple modules. --- consumer/nf_management.go | 10 +- context/context.go | 8 +- eventexposure/api_create_ee_subscription.go | 8 +- eventexposure/api_update_ee_subscription.go | 6 +- .../data_change_notification_to_nf.go | 8 +- .../api_subscription_data_update.go | 8 +- producer/event_exposure.go | 85 ++- producer/generate_auth_data.go | 631 +++++++----------- producer/subscriber_data_management.go | 565 ++++++++-------- producer/ue_context_management.go | 73 +- service/init.go | 206 +++--- subscribecallback/api_nf_subscribe_notify.go | 8 +- ...nd_mobility_subscription_data_retrieval.go | 6 +- .../api_subscription_creation.go | 6 +- ...i_subscription_creation_for_shared_data.go | 8 +- .../api_subscription_modification.go | 12 +- subscriberdatamanagement/routers.go | 8 +- udmtests/udm_nf_discovery_test.go | 8 +- ueauthentication/api_confirm_auth.go | 8 +- ueauthentication/api_generate_auth_data.go | 6 +- .../api_amf_registration_for3_gpp_access.go | 6 +- ...pi_amf_registration_for_non3_gpp_access.go | 8 +- ...in_the_amf_registration_for3_gpp_access.go | 6 +- ...he_amf_registration_for_non3_gpp_access.go | 6 +- uecontextmanagement/api_smf_registration.go | 6 +- uecontextmanagement/routers.go | 31 +- util/init_context.go | 104 ++- util/search_nf_service.go | 95 ++- 28 files changed, 944 insertions(+), 996 deletions(-) diff --git a/consumer/nf_management.go b/consumer/nf_management.go index 96c50f9..adaf9d4 100644 --- a/consumer/nf_management.go +++ b/consumer/nf_management.go @@ -20,6 +20,8 @@ import ( "github.com/omec-project/udm/logger" ) +const errServerNoResponse = "server no response" + func BuildNFInstance(udmContext *udmContext.UDMContext) (profile models.NfProfile, err error) { profile.NfInstanceId = udmContext.NfId profile.NfStatus = models.NfStatus_REGISTERED @@ -116,7 +118,7 @@ func SendDeregisterNFInstance() (problemDetails *models.ProblemDetails, err erro problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) problemDetails = &problem } else { - err = openapi.ReportError("server no response") + err = openapi.ReportError(errServerNoResponse) } return } @@ -146,7 +148,7 @@ var SendUpdateNFInstance = func(patchItem []models.PatchItem) (nfProfile models. problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) problemDetails = &problem } else { - err = openapi.ReportError("server no response") + err = openapi.ReportError(errServerNoResponse) } return } @@ -176,7 +178,7 @@ func SendCreateSubscription(nrfUri string, nrfSubscriptionData models.NrfSubscri problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) problemDetails = &problem } else { - err = openapi.ReportError("server no response") + err = openapi.ReportError(errServerNoResponse) } return } @@ -206,7 +208,7 @@ func SendRemoveSubscription(subscriptionId string) (problemDetails *models.Probl problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) problemDetails = &problem } else { - err = openapi.ReportError("server no response") + err = openapi.ReportError(errServerNoResponse) } return } diff --git a/context/context.go b/context/context.go index efb2493..4381e54 100644 --- a/context/context.go +++ b/context/context.go @@ -33,6 +33,8 @@ const ( LocationUriAuthEvents // New constant for AuthEvent resource URI type. ) +const uecmUriPrefix = "/nudm-uecm/v1/" + func init() { UDM_Self().NfService = make(map[models.ServiceName]models.NfService) UDM_Self().EeSubscriptionIDGenerator = idgenerator.NewGenerator(1, math.MaxInt32) @@ -357,11 +359,11 @@ func (context *UDMContext) GetAmfNon3gppRegContext(supi string) *models.AmfNon3G func (ue *UdmUeContext) GetLocationURI(types int) string { switch types { case LocationUriAmf3GppAccessRegistration: - return UDM_Self().GetIPv4Uri() + "/nudm-uecm/v1/" + ue.Supi + "/registrations/amf-3gpp-access" + return UDM_Self().GetIPv4Uri() + uecmUriPrefix + ue.Supi + "/registrations/amf-3gpp-access" case LocationUriAmfNon3GppAccessRegistration: - return UDM_Self().GetIPv4Uri() + "/nudm-uecm/v1/" + ue.Supi + "/registrations/amf-non-3gpp-access" + return UDM_Self().GetIPv4Uri() + uecmUriPrefix + ue.Supi + "/registrations/amf-non-3gpp-access" case LocationUriSmfRegistration: - return UDM_Self().GetIPv4Uri() + "/nudm-uecm/v1/" + ue.Supi + "/registrations/smf-registrations/" + ue.PduSessionID + return UDM_Self().GetIPv4Uri() + uecmUriPrefix + ue.Supi + "/registrations/smf-registrations/" + ue.PduSessionID } return "" } diff --git a/eventexposure/api_create_ee_subscription.go b/eventexposure/api_create_ee_subscription.go index c18fdfe..cd5037e 100644 --- a/eventexposure/api_create_ee_subscription.go +++ b/eventexposure/api_create_ee_subscription.go @@ -25,6 +25,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + // HTTPCreateEeSubscription - Subscribe func HTTPCreateEeSubscription(c *gin.Context) { var eeSubscriptionReq models.EeSubscription @@ -42,7 +44,7 @@ func HTTPCreateEeSubscription(c *gin.Context) { return } - err = openapi.Deserialize(&eeSubscriptionReq, requestBody, "application/json") + err = openapi.Deserialize(&eeSubscriptionReq, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -60,7 +62,7 @@ func HTTPCreateEeSubscription(c *gin.Context) { rsp := producer.HandleCreateEeSubscription(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.EeLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -70,6 +72,6 @@ func HTTPCreateEeSubscription(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/eventexposure/api_update_ee_subscription.go b/eventexposure/api_update_ee_subscription.go index 143b334..79723f4 100644 --- a/eventexposure/api_update_ee_subscription.go +++ b/eventexposure/api_update_ee_subscription.go @@ -42,7 +42,7 @@ func HTTPUpdateEeSubscription(c *gin.Context) { return } - err = openapi.Deserialize(&patchList, requestBody, "application/json") + err = openapi.Deserialize(&patchList, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -64,7 +64,7 @@ func HTTPUpdateEeSubscription(c *gin.Context) { if rsp.Status == http.StatusNoContent { c.Status(rsp.Status) } else { - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.EeLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -74,7 +74,7 @@ func HTTPUpdateEeSubscription(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } } diff --git a/httpcallback/data_change_notification_to_nf.go b/httpcallback/data_change_notification_to_nf.go index b7641ee..65f5fe5 100644 --- a/httpcallback/data_change_notification_to_nf.go +++ b/httpcallback/data_change_notification_to_nf.go @@ -16,6 +16,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + func HTTPDataChangeNotificationToNF(c *gin.Context) { var dataChangeNotify models.DataChangeNotify // step 1: retrieve http request body @@ -33,7 +35,7 @@ func HTTPDataChangeNotificationToNF(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&dataChangeNotify, requestBody, "application/json") + err = openapi.Deserialize(&dataChangeNotify, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -50,7 +52,7 @@ func HTTPDataChangeNotificationToNF(c *gin.Context) { req.Params["supi"] = c.Params.ByName("supi") rsp := producer.HandleDataChangeNotificationToNFRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.CallbackLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -60,6 +62,6 @@ func HTTPDataChangeNotificationToNF(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/parameterprovision/api_subscription_data_update.go b/parameterprovision/api_subscription_data_update.go index 575ef65..fae200f 100644 --- a/parameterprovision/api_subscription_data_update.go +++ b/parameterprovision/api_subscription_data_update.go @@ -25,6 +25,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + // Update - provision parameters func HTTPUpdate(c *gin.Context) { var ppDataReq models.PpData @@ -44,7 +46,7 @@ func HTTPUpdate(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&ppDataReq, requestBody, "application/json") + err = openapi.Deserialize(&ppDataReq, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -62,7 +64,7 @@ func HTTPUpdate(c *gin.Context) { rsp := producer.HandleUpdateRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.PpLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -72,6 +74,6 @@ func HTTPUpdate(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/producer/event_exposure.go b/producer/event_exposure.go index 8b44996..c5008bf 100644 --- a/producer/event_exposure.go +++ b/producer/event_exposure.go @@ -16,7 +16,13 @@ import ( "github.com/omec-project/util/httpwrapper" ) -const anyUE = "anyUE" +const ( + anyUE = "anyUE" + prefixMsisdn = "msisdn-" + prefixExtid = "extid-" + prefixExtgroupId = "extgroupid-" + fmtPatchItem = "patch item: %+v" +) func HandleCreateEeSubscription(request *httpwrapper.Request) *httpwrapper.Response { logger.EeLog.Infoln("Handle Create EE Subscription") @@ -47,10 +53,10 @@ func CreateEeSubscriptionProcedure(ueIdentity string, logger.EeLog.Debugf("udIdentity: %s", ueIdentity) switch { // GPSI (MSISDN identifier) represents a single UE - case strings.HasPrefix(ueIdentity, "msisdn-"): + case strings.HasPrefix(ueIdentity, prefixMsisdn): fallthrough // GPSI (External identifier) represents a single UE - case strings.HasPrefix(ueIdentity, "extid-"): + case strings.HasPrefix(ueIdentity, prefixExtid): if ue, ok := udmSelf.UdmUeFindByGpsi(ueIdentity); ok { id, err := udmSelf.EeSubscriptionIDGenerator.Allocate() if err != nil { @@ -75,7 +81,7 @@ func CreateEeSubscriptionProcedure(ueIdentity string, return nil, problemDetails } // external groupID represents a group of UEs - case strings.HasPrefix(ueIdentity, "extgroupid-"): + case strings.HasPrefix(ueIdentity, prefixExtgroupId): id, err := udmSelf.EeSubscriptionIDGenerator.Allocate() if err != nil { problemDetails := &models.ProblemDetails{ @@ -145,13 +151,13 @@ func DeleteEeSubscriptionProcedure(ueIdentity string, subscriptionID string) { udmSelf := udm_context.UDM_Self() switch { - case strings.HasPrefix(ueIdentity, "msisdn-"): + case strings.HasPrefix(ueIdentity, prefixMsisdn): fallthrough - case strings.HasPrefix(ueIdentity, "extid-"): + case strings.HasPrefix(ueIdentity, prefixExtid): if ue, ok := udmSelf.UdmUeFindByGpsi(ueIdentity); ok { delete(ue.EeSubscriptions, subscriptionID) } - case strings.HasPrefix(ueIdentity, "extgroupid-"): + case strings.HasPrefix(ueIdentity, prefixExtgroupId): udmSelf.UdmUePool.Range(func(key, value interface{}) bool { ue := value.(*udm_context.UdmUeContext) if ue.ExternalGroupID == ueIdentity { @@ -190,73 +196,58 @@ func HandleUpdateEeSubscription(request *httpwrapper.Request) *httpwrapper.Respo } // TODO: complete this procedure based on TS 29503 5.5 +// applyPatchToUe localizes the patching logic and reduces nesting in the main caller. +func applyPatchToUe(ue *udm_context.UdmUeContext, subscriptionID string, patchList []models.PatchItem) bool { + if _, ok := ue.EeSubscriptions[subscriptionID]; !ok { + return false + } + for _, patchItem := range patchList { + logger.EeLog.Debugf(fmtPatchItem, patchItem) + // TODO: patch the Eesubscription + } + return true +} + func UpdateEeSubscriptionProcedure(ueIdentity string, subscriptionID string, patchList []models.PatchItem, ) *models.ProblemDetails { udmSelf := udm_context.UDM_Self() switch { - case strings.HasPrefix(ueIdentity, "msisdn-"): - fallthrough - case strings.HasPrefix(ueIdentity, "extid-"): - if ue, ok := udmSelf.UdmUeFindByGpsi(ueIdentity); ok { - if _, ok := ue.EeSubscriptions[subscriptionID]; ok { - for _, patchItem := range patchList { - logger.EeLog.Debugf("patch item: %+v", patchItem) - // TODO: patch the Eesubscription - } - return nil - } else { - problemDetails := &models.ProblemDetails{ - Status: http.StatusNotFound, - Cause: "SUBSCRIPTION_NOT_FOUND", - } - return problemDetails - } - } else { - problemDetails := &models.ProblemDetails{ + case strings.HasPrefix(ueIdentity, prefixMsisdn), strings.HasPrefix(ueIdentity, prefixExtid): + ue, ok := udmSelf.UdmUeFindByGpsi(ueIdentity) + if !ok || !applyPatchToUe(ue, subscriptionID, patchList) { + return &models.ProblemDetails{ Status: http.StatusNotFound, Cause: "SUBSCRIPTION_NOT_FOUND", } - return problemDetails } - case strings.HasPrefix(ueIdentity, "extgroupid-"): + return nil + + case strings.HasPrefix(ueIdentity, prefixExtgroupId): udmSelf.UdmUePool.Range(func(key, value interface{}) bool { ue := value.(*udm_context.UdmUeContext) if ue.ExternalGroupID == ueIdentity { - if _, ok := ue.EeSubscriptions[subscriptionID]; ok { - for _, patchItem := range patchList { - logger.EeLog.Debugf("patch item: %+v", patchItem) - // TODO: patch the Eesubscription - } - } + applyPatchToUe(ue, subscriptionID, patchList) } return true }) return nil + case ueIdentity == anyUE: udmSelf.UdmUePool.Range(func(key, value interface{}) bool { - ue := value.(*udm_context.UdmUeContext) - if _, ok := ue.EeSubscriptions[subscriptionID]; ok { - for _, patchItem := range patchList { - logger.EeLog.Debugf("patch item: %+v", patchItem) - // TODO: patch the Eesubscription - } - } + applyPatchToUe(value.(*udm_context.UdmUeContext), subscriptionID, patchList) return true }) return nil + default: - problemDetails := &models.ProblemDetails{ + return &models.ProblemDetails{ Status: http.StatusBadRequest, Cause: "MANDATORY_IE_INCORRECT", InvalidParams: []models.InvalidParam{ - { - Param: "ueIdentity", - Reason: "incorrect format", - }, + {Param: "ueIdentity", Reason: "incorrect format"}, }, } - return problemDetails } } diff --git a/producer/generate_auth_data.go b/producer/generate_auth_data.go index 8e1e892..ea0034e 100644 --- a/producer/generate_auth_data.go +++ b/producer/generate_auth_data.go @@ -39,6 +39,10 @@ const ( opcStrLen int = 32 ) +type milenageResult struct { + macA, res, ck, ik, ak []byte +} + const ( authenticationRejected = "AUTHENTICATION_REJECTED" userNotFoundError = "USER_NOT_FOUND" @@ -132,498 +136,329 @@ func HandleConfirmAuthDataRequest(request *httpwrapper.Request) *httpwrapper.Res return httpwrapper.NewResponse(http.StatusInternalServerError, nil, problemDetails) } -func ConfirmAuthDataProcedure(authEvent models.AuthEvent, supi string) (header http.Header, response *models.AuthEvent, problemDetails *models.ProblemDetails) { - var createAuthParam Nudr_DataRepository.CreateAuthenticationStatusParamOpts - optInterface := optional.NewInterface(authEvent) - createAuthParam.AuthEvent = optInterface - - client, err := createUDMClientToUDR(supi) +// extractAuthEventBody handles the complexity of retrieving the body from either the error object or the response stream. +func extractAuthEventBody(resp *http.Response, err error) ([]byte, error) { if err != nil { - // Use naked return with named return variables. - problemDetails = util.ProblemDetailsSystemFailure(err.Error()) - return - } - - resp, err := client.AuthenticationStatusDocumentApi.CreateAuthenticationStatus( - context.Background(), supi, &createAuthParam) - - // The logic is completely replaced to handle the new 201 response correctly. - if resp != nil { - defer func() { - if rspCloseErr := resp.Body.Close(); rspCloseErr != nil { - logger.UeauLog.Errorf("CreateAuthenticationStatus response body cannot close: %+v", rspCloseErr) - } - }() - } - - // First, check for a valid response object and the successful status code. - if resp != nil && resp.StatusCode == http.StatusCreated { - var createdEvent models.AuthEvent - var responseBody []byte - - // This client library puts the 201 response body inside the error object. - // Check the error object first. - if err != nil { - if openApiErr, ok := err.(openapi.GenericOpenAPIError); ok { - responseBody = openApiErr.Body() - } - } - - // Fallback to read the body directly if it wasn't in the error. - if responseBody == nil { - body, readErr := io.ReadAll(resp.Body) - if readErr != nil { - problemDetails = util.ProblemDetailsSystemFailure("UDR Response Body Read Failure") - return - } - responseBody = body + if openApiErr, ok := err.(openapi.GenericOpenAPIError); ok && len(openApiErr.Body()) > 0 { + return openApiErr.Body(), nil } - - // Decode the JSON from the UDR response. - if decodeErr := json.Unmarshal(responseBody, &createdEvent); decodeErr != nil { - problemDetails = util.ProblemDetailsSystemFailure("UDR Response Decode Failure") - return - } - - // Find or create the UE's context in memory. - ue, ok := udm_context.UDM_Self().UdmUeFindBySupi(supi) - if !ok { - logger.UeauLog.Infof("Storing AuthEvent with ID [%s] in context for SUPI [%s]", createdEvent.AuthEventId, supi) - ue = udm_context.UDM_Self().NewUdmUe(supi) - } - // Store the event in the UE's context for the subsequent deletion request. - ue.LastAuthenticationEvent = &createdEvent - - // Build the Location header using the new helper function. - locationURI := udm_context.UDM_Self().GetLocationURI3(udm_context.LocationUriAuthEvents, supi, createdEvent.AuthEventId) - header = make(http.Header) - header.Set("Location", locationURI) // Set the response body and return successfully. - response = &createdEvent - return } + if resp != nil && resp.Body != nil { + return io.ReadAll(resp.Body) + } + return nil, fmt.Errorf("no response body available") +} - problemDetails = &models.ProblemDetails{ +// buildAuthDataProblemDetails centralizes the error mapping logic. +func buildAuthDataProblemDetails(resp *http.Response, err error) *models.ProblemDetails { + pd := &models.ProblemDetails{ Status: http.StatusInternalServerError, Cause: "UDR_ERROR", Detail: "Received an unexpected status code or error from UDR.", } if resp != nil { - problemDetails.Status = int32(resp.StatusCode) + pd.Status = int32(resp.StatusCode) } if err != nil { if openApiErr, ok := err.(openapi.GenericOpenAPIError); ok { if prob, ok := openApiErr.Model().(models.ProblemDetails); ok { - problemDetails.Cause = prob.Cause + pd.Cause = prob.Cause } } - problemDetails.Detail = err.Error() + pd.Detail = err.Error() } - return + return pd } -func GenerateAuthDataProcedure(authInfoRequest models.AuthenticationInfoRequest, supiOrSuci string) (response *models.AuthenticationInfoResult, problemDetails *models.ProblemDetails) { - logger.UeauLog.Debugln("in GenerateAuthDataProcedure") - response = &models.AuthenticationInfoResult{} - supi, err := suci.ToSupi(supiOrSuci, udm_context.UDM_Self().SuciProfiles) - if err != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: err.Error(), - } - - logger.UeauLog.Errorln("suciToSupi error:", err.Error()) - return nil, problemDetails +func ConfirmAuthDataProcedure(authEvent models.AuthEvent, supi string) (header http.Header, response *models.AuthEvent, problemDetails *models.ProblemDetails) { + createAuthParam := Nudr_DataRepository.CreateAuthenticationStatusParamOpts{ + AuthEvent: optional.NewInterface(authEvent), } - logger.UeauLog.Debugf("supi conversion => %s", supi) + client, err := createUDMClientToUDR(supi) if err != nil { - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, nil, util.ProblemDetailsSystemFailure(err.Error()) } - authSubs, res, err := client.AuthenticationDataDocumentApi.QueryAuthSubsData(context.Background(), supi, nil) - if err != nil { - var problemDetails models.ProblemDetails - problemDetails.Detail = err.Error() - if res != nil { - switch res.StatusCode { - case http.StatusNotFound: - problemDetails.Status = http.StatusNotFound - problemDetails.Cause = userNotFoundError - case http.StatusForbidden: - problemDetails.Status = http.StatusForbidden - problemDetails.Cause = authenticationRejected - default: - problemDetails.Status = http.StatusInternalServerError - problemDetails.Cause = authenticationRejected + + resp, err := client.AuthenticationStatusDocumentApi.CreateAuthenticationStatus(context.Background(), supi, &createAuthParam) + if resp != nil { + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil { + logger.UeauLog.Errorf("CreateAuthenticationStatus response body cannot close: %+v", closeErr) } - } else { - problemDetails.Status = http.StatusForbidden - problemDetails.Cause = authenticationRejected - } - logger.UeauLog.Errorln("return from UDR QueryAuthSubsData error") - return nil, &problemDetails + }() } - defer func() { - if rspCloseErr := res.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QueryAuthSubsData response body cannot close: %+v", rspCloseErr) - } - }() - - /* - K, RAND, CK, IK: 128 bits (16 bytes) (hex len = 32) - SQN, AK: 48 bits (6 bytes) (hex len = 12) TS33.102 - 6.3.2 - AMF: 16 bits (2 bytes) (hex len = 4) TS33.102 - Annex H - */ - hasK, hasOP, hasOPC := false, false, false + // Exit early if the response is not 201 Created + if resp == nil || resp.StatusCode != http.StatusCreated { + return nil, nil, buildAuthDataProblemDetails(resp, err) + } - var kStr, opStr, opcStr string + // Extract and Decode Body + responseBody, readErr := extractAuthEventBody(resp, err) + if readErr != nil { + return nil, nil, util.ProblemDetailsSystemFailure("UDR Response Body Read Failure") + } - k, op, opc := make([]byte, 16), make([]byte, 16), make([]byte, 16) + var createdEvent models.AuthEvent + if decodeErr := json.Unmarshal(responseBody, &createdEvent); decodeErr != nil { + return nil, nil, util.ProblemDetailsSystemFailure("UDR Response Decode Failure") + } - logger.UeauLog.Debugln("K", k) + // Update Context + ue, ok := udm_context.UDM_Self().UdmUeFindBySupi(supi) + if !ok { + ue = udm_context.UDM_Self().NewUdmUe(supi) + } + ue.LastAuthenticationEvent = &createdEvent - if authSubs.PermanentKey != nil { - kStr = authSubs.PermanentKey.PermanentKeyValue - if len(kStr) == keyStrLen { - k, err = hex.DecodeString(kStr) - if err != nil { - logger.UeauLog.Errorln("err", err) - } else { - hasK = true - } - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - } + // Success Response + locationURI := udm_context.UDM_Self().GetLocationURI3(udm_context.LocationUriAuthEvents, supi, createdEvent.AuthEventId) + header = make(http.Header) + header.Set("Location", locationURI) + return header, &createdEvent, nil +} - logger.UeauLog.Errorln("kStr length is", len(kStr)) - return nil, problemDetails - } - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - } +func GenerateAuthDataProcedure(authInfoRequest models.AuthenticationInfoRequest, supiOrSuci string) (*models.AuthenticationInfoResult, *models.ProblemDetails) { + logger.UeauLog.Debugln("in GenerateAuthDataProcedure") - logger.UeauLog.Errorln("Nil PermanentKey") - return nil, problemDetails - } - - if authSubs.Milenage != nil { - if authSubs.Milenage.Op != nil { - opStr = authSubs.Milenage.Op.OpValue - if len(opStr) == opStrLen { - op, err = hex.DecodeString(opStr) - if err != nil { - logger.UeauLog.Errorln("err", err) - } else { - hasOP = true - } - } else { - logger.UeauLog.Errorln("opStr length is", len(opStr)) - } - } else { - logger.UeauLog.Infoln("Nil Op") - } - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - } + // 1. Resolve Identity and Fetch Subscription Data + supi, authSubs, client, prob := fetchAuthSubscription(supiOrSuci) + if prob != nil { + return nil, prob + } - logger.UeauLog.Infoln("Nil Milenage") - return nil, problemDetails + // 2. Extract and Derive Credentials (K, OP, OPC) + k, opc, prob := deriveAuthenticationKeys(authSubs) + if prob != nil { + return nil, prob } - if authSubs.Opc != nil && authSubs.Opc.OpcValue != "" { - opcStr = authSubs.Opc.OpcValue - if len(opcStr) == opcStrLen { - opc, err = hex.DecodeString(opcStr) - if err != nil { - logger.UeauLog.Errorln("err", err) - } else { - hasOPC = true - } - } else { - logger.UeauLog.Errorln("opStr length is", len(opStr)) - } - } else { - logger.UeauLog.Infoln("Nil Opc") + // 3. Manage SQN and RAND (Handle Resync and Increment) + sqnBytes, randBytes, prob := handleSqnAndResync(client, supi, authSubs, authInfoRequest, k, opc) + if prob != nil { + return nil, prob } - if !hasOPC && !hasOP { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - } + // 4. Run Milenage Algorithm + mOut, err := runMilenage(k, opc, randBytes, sqnBytes) + if err != nil { + logger.UeauLog.Errorln("Milenage error:", err) + return nil, util.ProblemDetailsSystemFailure("Milenage algorithm execution failed") + } - return nil, problemDetails + // 5. Derive Authentication Vector (5G AKA or EAP-AKA') + response, prob := buildAuthResponse(authInfoRequest, authSubs, mOut, supi, randBytes) + if prob != nil { + return nil, prob } - if !hasOPC { - if hasK && hasOP { - opc, err = milenage.GenerateOPC(k, op) - if err != nil { - logger.UeauLog.Errorln("milenage GenerateOPC err", err) - } - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - } + return response, nil +} - logger.UeauLog.Errorln("unable to derive OPC") - return nil, problemDetails - } +// fetchAuthSubscription handles SUCI-to-SUPI conversion and initial UDR data retrieval. +func fetchAuthSubscription(supiOrSuci string) (string, *models.AuthenticationSubscription, *Nudr_DataRepository.APIClient, *models.ProblemDetails) { + supi, err := suci.ToSupi(supiOrSuci, udm_context.UDM_Self().SuciProfiles) + if err != nil { + logger.UeauLog.Errorln("suciToSupi error:", err.Error()) + return "", nil, nil, &models.ProblemDetails{Status: http.StatusForbidden, Cause: authenticationRejected, Detail: err.Error()} } - sqnStr := strictHex(authSubs.SequenceNumber, 12) - logger.UeauLog.Debugln("sqnStr", sqnStr) - sqn, err := hex.DecodeString(sqnStr) + client, err := createUDMClientToUDR(supi) if err != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: err.Error(), - } - - logger.UeauLog.Errorln("err", err) - return nil, problemDetails + return "", nil, nil, util.ProblemDetailsSystemFailure(err.Error()) } - logger.UeauLog.Debugln("sqn", sqn) - - RAND := make([]byte, 16) - _, err = rand.Read(RAND) + authSubs, res, err := client.AuthenticationDataDocumentApi.QueryAuthSubsData(context.Background(), supi, nil) if err != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: err.Error(), - } - - logger.UeauLog.Errorln("err", err) - return nil, problemDetails + return "", nil, nil, mapUdrErrorToProblemDetails(res, err) } + defer res.Body.Close() - AMF, err := hex.DecodeString("8000") - if err != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: err.Error(), - } + return supi, &authSubs, client, nil +} - logger.UeauLog.Errorln("err", err) - return nil, problemDetails +// deriveAuthenticationKeys extracts K and identifies/generates OPC. +func deriveAuthenticationKeys(authSubs *models.AuthenticationSubscription) ([]byte, []byte, *models.ProblemDetails) { + if authSubs.PermanentKey == nil || len(authSubs.PermanentKey.PermanentKeyValue) != keyStrLen { + return nil, nil, &models.ProblemDetails{Status: http.StatusForbidden, Cause: authenticationRejected} } - // re-synchroniztion - if authInfoRequest.ResynchronizationInfo != nil { - Auts, deCodeErr := hex.DecodeString(authInfoRequest.ResynchronizationInfo.Auts) - if deCodeErr != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: deCodeErr.Error(), - } + k, err := hex.DecodeString(authSubs.PermanentKey.PermanentKeyValue) + if err != nil { + return nil, nil, &models.ProblemDetails{Status: http.StatusForbidden, Cause: authenticationRejected, Detail: "K decode fail"} + } - logger.UeauLog.Errorln("err", deCodeErr) - return nil, problemDetails + var op, opc []byte + if authSubs.Opc != nil && len(authSubs.Opc.OpcValue) == opcStrLen { + opc, err = hex.DecodeString(authSubs.Opc.OpcValue) + if err == nil { + return k, opc, nil } + } - randHex, deCodeErr := hex.DecodeString(authInfoRequest.ResynchronizationInfo.Rand) - if deCodeErr != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: deCodeErr.Error(), - } - - logger.UeauLog.Errorln("err", deCodeErr) - return nil, problemDetails + if authSubs.Milenage != nil && authSubs.Milenage.Op != nil && len(authSubs.Milenage.Op.OpValue) == opStrLen { + op, err = hex.DecodeString(authSubs.Milenage.Op.OpValue) + if err != nil { + return nil, nil, &models.ProblemDetails{Status: http.StatusForbidden, Cause: authenticationRejected, Detail: "OP decode fail"} + } + opc, err = milenage.GenerateOPC(k, op) + if err != nil { + return nil, nil, &models.ProblemDetails{Status: http.StatusForbidden, Cause: authenticationRejected, Detail: "OPC derive fail"} } + return k, opc, nil + } - SQNms, macS := aucSQN(opc, k, Auts, randHex) - if reflect.DeepEqual(macS, Auts[6:]) { - _, err = rand.Read(RAND) - if err != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: authenticationRejected, - Detail: err.Error(), - } - - logger.UeauLog.Errorln("err", err) - return nil, problemDetails - } + return nil, nil, &models.ProblemDetails{Status: http.StatusForbidden, Cause: authenticationRejected} +} - // increment sqn authSubs.SequenceNumber - bigSQN := big.NewInt(0) - sqnStr = hex.EncodeToString(SQNms) - logger.UeauLog.Infof("SQNstr %s", sqnStr) - bigSQN.SetString(sqnStr, 16) - - bigInc := big.NewInt(ind + 1) - - bigP := big.NewInt(SqnMAx) - bigSQN = bigInc.Add(bigSQN, bigInc) - bigSQN = bigSQN.Mod(bigSQN, bigP) - sqnStr = fmt.Sprintf("%x", bigSQN) - sqnStr = strictHex(sqnStr, 12) - } else { - logger.UeauLog.Errorln("Re-Sync MAC failed", supi) - logger.UeauLog.Errorln("MACS", macS) - logger.UeauLog.Errorln("Auts[6:]", Auts[6:]) - logger.UeauLog.Errorln("Sqn", SQNms) - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "modification is rejected", - } - return nil, problemDetails +func handleSqnAndResync(client *Nudr_DataRepository.APIClient, supi string, subs *models.AuthenticationSubscription, req models.AuthenticationInfoRequest, k, opc []byte) ([]byte, []byte, *models.ProblemDetails) { + sqnStr := strictHex(subs.SequenceNumber, 12) + randBytes := make([]byte, 16) + if _, err := rand.Read(randBytes); err != nil { + return nil, nil, util.ProblemDetailsSystemFailure("Random generator failed") + } + + if req.ResynchronizationInfo != nil { + var prob *models.ProblemDetails + sqnStr, prob = performResync(supi, req.ResynchronizationInfo, k, opc, randBytes) + if prob != nil { + return nil, nil, prob } } - // increment sqn - bigSQN := big.NewInt(0) - sqn, err = hex.DecodeString(sqnStr) + if prob := updateSqnInUdr(client, supi, sqnStr); prob != nil { + return nil, nil, prob + } + + sqnBytes, err := hex.DecodeString(sqnStr) if err != nil { - problemDetails = &models.ProblemDetails{ + return nil, nil, util.ProblemDetailsSystemFailure("SQN string is not valid hex") + } + return sqnBytes, randBytes, nil +} + +func performResync(supi string, resync *models.ResynchronizationInfo, k, opc, newRand []byte) (string, *models.ProblemDetails) { + auts, err1 := hex.DecodeString(resync.Auts) + oldRand, err2 := hex.DecodeString(resync.Rand) + if err1 != nil || err2 != nil { + return "", &models.ProblemDetails{ Status: http.StatusForbidden, Cause: authenticationRejected, - Detail: err.Error(), + Detail: "Resync parameters are not valid hex", } + } - logger.UeauLog.Errorln("err", err) - return nil, problemDetails + sqnMs, macS := aucSQN(opc, k, auts, oldRand) + if !reflect.DeepEqual(macS, auts[6:]) { + logger.UeauLog.Errorln("Re-Sync MAC failed", supi) + return "", &models.ProblemDetails{Status: http.StatusForbidden, Cause: "modification is rejected"} } - bigSQN.SetString(sqnStr, 16) + bigSQN := big.NewInt(0).SetBytes(sqnMs) + bigInc := big.NewInt(ind + 1) + bigSQN.Add(bigSQN, bigInc).Mod(bigSQN, big.NewInt(SqnMAx)) - bigInc := big.NewInt(1) - bigSQN = bigInc.Add(bigSQN, bigInc) + return strictHex(fmt.Sprintf("%x", bigSQN), 12), nil +} - SQNheStr := fmt.Sprintf("%x", bigSQN) - SQNheStr = strictHex(SQNheStr, 12) - patchItemArray := []models.PatchItem{ - { - Op: models.PatchOperation_REPLACE, - Path: "/sequenceNumber", - Value: SQNheStr, - }, +func updateSqnInUdr(client *Nudr_DataRepository.APIClient, supi, currentSqnStr string) *models.ProblemDetails { + bigSQN, _ := big.NewInt(0).SetString(currentSqnStr, 16) + nextSqnStr := strictHex(fmt.Sprintf("%x", bigSQN.Add(bigSQN, big.NewInt(1))), 12) + + patch := []models.PatchItem{{Op: models.PatchOperation_REPLACE, Path: "/sequenceNumber", Value: nextSqnStr}} + rsp, err := client.AuthenticationDataDocumentApi.ModifyAuthentication(context.Background(), supi, patch) + if err != nil { + return &models.ProblemDetails{Status: http.StatusForbidden, Cause: "modification is rejected", Detail: err.Error()} } + rsp.Body.Close() + return nil +} - var rsp *http.Response - rsp, err = client.AuthenticationDataDocumentApi.ModifyAuthentication( - context.Background(), supi, patchItemArray) +func runMilenage(k, opc, rand, sqn []byte) (milenageResult, error) { + // FIX: errcheck for hardcoded hex string + amf, err := hex.DecodeString("8000") if err != nil { - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "modification is rejected ", - Detail: err.Error(), - } + return milenageResult{}, err + } - logger.UeauLog.Errorln("update sqn error", err) - return nil, problemDetails + res := milenageResult{ + macA: make([]byte, 8), ck: make([]byte, 16), ik: make([]byte, 16), + res: make([]byte, 8), ak: make([]byte, 6), } - defer func() { - if rspCloseErr := rsp.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("ModifyAuthentication response body cannot close: %+v", rspCloseErr) - } - }() - // Run milenage - macA, macS := make([]byte, 8), make([]byte, 8) - CK, IK := make([]byte, 16), make([]byte, 16) - RES := make([]byte, 8) - AK, AKstar := make([]byte, 6), make([]byte, 6) + if err := milenage.F1(opc, k, rand, sqn, amf, res.macA, make([]byte, 8)); err != nil { + return res, err + } + if err := milenage.F2345(opc, k, rand, res.res, res.ck, res.ik, res.ak, make([]byte, 6)); err != nil { + return res, err + } - // Generate macA, macS - err = milenage.F1(opc, k, RAND, sqn, AMF, macA, macS) + return res, nil +} + +func buildAuthResponse(req models.AuthenticationInfoRequest, subs *models.AuthenticationSubscription, m milenageResult, supi string, randBytes []byte) (*models.AuthenticationInfoResult, *models.ProblemDetails) { + // FIX: errcheck for AMF and SQN decoding + amf, err := hex.DecodeString("8000") if err != nil { - logger.UeauLog.Errorln("milenage F1 err ", err) + return nil, util.ProblemDetailsSystemFailure("Internal error: AMF decode failed") } - // Generate RES, CK, IK, AK, AKstar - // RES == XRES (expected RES) for server - err = milenage.F2345(opc, k, RAND, RES, CK, IK, AK, AKstar) + sqnBytes, err := hex.DecodeString(strictHex(subs.SequenceNumber, 12)) if err != nil { - logger.UeauLog.Errorln("milenage F2345 err", err) + return nil, util.ProblemDetailsSystemFailure("Internal error: SQN decode failed") } - // Generate AUTN - SQNxorAK := make([]byte, 6) - for i := 0; i < len(sqn); i++ { - SQNxorAK[i] = sqn[i] ^ AK[i] + sqnXorAk := make([]byte, 6) + for i := 0; i < 6; i++ { + sqnXorAk[i] = sqnBytes[i] ^ m.ak[i] } - AUTN := append(append(SQNxorAK, AMF...), macA...) - logger.UeauLog.Infof("AUTN = %x", AUTN) + autn := append(append(sqnXorAk, amf...), m.macA...) - var av models.AuthenticationVector - if authSubs.AuthenticationMethod == models.AuthMethod__5_G_AKA { - response.AuthType = models.AuthType__5_G_AKA + av := &models.AuthenticationVector{Rand: hex.EncodeToString(randBytes), Autn: hex.EncodeToString(autn)} + result := &models.AuthenticationInfoResult{Supi: supi, AuthenticationVector: av} - // derive XRES* - key := append(CK, IK...) - FC := ueauth.FC_FOR_RES_STAR_XRES_STAR_DERIVATION - P0 := []byte(authInfoRequest.ServingNetworkName) - P1 := RAND - P2 := RES + key := append(m.ck, m.ik...) + snName := []byte(req.ServingNetworkName) - kdfValForXresStar, err := ueauth.GetKDFValue( - key, FC, P0, ueauth.KDFLen(P0), P1, ueauth.KDFLen(P1), P2, ueauth.KDFLen(P2)) + if subs.AuthenticationMethod == models.AuthMethod__5_G_AKA { + result.AuthType = models.AuthType__5_G_AKA + xresStar, err := ueauth.GetKDFValue(key, ueauth.FC_FOR_RES_STAR_XRES_STAR_DERIVATION, snName, ueauth.KDFLen(snName), randBytes, ueauth.KDFLen(randBytes), m.res, ueauth.KDFLen(m.res)) if err != nil { - logger.UeauLog.Error(err) + return nil, util.ProblemDetailsSystemFailure(err.Error()) } - xresStar := kdfValForXresStar[len(kdfValForXresStar)/2:] - - // derive Kausf - FC = ueauth.FC_FOR_KAUSF_DERIVATION - P0 = []byte(authInfoRequest.ServingNetworkName) - P1 = SQNxorAK - kdfValForKausf, err := ueauth.GetKDFValue(key, FC, P0, ueauth.KDFLen(P0), P1, ueauth.KDFLen(P1)) + kausf, err := ueauth.GetKDFValue(key, ueauth.FC_FOR_KAUSF_DERIVATION, snName, ueauth.KDFLen(snName), sqnXorAk, ueauth.KDFLen(sqnXorAk)) if err != nil { - logger.UeauLog.Error(err) + return nil, util.ProblemDetailsSystemFailure(err.Error()) } - - // Fill in rand, xresStar, autn, kausf - av.Rand = hex.EncodeToString(RAND) - av.XresStar = hex.EncodeToString(xresStar) - av.Autn = hex.EncodeToString(AUTN) - av.Kausf = hex.EncodeToString(kdfValForKausf) - } else { // EAP-AKA' - response.AuthType = models.AuthType_EAP_AKA_PRIME - - // derive CK' and IK' - key := append(CK, IK...) - FC := ueauth.FC_FOR_CK_PRIME_IK_PRIME_DERIVATION - P0 := []byte(authInfoRequest.ServingNetworkName) - P1 := SQNxorAK - kdfVal, err := ueauth.GetKDFValue(key, FC, P0, ueauth.KDFLen(P0), P1, ueauth.KDFLen(P1)) + av.XresStar = hex.EncodeToString(xresStar[len(xresStar)/2:]) + av.Kausf = hex.EncodeToString(kausf) + } else { + result.AuthType = models.AuthType_EAP_AKA_PRIME + kdf, err := ueauth.GetKDFValue(key, ueauth.FC_FOR_CK_PRIME_IK_PRIME_DERIVATION, snName, ueauth.KDFLen(snName), sqnXorAk, ueauth.KDFLen(sqnXorAk)) if err != nil { - logger.UeauLog.Error(err) + return nil, util.ProblemDetailsSystemFailure(err.Error()) } - - // For TS 35.208 test set 19 & RFC 5448 test vector 1 - // CK': 0093 962d 0dd8 4aa5 684b 045c 9edf fa04 - // IK': ccfc 230c a74f cc96 c0a5 d611 64f5 a76 - - ckPrime := kdfVal[:len(kdfVal)/2] - ikPrime := kdfVal[len(kdfVal)/2:] - - // Fill in rand, xres, autn, ckPrime, ikPrime - av.Rand = hex.EncodeToString(RAND) - av.Xres = hex.EncodeToString(RES) - av.Autn = hex.EncodeToString(AUTN) - av.CkPrime = hex.EncodeToString(ckPrime) - av.IkPrime = hex.EncodeToString(ikPrime) + av.Xres = hex.EncodeToString(m.res) + av.CkPrime = hex.EncodeToString(kdf[:16]) + av.IkPrime = hex.EncodeToString(kdf[16:]) } + return result, nil +} - response.AuthenticationVector = &av - response.Supi = supi - return response, nil +func mapUdrErrorToProblemDetails(res *http.Response, err error) *models.ProblemDetails { + pd := &models.ProblemDetails{Detail: err.Error(), Status: http.StatusForbidden, Cause: authenticationRejected} + if res != nil { + switch res.StatusCode { + case http.StatusNotFound: + pd.Status, pd.Cause = http.StatusNotFound, userNotFoundError + case http.StatusForbidden: + pd.Status, pd.Cause = http.StatusForbidden, authenticationRejected + default: + pd.Status = http.StatusInternalServerError + } + } + return pd } // New handler for the PUT request to delete an authentication event. diff --git a/producer/subscriber_data_management.go b/producer/subscriber_data_management.go index 44f9ac6..1c3af49 100644 --- a/producer/subscriber_data_management.go +++ b/producer/subscriber_data_management.go @@ -22,25 +22,40 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const ( + queryPlmnID = "plmn-id" + querySupportedFeatures = "supported-features" + metricAmData = "am-data" + metricIdTranslation = "id-translation-result" + metricSharedData = "shared-data" + metricSmData = "sm-data" + metricSmfSelectData = "smf-select-data" + metricSharedDataSubs = "shared-data-subscriptions" + metricSdmSubs = "sdm-subscriptions" + metricTraceData = "trace-data" + metricUeCtxInSmf = "ue-context-in-smf-data" + errQueryAmDataClose = "QueryAmData response body cannot close: %+v" +) + func HandleGetAmDataRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetAmData") supi := request.Params["supi"] - plmnID := request.Query.Get("plmn-id") - supportedFeatures := request.Query.Get("supported-features") + plmnID := request.Query.Get(queryPlmnID) + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getAmDataProcedure(supi, plmnID, supportedFeatures) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "am-data", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricAmData, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "am-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricAmData, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "am-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricAmData, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -74,7 +89,7 @@ func getAmDataProcedure(supi string, plmnID string, supportedFeatures string) ( } defer func() { if rspCloseErr := res.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QueryAmData response body cannot close: %+v", rspCloseErr) + logger.SdmLog.Errorf(errQueryAmDataClose, rspCloseErr) } }() @@ -96,18 +111,18 @@ func HandleGetIdTranslationResultRequest(request *httpwrapper.Request) *httpwrap gpsi := request.Params["gpsi"] response, problemDetails := getIdTranslationResultProcedure(gpsi) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "id-translation-result", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricIdTranslation, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "id-translation-result", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricIdTranslation, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "id-translation-result", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricIdTranslation, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -173,9 +188,9 @@ func getIdTranslationResultProcedure(gpsi string) (response *models.IdTranslatio func HandleGetSupiRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetSupiRequest") supi := request.Params["supi"] - plmnID := request.Query.Get("plmn-id") + plmnID := request.Query.Get(queryPlmnID) dataSetNames := request.Query["dataset-names"] - supportedFeatures := request.Query.Get("supported-features") + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getSupiProcedure(supi, plmnID, dataSetNames, supportedFeatures) if response != nil { stats.IncrementUdmSubscriberDataManagementStats("get", "supi", "SUCCESS") @@ -201,255 +216,205 @@ func getSupiProcedure(supi string, plmnID string, dataSetNames []string, support return nil, util.ProblemDetailsSystemFailure(err.Error()) } - var subscriptionDataSets, subsDataSetBody models.SubscriptionDataSets - var ueContextInSmfDataResp models.UeContextInSmfData - pduSessionMap := make(map[string]models.PduSession) - var pgwInfoArray []models.PgwInfo + response = &models.SubscriptionDataSets{} - var queryAmDataParamOpts Nudr.QueryAmDataParamOpts - queryAmDataParamOpts.SupportedFeatures = optional.NewString(supportedFeatures) - var querySmfSelectDataParamOpts Nudr.QuerySmfSelectDataParamOpts - var queryTraceDataParamOpts Nudr.QueryTraceDataParamOpts - var querySmDataParamOpts Nudr.QuerySmDataParamOpts + // 1. Access and Mobility Data + if prob := fetchAmData(clientAPI, supi, plmnID, supportedFeatures, response); prob != nil { + return nil, prob + } - queryAmDataParamOpts.SupportedFeatures = optional.NewString(supportedFeatures) - querySmfSelectDataParamOpts.SupportedFeatures = optional.NewString(supportedFeatures) - udm_context.UDM_Self().CreateSubsDataSetsForUe(supi, subsDataSetBody) - - var body models.AccessAndMobilitySubscriptionData - udm_context.UDM_Self().CreateAccessMobilitySubsDataForUe(supi, body) - amData, res1, err1 := clientAPI.AccessAndMobilitySubscriptionDataDocumentApi.QueryAmData( - context.Background(), supi, plmnID, &queryAmDataParamOpts) - if err1 != nil { - if res1 == nil { - logger.SdmLog.Errorln(err1.Error()) - } else if err1.Error() != res1.Status { - logger.SdmLog.Errorln(err1.Error()) - } else { - problemDetails = &models.ProblemDetails{ - Status: int32(res1.StatusCode), - Cause: err1.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, - Detail: err1.Error(), - } + // 2. SMF Selection Data + if prob := fetchSmfSelectData(clientAPI, supi, plmnID, supportedFeatures, response); prob != nil { + return nil, prob + } - return nil, problemDetails - } + // 3. Trace Data + if prob := fetchTraceData(clientAPI, supi, plmnID, response); prob != nil { + return nil, prob } - defer func() { - if rspCloseErr := res1.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QueryAmData response body cannot close: %+v", rspCloseErr) - } - }() - if res1.StatusCode == http.StatusOK { - udmUe := udm_context.UDM_Self().NewUdmUe(supi) - udmUe.SetAMSubsriptionData(&amData) - subscriptionDataSets.AmData = &amData - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusNotFound, - Cause: "DATA_NOT_FOUND", - } - return nil, problemDetails + // 4. Session Management Data + if prob := fetchSmData(clientAPI, supi, plmnID, response); prob != nil { + return nil, prob } - var smfSelSubsbody models.SmfSelectionSubscriptionData - udm_context.UDM_Self().CreateSmfSelectionSubsDataforUe(supi, smfSelSubsbody) - smfSelData, res2, err2 := clientAPI.SMFSelectionSubscriptionDataDocumentApi.QuerySmfSelectData(context.Background(), - supi, plmnID, &querySmfSelectDataParamOpts) - if err2 != nil { - if res2 == nil { - logger.SdmLog.Errorln(err2.Error()) - } else if err2.Error() != res2.Status { - logger.SdmLog.Errorln(err2.Error()) - } else { - problemDetails = &models.ProblemDetails{ - Status: int32(res2.StatusCode), - Cause: err2.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, - Detail: err2.Error(), - } + // 5. UE Context in SMF Data + if prob := fetchUeContextInSmfData(clientAPI, supi, supportedFeatures, response); prob != nil { + return nil, prob + } - return nil, problemDetails + return response, nil +} + +// Update handleUdrResponse: Remove the internal defer Close() so it doesn't conflict +func handleUdrResponse(res *http.Response, err error, contextStr string) *models.ProblemDetails { + if err != nil { + if res == nil || err.Error() != res.Status { + logger.SdmLog.Errorln(err.Error()) + return util.ProblemDetailsSystemFailure(err.Error()) } - } - defer func() { - if rspCloseErr := res2.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QuerySmfSelectData response body cannot close: %+v", rspCloseErr) + return &models.ProblemDetails{ + Status: int32(res.StatusCode), + Cause: err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, + Detail: err.Error(), } - }() - if res2.StatusCode == http.StatusOK { - udmUe := udm_context.UDM_Self().NewUdmUe(supi) - udmUe.SetSmfSelectionSubsData(&smfSelData) - subscriptionDataSets.SmfSelData = &smfSelData - } else { - problemDetails = &models.ProblemDetails{ + } + if res == nil || res.StatusCode != http.StatusOK { + return &models.ProblemDetails{ Status: http.StatusNotFound, Cause: "DATA_NOT_FOUND", } - - return nil, problemDetails } + return nil +} - var TraceDatabody models.TraceData - udm_context.UDM_Self().CreateTraceDataforUe(supi, TraceDatabody) - traceData, res3, err3 := clientAPI.TraceDataDocumentApi.QueryTraceData( - context.Background(), supi, plmnID, &queryTraceDataParamOpts) - if err3 != nil { - if res3 == nil { - logger.SdmLog.Errorln(err3.Error()) - } else if err3.Error() != res3.Status { - logger.SdmLog.Errorln(err3.Error()) - } else { - problemDetails = &models.ProblemDetails{ - Status: int32(res3.StatusCode), - Cause: err3.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, - Detail: err3.Error(), +func fetchAmData(client *Nudr.APIClient, supi, plmn, feat string, ds *models.SubscriptionDataSets) *models.ProblemDetails { + opts := &Nudr.QueryAmDataParamOpts{SupportedFeatures: optional.NewString(feat)} + data, res, err := client.AccessAndMobilitySubscriptionDataDocumentApi.QueryAmData(context.Background(), supi, plmn, opts) + if res != nil { + defer func() { + if cerr := res.Body.Close(); cerr != nil { + logger.SdmLog.Errorf("QueryAmData response body cannot close: %+v", cerr) } - } - return nil, problemDetails + }() } - defer func() { - if rspCloseErr := res3.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QueryTraceData response body cannot close: %+v", rspCloseErr) - } - }() - if res3.StatusCode == http.StatusOK { - udmUe := udm_context.UDM_Self().NewUdmUe(supi) - udmUe.TraceData = &traceData - udmUe.TraceDataResponse.TraceData = &traceData - subscriptionDataSets.TraceData = &traceData - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusNotFound, - Cause: "DATA_NOT_FOUND", - } - - return nil, problemDetails + if prob := handleUdrResponse(res, err, "QueryAmData"); prob != nil { + return prob } - sessionManagementSubscriptionData, res4, err4 := clientAPI.SessionManagementSubscriptionDataApi. - QuerySmData(context.Background(), supi, plmnID, &querySmDataParamOpts) - if err4 != nil { - if res4 == nil { - logger.SdmLog.Errorln(err4.Error()) - } else if err4.Error() != res4.Status { - logger.SdmLog.Errorln(err4.Error()) - } else { - problemDetails = &models.ProblemDetails{ - Status: int32(res4.StatusCode), - Cause: err4.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, - Detail: err4.Error(), - } + udmUe := udm_context.UDM_Self().NewUdmUe(supi) + udmUe.SetAMSubsriptionData(&data) + ds.AmData = &data + return nil +} - return nil, problemDetails - } +func fetchSmfSelectData(client *Nudr.APIClient, supi, plmn, feat string, ds *models.SubscriptionDataSets) *models.ProblemDetails { + opts := &Nudr.QuerySmfSelectDataParamOpts{SupportedFeatures: optional.NewString(feat)} + data, res, err := client.SMFSelectionSubscriptionDataDocumentApi.QuerySmfSelectData(context.Background(), supi, plmn, opts) + if res != nil { + defer func() { + if cerr := res.Body.Close(); cerr != nil { + logger.SdmLog.Errorf("QuerySmfSelectData response body cannot close: %+v", cerr) + } + }() } - defer func() { - if rspCloseErr := res4.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QuerySmData response body cannot close: %+v", rspCloseErr) - } - }() - if res4.StatusCode == http.StatusOK { - udmUe := udm_context.UDM_Self().NewUdmUe(supi) - smData, _, _, _ := udm_context.UDM_Self().ManageSmData(sessionManagementSubscriptionData, "", "") - udmUe.SetSMSubsData(smData) - subscriptionDataSets.SmData = sessionManagementSubscriptionData - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusNotFound, - Cause: "DATA_NOT_FOUND", - } - - return nil, problemDetails + if prob := handleUdrResponse(res, err, "QuerySmfSelectData"); prob != nil { + return prob } - var UeContextInSmfbody models.UeContextInSmfData - var querySmfRegListParamOpts Nudr.QuerySmfRegListParamOpts - querySmfRegListParamOpts.SupportedFeatures = optional.NewString(supportedFeatures) - udm_context.UDM_Self().CreateUeContextInSmfDataforUe(supi, UeContextInSmfbody) - pdusess, res, err := clientAPI.SMFRegistrationsCollectionApi.QuerySmfRegList( - context.Background(), supi, &querySmfRegListParamOpts) - if err != nil { - if res == nil { - logger.SdmLog.Errorln(err.Error()) - } else if err.Error() != res.Status { - logger.SdmLog.Errorln(err.Error()) - } else { - problemDetails = &models.ProblemDetails{ - Status: int32(res.StatusCode), - Cause: err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, - Detail: err.Error(), + udmUe := udm_context.UDM_Self().NewUdmUe(supi) + udmUe.SetSmfSelectionSubsData(&data) + ds.SmfSelData = &data + return nil +} + +func fetchTraceData(client *Nudr.APIClient, supi, plmn string, ds *models.SubscriptionDataSets) *models.ProblemDetails { + data, res, err := client.TraceDataDocumentApi.QueryTraceData(context.Background(), supi, plmn, nil) + if res != nil { + defer func() { + if cerr := res.Body.Close(); cerr != nil { + logger.SdmLog.Errorf("QueryTraceData response body cannot close: %+v", cerr) } + }() + } + if prob := handleUdrResponse(res, err, "QueryTraceData"); prob != nil { + return prob + } - return nil, problemDetails - } + udmUe := udm_context.UDM_Self().NewUdmUe(supi) + udmUe.TraceData = &data + udmUe.TraceDataResponse.TraceData = &data + ds.TraceData = &data + return nil +} + +func fetchSmData(client *Nudr.APIClient, supi, plmn string, ds *models.SubscriptionDataSets) *models.ProblemDetails { + data, res, err := client.SessionManagementSubscriptionDataApi.QuerySmData(context.Background(), supi, plmn, nil) + if res != nil { + defer func() { + if cerr := res.Body.Close(); cerr != nil { + logger.SdmLog.Errorf("QuerySmData response body cannot close: %+v", cerr) + } + }() + } + if prob := handleUdrResponse(res, err, "QuerySmData"); prob != nil { + return prob } - defer func() { - if rspCloseErr := res.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QuerySmfRegList response body cannot close: %+v", rspCloseErr) - } - }() - for _, element := range pdusess { - var pduSession models.PduSession - pduSession.Dnn = element.Dnn - pduSession.SmfInstanceId = element.SmfInstanceId - pduSession.PlmnId = element.PlmnId - pduSessionMap[strconv.Itoa(int(element.PduSessionId))] = pduSession + udmUe := udm_context.UDM_Self().NewUdmUe(supi) + // Fix dogsled: Check the return values instead of using 3 blank identifiers + smData, snssai, dnnByDnn, allDnns := udm_context.UDM_Self().ManageSmData(data, "", "") + _ = snssai // bypass unused if necessary + _ = dnnByDnn // bypass unused if necessary + _ = allDnns // bypass unused if necessary + + udmUe.SetSMSubsData(smData) + ds.SmData = data + return nil +} + +func fetchUeContextInSmfData(client *Nudr.APIClient, supi, feat string, ds *models.SubscriptionDataSets) *models.ProblemDetails { + opts := &Nudr.QuerySmfRegListParamOpts{SupportedFeatures: optional.NewString(feat)} + pdusess, res, err := client.SMFRegistrationsCollectionApi.QuerySmfRegList(context.Background(), supi, opts) + + // FIX: bodyclose violation + if res != nil { + defer func() { + if cerr := res.Body.Close(); cerr != nil { + logger.SdmLog.Errorf("QuerySmfRegList response body cannot close: %+v", cerr) + } + }() } - ueContextInSmfDataResp.PduSessions = pduSessionMap - for _, element := range pdusess { - var pgwInfo models.PgwInfo - pgwInfo.Dnn = element.Dnn - pgwInfo.PgwFqdn = element.PgwFqdn - pgwInfo.PlmnId = element.PlmnId - pgwInfoArray = append(pgwInfoArray, pgwInfo) + if prob := handleUdrResponse(res, err, "QuerySmfRegList"); prob != nil { + return prob } - ueContextInSmfDataResp.PgwInfo = pgwInfoArray - if res.StatusCode == http.StatusOK { - udmUe := udm_context.UDM_Self().NewUdmUe(supi) - udmUe.UeCtxtInSmfData = &ueContextInSmfDataResp - } else { - var problemDetails models.ProblemDetails - problemDetails.Cause = "DATA_NOT_FOUND" - logger.SdmLog.Errorln(problemDetails.Cause) + ueCtx := &models.UeContextInSmfData{ + PduSessions: make(map[string]models.PduSession), + PgwInfo: []models.PgwInfo{}, } - if (res.StatusCode == http.StatusOK) && (res1.StatusCode == http.StatusOK) && - (res2.StatusCode == http.StatusOK) && (res3.StatusCode == http.StatusOK) && - (res4.StatusCode == http.StatusOK) { - subscriptionDataSets.UecSmfData = &ueContextInSmfDataResp - return &subscriptionDataSets, nil - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusNotFound, - Cause: "DATA_NOT_FOUND", + for _, element := range pdusess { + pduSession := models.PduSession{ + Dnn: element.Dnn, + SmfInstanceId: element.SmfInstanceId, + PlmnId: element.PlmnId, } + ueCtx.PduSessions[strconv.Itoa(int(element.PduSessionId))] = pduSession - return nil, problemDetails + pgwInfo := models.PgwInfo{ + Dnn: element.Dnn, + PgwFqdn: element.PgwFqdn, + PlmnId: element.PlmnId, + } + ueCtx.PgwInfo = append(ueCtx.PgwInfo, pgwInfo) } + + udmUe := udm_context.UDM_Self().NewUdmUe(supi) + udmUe.UeCtxtInSmfData = ueCtx + ds.UecSmfData = ueCtx + return nil } func HandleGetSharedDataRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetSharedData") sharedDataIds := request.Query["sharedDataIds"] - supportedFeatures := request.Query.Get("supported-features") + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getSharedDataProcedure(sharedDataIds, supportedFeatures) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "shared-data", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSharedData, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "shared-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSharedData, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "shared-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSharedData, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -504,109 +469,125 @@ func getSharedDataProcedure(sharedDataIds []string, supportedFeatures string) ( func HandleGetSmDataRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetSmData") supi := request.Params["supi"] - plmnID := request.Query.Get("plmn-id") + plmnID := request.Query.Get(queryPlmnID) Dnn := request.Query.Get("dnn") Snssai := request.Query.Get("single-nssai") - supportedFeatures := request.Query.Get("supported-features") + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getSmDataProcedure(supi, plmnID, Dnn, Snssai, supportedFeatures) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "sm-data", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSmData, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "sm-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSmData, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "sm-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSmData, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } +// selectSmDataResponse determines which subset of Session Management data to return +// based on the presence of Snssai and Dnn query parameters. +func selectSmDataResponse(ue *udm_context.UdmUeContext, snssai, dnn, snssaiKey string, dnnConfigs []models.DnnConfiguration, allDnns []map[string]models.DnnConfiguration) interface{} { + // Acquire a read lock to safely access the UE context data + ue.SmSubsDataLock.RLock() + defer ue.SmSubsDataLock.RUnlock() + + switch { + // Case 1: Neither Snssai nor Dnn provided - return all DNN configurations across all slices + case snssai == "" && dnn == "": + return allDnns + + // Case 2: Only Snssai provided - return all DNN configurations for that specific slice + case snssai != "" && dnn == "": + return ue.SessionManagementSubsData[snssaiKey].DnnConfigurations + + // Case 3: Only Dnn provided - return configurations for that DNN across all slices where it exists + case snssai == "" && dnn != "": + return dnnConfigs + + // Case 4: Both Snssai and Dnn provided - return a flat list of matching subscription data + case snssai != "" && dnn != "": + rspSMSubDataList := make([]models.SessionManagementSubscriptionData, 0, len(ue.SessionManagementSubsData)) + for _, eachSMSubData := range ue.SessionManagementSubsData { + rspSMSubDataList = append(rspSMSubDataList, eachSMSubData) + } + return rspSMSubDataList + + // Default: Return the full map of session management subscription data + default: + return ue.SessionManagementSubsData + } +} + +// getSmDataProcedure retrieves Session Management subscription data from UDR and filters it. func getSmDataProcedure(supi string, plmnID string, Dnn string, Snssai string, supportedFeatures string) ( response interface{}, problemDetails *models.ProblemDetails, ) { logger.SdmLog.Infof("getSmDataProcedure: SUPI[%s] PLMNID[%s] DNN[%s] SNssai[%s]", supi, plmnID, Dnn, Snssai) + // Step 1: Initialize the UDR Client clientAPI, err := createUDMClientToUDR(supi) if err != nil { return nil, util.ProblemDetailsSystemFailure(err.Error()) } - var querySmDataParamOpts Nudr.QuerySmDataParamOpts - querySmDataParamOpts.SingleNssai = optional.NewInterface(Snssai) + // Step 2: Prepare query options + querySmDataParamOpts := Nudr.QuerySmDataParamOpts{ + SingleNssai: optional.NewInterface(Snssai), + } - sessionManagementSubscriptionDataResp, res, err := clientAPI.SessionManagementSubscriptionDataApi. + // Step 3: Communicate with UDR and handle potential protocol errors + sessionResp, res, err := clientAPI.SessionManagementSubscriptionDataApi. QuerySmData(context.Background(), supi, plmnID, &querySmDataParamOpts) if err != nil { - if res == nil { - logger.SdmLog.Warnln(err) - } else if err.Error() != res.Status { - logger.SdmLog.Warnln(err) - } else { - logger.SdmLog.Warnln(err) - problemDetails = &models.ProblemDetails{ + logger.SdmLog.Warnln(err) + // If the error matches the response status, it's a protocol-defined error (e.g., 4xx/5xx) + if res != nil && err.Error() == res.Status { + return nil, &models.ProblemDetails{ Status: int32(res.StatusCode), Cause: err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause, Detail: err.Error(), } + } + } - return nil, problemDetails + // Step 5: Validate Response Success + if res == nil || res.StatusCode != http.StatusOK { + return nil, &models.ProblemDetails{ + Status: http.StatusNotFound, + Cause: "DATA_NOT_FOUND", } } + + // Ensure the response body is closed after processing defer func() { if rspCloseErr := res.Body.Close(); rspCloseErr != nil { logger.SdmLog.Errorf("QuerySmData response body cannot close: %+v", rspCloseErr) } }() - if res.StatusCode == http.StatusOK { - udmUe := udm_context.UDM_Self().NewUdmUe(supi) - smData, snssaikey, AllDnnConfigsbyDnn, AllDnns := udm_context.UDM_Self().ManageSmData( - sessionManagementSubscriptionDataResp, Snssai, Dnn) - udmUe.SetSMSubsData(smData) + // Step 6: Update Local UDM Context + // NewUdmUe initializes or retrieves the UE context in memory + udmUe := udm_context.UDM_Self().NewUdmUe(supi) - rspSMSubDataList := make([]models.SessionManagementSubscriptionData, 0, 4) + // ManageSmData parses the UDR response into internal structures + smData, snssaiKey, allDnnByDnn, allDnns := udm_context.UDM_Self().ManageSmData(sessionResp, Snssai, Dnn) + udmUe.SetSMSubsData(smData) - udmUe.SmSubsDataLock.RLock() - for _, eachSMSubData := range udmUe.SessionManagementSubsData { - rspSMSubDataList = append(rspSMSubDataList, eachSMSubData) - } - udmUe.SmSubsDataLock.RUnlock() - - switch { - case Snssai == "" && Dnn == "": - return AllDnns, nil - case Snssai != "" && Dnn == "": - udmUe.SmSubsDataLock.RLock() - defer udmUe.SmSubsDataLock.RUnlock() - return udmUe.SessionManagementSubsData[snssaikey].DnnConfigurations, nil - case Snssai == "" && Dnn != "": - return AllDnnConfigsbyDnn, nil - case Snssai != "" && Dnn != "": - return rspSMSubDataList, nil - default: - udmUe.SmSubsDataLock.RLock() - defer udmUe.SmSubsDataLock.RUnlock() - return udmUe.SessionManagementSubsData, nil - } - } else { - problemDetails = &models.ProblemDetails{ - Status: http.StatusNotFound, - Cause: "DATA_NOT_FOUND", - } - - return nil, problemDetails - } + // Step 7: Select and return the appropriate data subset via the helper + return selectSmDataResponse(udmUe, Snssai, Dnn, snssaiKey, allDnnByDnn, allDnns), nil } func HandleGetNssaiRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetNssai") supi := request.Params["supi"] - plmnID := request.Query.Get("plmn-id") - supportedFeatures := request.Query.Get("supported-features") + plmnID := request.Query.Get(queryPlmnID) + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getNssaiProcedure(supi, plmnID, supportedFeatures) if response != nil { stats.IncrementUdmSubscriberDataManagementStats("get", "nssai", "SUCCESS") @@ -655,7 +636,7 @@ func getNssaiProcedure(supi string, plmnID string, supportedFeatures string) ( } defer func() { if rspCloseErr := res.Body.Close(); rspCloseErr != nil { - logger.SdmLog.Errorf("QueryAmData response body cannot close: %+v", rspCloseErr) + logger.SdmLog.Errorf(errQueryAmDataClose, rspCloseErr) } }() @@ -677,22 +658,22 @@ func getNssaiProcedure(supi string, plmnID string, supportedFeatures string) ( func HandleGetSmfSelectDataRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetSmfSelectData") supi := request.Params["supi"] - plmnID := request.Query.Get("plmn-id") - supportedFeatures := request.Query.Get("supported-features") + plmnID := request.Query.Get(queryPlmnID) + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getSmfSelectDataProcedure(supi, plmnID, supportedFeatures) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "smf-select-data", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSmfSelectData, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "smf-select-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSmfSelectData, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "smf-select-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricSmfSelectData, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -752,14 +733,14 @@ func HandleSubscribeToSharedDataRequest(request *httpwrapper.Request) *httpwrapp sdmSubscription := request.Body.(models.SdmSubscription) header, response, problemDetails := subscribeToSharedDataProcedure(&sdmSubscription) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("create", "shared-data-subscriptions", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("create", metricSharedDataSubs, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusCreated, header, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("create", "shared-data-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("create", metricSharedDataSubs, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmSubscriberDataManagementStats("create", "shared-data-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("create", metricSharedDataSubs, "FAILURE") return httpwrapper.NewResponse(http.StatusNotFound, nil, nil) } } @@ -822,14 +803,14 @@ func HandleSubscribeRequest(request *httpwrapper.Request) *httpwrapper.Response supi := request.Params["supi"] header, response, problemDetails := subscribeProcedure(&sdmSubscription, supi) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("create", "sdm-subscriptions", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("create", metricSdmSubs, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusCreated, header, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("create", "sdm-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("create", metricSdmSubs, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmSubscriberDataManagementStats("create", "sdm-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("create", metricSdmSubs, "FAILURE") return httpwrapper.NewResponse(http.StatusNotFound, nil, nil) } } @@ -895,10 +876,10 @@ func HandleUnsubscribeForSharedDataRequest(request *httpwrapper.Request) *httpwr subscriptionID := request.Params["subscriptionId"] problemDetails := unsubscribeForSharedDataProcedure(subscriptionID) if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("delete", "shared-data-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("delete", metricSharedDataSubs, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } - stats.IncrementUdmSubscriberDataManagementStats("delete", "shared-data-subscriptions", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("delete", metricSharedDataSubs, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } @@ -946,10 +927,10 @@ func HandleUnsubscribeRequest(request *httpwrapper.Request) *httpwrapper.Respons subscriptionID := request.Params["subscriptionId"] problemDetails := unsubscribeProcedure(supi, subscriptionID) if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("delete", "sdm-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("delete", metricSdmSubs, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } - stats.IncrementUdmSubscriberDataManagementStats("delete", "sdm-subscriptions", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("delete", metricSdmSubs, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } @@ -999,18 +980,18 @@ func HandleModifyRequest(request *httpwrapper.Request) *httpwrapper.Response { subscriptionID := request.Params["subscriptionId"] response, problemDetails := modifyProcedure(&sdmSubsModification, supi, subscriptionID) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("update", "sdm-subscriptions", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("update", metricSdmSubs, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("update", "sdm-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("update", metricSdmSubs, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("update", "sdm-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("update", metricSdmSubs, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -1067,18 +1048,18 @@ func HandleModifyForSharedDataRequest(request *httpwrapper.Request) *httpwrapper subscriptionID := request.Params["subscriptionId"] response, problemDetails := modifyForSharedDataProcedure(&sdmSubsModification, supi, subscriptionID) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("update", "shared-data-subscriptions", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("update", metricSharedDataSubs, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("update", "shared-data-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("update", metricSharedDataSubs, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("update", "shared-data-subscriptions", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("update", metricSharedDataSubs, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -1133,21 +1114,21 @@ func modifyForSharedDataProcedure(sdmSubsModification *models.SdmSubsModificatio func HandleGetTraceDataRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetTraceData") supi := request.Params["supi"] - plmnID := request.Query.Get("plmn-id") + plmnID := request.Query.Get(queryPlmnID) response, problemDetails := getTraceDataProcedure(supi, plmnID) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "trace-data", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricTraceData, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "trace-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricTraceData, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "trace-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricTraceData, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -1206,21 +1187,21 @@ func getTraceDataProcedure(supi string, plmnID string) ( func HandleGetUeContextInSmfDataRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.SdmLog.Infoln("handle GetUeContextInSmfData") supi := request.Params["supi"] - supportedFeatures := request.Query.Get("supported-features") + supportedFeatures := request.Query.Get(querySupportedFeatures) response, problemDetails := getUeContextInSmfDataProcedure(supi, supportedFeatures) if response != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "ue-context-in-smf-data", "SUCCESS") + stats.IncrementUdmSubscriberDataManagementStats("get", metricUeCtxInSmf, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmSubscriberDataManagementStats("get", "ue-context-in-smf-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricUeCtxInSmf, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmSubscriberDataManagementStats("get", "ue-context-in-smf-data", "FAILURE") + stats.IncrementUdmSubscriberDataManagementStats("get", metricUeCtxInSmf, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } diff --git a/producer/ue_context_management.go b/producer/ue_context_management.go index d73dc35..07472cb 100644 --- a/producer/ue_context_management.go +++ b/producer/ue_context_management.go @@ -26,6 +26,12 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const ( + uecmAmf3gppAccess = "amf-3gpp-access" + uecmAmfNon3gppAccess = "amf-non-3gpp-access" + uecmSmfRegistrations = "smf-registrations" +) + func createUDMClientToUDR(id string) (*Nudr_DataRepository.APIClient, error) { uri := getUdrURI(id) if uri == "" { @@ -40,36 +46,33 @@ func createUDMClientToUDR(id string) (*Nudr_DataRepository.APIClient, error) { func getUdrURI(id string) string { if strings.Contains(id, "imsi") || strings.Contains(id, "nai") { // supi + // Consolidation of SUPI logic: Find or create UE, then set URI once ue, ok := udmContext.UDM_Self().UdmUeFindBySupi(id) - if ok { - ue.UdrUri = consumer.SendNFInstancesUDR(id, consumer.NFDiscoveryToUDRParamSupi) - return ue.UdrUri - } else { + if !ok { ue = udmContext.UDM_Self().NewUdmUe(id) - ue.UdrUri = consumer.SendNFInstancesUDR(id, consumer.NFDiscoveryToUDRParamSupi) - return ue.UdrUri } + ue.UdrUri = consumer.SendNFInstancesUDR(id, consumer.NFDiscoveryToUDRParamSupi) + return ue.UdrUri } else if strings.Contains(id, "pei") { var udrURI string udmContext.UDM_Self().UdmUePool.Range(func(key, value interface{}) bool { ue := value.(*udmContext.UdmUeContext) - if ue.Amf3GppAccessRegistration != nil && ue.Amf3GppAccessRegistration.Pei == id { - ue.UdrUri = consumer.SendNFInstancesUDR(ue.Supi, consumer.NFDiscoveryToUDRParamSupi) - udrURI = ue.UdrUri - return false - } else if ue.AmfNon3GppAccessRegistration != nil && ue.AmfNon3GppAccessRegistration.Pei == id { + + // Consolidation of PEI logic: Check both 3GPP and Non-3GPP registrations + is3GppMatch := ue.Amf3GppAccessRegistration != nil && ue.Amf3GppAccessRegistration.Pei == id + isNon3GppMatch := ue.AmfNon3GppAccessRegistration != nil && ue.AmfNon3GppAccessRegistration.Pei == id + + if is3GppMatch || isNon3GppMatch { ue.UdrUri = consumer.SendNFInstancesUDR(ue.Supi, consumer.NFDiscoveryToUDRParamSupi) udrURI = ue.UdrUri - return false + return false // Stop iteration } - return true + return true // Continue iteration }) return udrURI } else if strings.Contains(id, "extgroupid") { - // extra group id return consumer.SendNFInstancesUDR(id, consumer.NFDiscoveryToUDRParamExtGroupId) } else if strings.Contains(id, "msisdn") || strings.Contains(id, "extid") { - // gpsi return consumer.SendNFInstancesUDR(id, consumer.NFDiscoveryToUDRParamGpsi) } return consumer.SendNFInstancesUDR("", consumer.NFDiscoveryToUDRParamNone) @@ -81,18 +84,18 @@ func HandleGetAmf3gppAccessRequest(request *httpwrapper.Request) *httpwrapper.Re supportedFeatures := request.Query.Get("supported-features") response, problemDetails := GetAmf3gppAccessProcedure(ueID, supportedFeatures) if response != nil { - stats.IncrementUdmUeContextManagementStats("get", "amf-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("get", uecmAmf3gppAccess, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("get", "amf-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("get", uecmAmf3gppAccess, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmUeContextManagementStats("get", "amf-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("get", uecmAmf3gppAccess, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -134,18 +137,18 @@ func HandleGetAmfNon3gppAccessRequest(request *httpwrapper.Request) *httpwrapper queryAmfContextNon3gppParamOpts.SupportedFeatures = optional.NewString(supportedFeatures) response, problemDetails := GetAmfNon3gppAccessProcedure(queryAmfContextNon3gppParamOpts, ueId) if response != nil { - stats.IncrementUdmUeContextManagementStats("get", "amf-non-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("get", uecmAmfNon3gppAccess, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusOK, nil, response) } else if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("get", "amf-non-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("get", uecmAmfNon3gppAccess, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } - stats.IncrementUdmUeContextManagementStats("get", "amf-non-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("get", uecmAmfNon3gppAccess, "FAILURE") return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) } @@ -184,14 +187,14 @@ func HandleRegistrationAmf3gppAccessRequest(request *httpwrapper.Request) *httpw logger.UecmLog.Info("UEID: ", ueID) header, response, problemDetails := RegistrationAmf3gppAccessProcedure(registerRequest, ueID) if response != nil { - stats.IncrementUdmUeContextManagementStats("create", "amf-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("create", uecmAmf3gppAccess, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusCreated, header, response) } else if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("create", "amf-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("create", uecmAmf3gppAccess, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmUeContextManagementStats("create", "amf-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("create", uecmAmf3gppAccess, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } } @@ -265,14 +268,14 @@ func HandleRegisterAmfNon3gppAccessRequest(request *httpwrapper.Request) *httpwr ueID := request.Params["ueId"] header, response, problemDetails := RegisterAmfNon3gppAccessProcedure(registerRequest, ueID) if response != nil { - stats.IncrementUdmUeContextManagementStats("create", "amf-non-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("create", uecmAmfNon3gppAccess, "SUCCESS") // status code is based on SPEC, and option headers return httpwrapper.NewResponse(http.StatusCreated, header, response) } else if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("create", "amf-non-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("create", uecmAmfNon3gppAccess, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmUeContextManagementStats("create", "amf-non-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("create", uecmAmfNon3gppAccess, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } } @@ -338,10 +341,10 @@ func HandleUpdateAmf3gppAccessRequest(request *httpwrapper.Request) *httpwrapper ueID := request.Params["ueId"] problemDetails := UpdateAmf3gppAccessProcedure(amf3GppAccessRegistrationModification, ueID) if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("update", "amf-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("update", uecmAmf3gppAccess, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmUeContextManagementStats("update", "amf-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("update", uecmAmf3gppAccess, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } } @@ -445,10 +448,10 @@ func HandleUpdateAmfNon3gppAccessRequest(request *httpwrapper.Request) *httpwrap ueID := request.Params["ueId"] problemDetails := UpdateAmfNon3gppAccessProcedure(requestMSG, ueID) if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("update", "amf-non-3gpp-access", "FAILURE") + stats.IncrementUdmUeContextManagementStats("update", uecmAmfNon3gppAccess, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmUeContextManagementStats("update", "amf-non-3gpp-access", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("update", uecmAmfNon3gppAccess, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } } @@ -550,10 +553,10 @@ func HandleDeregistrationSmfRegistrations(request *httpwrapper.Request) *httpwra pduSessionID := request.Params["pduSessionId"] problemDetails := DeregistrationSmfRegistrationsProcedure(ueID, pduSessionID) if problemDetails != nil { - stats.IncrementUdmUeContextManagementStats("delete", "smf-registrations", "FAILURE") + stats.IncrementUdmUeContextManagementStats("delete", uecmSmfRegistrations, "FAILURE") return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } else { - stats.IncrementUdmUeContextManagementStats("delete", "smf-registrations", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("delete", uecmSmfRegistrations, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } } @@ -598,11 +601,11 @@ func HandleRegistrationSmfRegistrationsRequest(request *httpwrapper.Request) *ht } if response != nil { - stats.IncrementUdmUeContextManagementStats("create", "smf-registrations", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("create", uecmSmfRegistrations, "SUCCESS") return httpwrapper.NewResponse(http.StatusCreated, header, response) } - stats.IncrementUdmUeContextManagementStats("create", "smf-registrations", "SUCCESS") + stats.IncrementUdmUeContextManagementStats("create", uecmSmfRegistrations, "SUCCESS") return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } diff --git a/service/init.go b/service/init.go index 6f6afcf..c9f754c 100644 --- a/service/init.go +++ b/service/init.go @@ -50,6 +50,11 @@ func init() { ConfigPodTrigger = make(chan bool) } +const ( + msgSendConfigTrigger = "send config trigger to main routine" + errUpdateNrf = "UDM update to NRF Error[%s]" +) + type ( // Config information. Config struct { @@ -114,55 +119,87 @@ func (udm *UDM) Initialize(c *cli.Command) error { // manageGrpcClient connects the config pod GRPC server and subscribes the config changes. // Then it updates UDM configuration. +// checkClientHealth monitors connectivity and handles cleanup if the client is unreachable for too long. +func checkClientHealth(client grpcClient.ConfClient, count *int) bool { + if client.CheckGrpcConnectivity() == "READY" { + *count = 0 // Reset counter on successful connection + return true + } + + // Not READY: Wait and increment retry counter + logger.InitLog.Infoln("checking the connectivity readiness") + time.Sleep(time.Second * 30) + *count++ + + if *count > 5 { + if err := client.GetConfigClientConn().Close(); err != nil { + logger.InitLog.Infof("failing ConfigClient is not closed properly: %+v", err) + } + *count = 0 + return false // Signal that client should be reset + } + return true // Stay in current state, try again next iteration +} + +// ensureConfigSubscription ensures the GRPC stream and config channel are initialized. +func ensureConfigSubscription( + client grpcClient.ConfClient, + stream *protos.ConfigService_NetworkSliceSubscribeClient, + configChannel *chan *protos.NetworkSliceResponse, + udm *UDM, +) { + var err error + // 1. Ensure Stream + if *stream == nil { + *stream, err = client.SubscribeToConfigServer() + if err != nil { + logger.InitLog.Infof("failing SubscribeToConfigServer: %+v", err) + return + } + } + + // 2. Ensure Channel and Start Observer + if *configChannel == nil { + *configChannel = client.PublishOnConfigChange(true, *stream) + logger.InitLog.Infoln("PublishOnConfigChange is triggered") + go udm.updateConfig(*configChannel) + logger.InitLog.Infoln("UDM updateConfig is triggered") + } +} + +// manageGrpcClient connects the config pod GRPC server and subscribes the config changes. func manageGrpcClient(webuiUri string, udm *UDM) { var configChannel chan *protos.NetworkSliceResponse var client grpcClient.ConfClient var stream protos.ConfigService_NetworkSliceSubscribeClient - var err error count := 0 - for { - if client != nil { - if client.CheckGrpcConnectivity() != "READY" { - time.Sleep(time.Second * 30) - count++ - if count > 5 { - err = client.GetConfigClientConn().Close() - if err != nil { - logger.InitLog.Infof("failing ConfigClient is not closed properly: %+v", err) - } - client = nil - count = 0 - } - logger.InitLog.Infoln("checking the connectivity readiness") - continue - } - if stream == nil { - stream, err = client.SubscribeToConfigServer() - if err != nil { - logger.InitLog.Infof("failing SubscribeToConfigServer: %+v", err) - continue - } - } - - if configChannel == nil { - configChannel = client.PublishOnConfigChange(true, stream) - logger.InitLog.Infoln("PublishOnConfigChange is triggered") - go udm.updateConfig(configChannel) - logger.InitLog.Infoln("UDM updateConfig is triggered") - } - - time.Sleep(time.Second * 5) // Fixes (avoids) 100% CPU utilization - } else { + for { + // State: Disconnected - Attempt to connect + if client == nil { + logger.InitLog.Infoln("connecting to config server") + var err error client, err = grpcClient.ConnectToConfigServer(webuiUri) stream = nil configChannel = nil - logger.InitLog.Infoln("connecting to config server") if err != nil { - logger.InitLog.Errorf("%+v", err) + logger.InitLog.Errorf("connection failed: %+v", err) + time.Sleep(time.Second * 5) // Backoff before retrying } continue } + + // State: Connected - Check Health + if !checkClientHealth(client, &count) { + client = nil // Trigger reconnection in next loop iteration + continue + } + + // State: Healthy - Ensure Subscriptions are active + ensureConfigSubscription(client, &stream, &configChannel, udm) + + // Heartbeat sleep to prevent 100% CPU usage + time.Sleep(time.Second * 5) } } @@ -330,56 +367,63 @@ func (udm *UDM) Terminate() { logger.InitLog.Infoln("UDM terminated") } +// addPlmnFromSlice extracts PLMN information from a network slice and adds it to the context if it is unique. +func addPlmnFromSlice(self *context.UDMContext, ns *protos.NetworkSlice) { + if ns.Site == nil || ns.Site.Plmn == nil { + return + } + + site := ns.Site + newPlmn := site.Plmn + + // Check for duplicates in the current PlmnList + for _, item := range self.PlmnList { + if item.PlmnId.Mcc == newPlmn.Mcc && item.PlmnId.Mnc == newPlmn.Mnc { + return + } + } + + // Add unique PLMN to context + self.PlmnList = append(self.PlmnList, factory.PlmnSupportItem{ + PlmnId: models.PlmnId{ + Mcc: newPlmn.Mcc, + Mnc: newPlmn.Mnc, + }, + }) + logger.GrpcLog.Infof("plmn [%s:%s] added in the context", newPlmn.Mcc, newPlmn.Mnc) +} + +// updateConfigTriggerState manages the minConfig state and notifies the main routine of changes. +func updateConfigTriggerState(minConfig *bool, plmnCount int) { + hasPlmns := plmnCount > 0 + + // State Machine: Trigger only if first config is received or if we are already in a configured state + if !*minConfig && hasPlmns { + *minConfig = true + ConfigPodTrigger <- true + logger.GrpcLog.Infoln(msgSendConfigTrigger) + } else if *minConfig { + *minConfig = hasPlmns + ConfigPodTrigger <- hasPlmns + logger.GrpcLog.Infoln(msgSendConfigTrigger) + } +} + func (udm *UDM) updateConfig(commChannel chan *protos.NetworkSliceResponse) bool { var minConfig bool self := context.UDM_Self() + for rsp := range commChannel { logger.GrpcLog.Infoln("received updateConfig in the udm app:", rsp) + + // Process each slice to update the PLMN list for _, ns := range rsp.NetworkSlice { logger.GrpcLog.Infoln("network Slice Name", ns.Name) - if ns.Site != nil { - temp := factory.PlmnSupportItem{} - found := false - logger.GrpcLog.Infoln("network Slice has site name present ") - site := ns.Site - logger.GrpcLog.Infoln("site name", site.SiteName) - if site.Plmn != nil { - temp.PlmnId.Mcc = site.Plmn.Mcc - temp.PlmnId.Mnc = site.Plmn.Mnc - logger.GrpcLog.Infoln("plmn mcc", site.Plmn.Mcc) - for _, item := range self.PlmnList { - if item.PlmnId.Mcc == temp.PlmnId.Mcc && item.PlmnId.Mnc == temp.PlmnId.Mnc { - found = true - break - } - } - if !found { - self.PlmnList = append(self.PlmnList, temp) - logger.GrpcLog.Infoln("plmn added in the context", self.PlmnList) - } - } else { - logger.GrpcLog.Infoln("plmn not present in the message") - } - } - } - if !minConfig { - // first slice Created - if len(self.PlmnList) > 0 { - minConfig = true - ConfigPodTrigger <- true - logger.GrpcLog.Infoln("send config trigger to main routine") - } - } else { - // all slices deleted - if len(self.PlmnList) == 0 { - minConfig = false - ConfigPodTrigger <- false - logger.GrpcLog.Infoln("send config trigger to main routine") - } else { - ConfigPodTrigger <- true - logger.GrpcLog.Infoln("send config trigger to main routine") - } + addPlmnFromSlice(self, ns) } + + // Update the trigger status based on the current PlmnList size + updateConfigTriggerState(&minConfig, len(self.PlmnList)) } return true } @@ -443,14 +487,14 @@ func (udm *UDM) UpdateNF() { // register with NRF full profile nfProfile, err = udm.BuildAndSendRegisterNFInstance() if err != nil { - logger.InitLog.Errorf("UDM update to NRF Error[%s]", err.Error()) + logger.InitLog.Errorf(errUpdateNrf, err.Error()) } } } else if err != nil { - logger.InitLog.Errorf("UDM update to NRF Error[%s]", err.Error()) + logger.InitLog.Errorf(errUpdateNrf, err.Error()) nfProfile, err = udm.BuildAndSendRegisterNFInstance() if err != nil { - logger.InitLog.Errorf("UDM update to NRF Error[%s]", err.Error()) + logger.InitLog.Errorf(errUpdateNrf, err.Error()) } } diff --git a/subscribecallback/api_nf_subscribe_notify.go b/subscribecallback/api_nf_subscribe_notify.go index 841fbe3..17e8477 100644 --- a/subscribecallback/api_nf_subscribe_notify.go +++ b/subscribecallback/api_nf_subscribe_notify.go @@ -18,6 +18,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + func HTTPNfSubscriptionStatusNotify(c *gin.Context) { var nfSubscriptionStatusNotification models.NotificationData @@ -34,7 +36,7 @@ func HTTPNfSubscriptionStatusNotify(c *gin.Context) { return } - err = openapi.Deserialize(&nfSubscriptionStatusNotification, requestBody, "application/json") + err = openapi.Deserialize(&nfSubscriptionStatusNotification, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -51,7 +53,7 @@ func HTTPNfSubscriptionStatusNotify(c *gin.Context) { rsp := producer.HandleNfSubscriptionStatusNotify(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.CallbackLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -61,6 +63,6 @@ func HTTPNfSubscriptionStatusNotify(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else if rsp.Body != nil { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/subscriberdatamanagement/api_access_and_mobility_subscription_data_retrieval.go b/subscriberdatamanagement/api_access_and_mobility_subscription_data_retrieval.go index d3ba9a6..29f1cb6 100644 --- a/subscriberdatamanagement/api_access_and_mobility_subscription_data_retrieval.go +++ b/subscriberdatamanagement/api_access_and_mobility_subscription_data_retrieval.go @@ -25,12 +25,14 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const queryPlmnID = "plmn-id" + // GetAmData - retrieve a UE's Access and Mobility Subscription Data func HTTPGetAmData(c *gin.Context) { req := httpwrapper.NewRequest(c.Request, nil) req.Params["supi"] = c.Params.ByName("supi") - req.Query.Set("plmn-id", c.Query("plmn-id")) - req.Query.Set("supported-features", c.Query("plmn-id")) + req.Query.Set(queryPlmnID, c.Query(queryPlmnID)) + req.Query.Set("supported-features", c.Query(queryPlmnID)) rsp := producer.HandleGetAmDataRequest(req) diff --git a/subscriberdatamanagement/api_subscription_creation.go b/subscriberdatamanagement/api_subscription_creation.go index 131b49d..bb46d6a 100644 --- a/subscriberdatamanagement/api_subscription_creation.go +++ b/subscriberdatamanagement/api_subscription_creation.go @@ -44,7 +44,7 @@ func HTTPSubscribe(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&sdmSubscriptionReq, requestBody, "application/json") + err = openapi.Deserialize(&sdmSubscriptionReq, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -65,7 +65,7 @@ func HTTPSubscribe(c *gin.Context) { for key, val := range rsp.Header { // header response is optional c.Header(key, val[0]) } - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.SdmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -75,6 +75,6 @@ func HTTPSubscribe(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/subscriberdatamanagement/api_subscription_creation_for_shared_data.go b/subscriberdatamanagement/api_subscription_creation_for_shared_data.go index 9e0c7b7..04c89c7 100644 --- a/subscriberdatamanagement/api_subscription_creation_for_shared_data.go +++ b/subscriberdatamanagement/api_subscription_creation_for_shared_data.go @@ -25,6 +25,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + // SubscribeToSharedData - subscribe to notifications for shared data func HTTPSubscribeToSharedData(c *gin.Context) { var sharedDataSubsReq models.SdmSubscription @@ -43,7 +45,7 @@ func HTTPSubscribeToSharedData(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&sharedDataSubsReq, requestBody, "application/json") + err = openapi.Deserialize(&sharedDataSubsReq, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -62,7 +64,7 @@ func HTTPSubscribeToSharedData(c *gin.Context) { for key, val := range rsp.Header { // header response is optional c.Header(key, val[0]) } - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.SdmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -72,6 +74,6 @@ func HTTPSubscribeToSharedData(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/subscriberdatamanagement/api_subscription_modification.go b/subscriberdatamanagement/api_subscription_modification.go index e728477..7a91400 100644 --- a/subscriberdatamanagement/api_subscription_modification.go +++ b/subscriberdatamanagement/api_subscription_modification.go @@ -43,7 +43,7 @@ func HTTPModify(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&sdmSubsModificationReq, requestBody, "application/json") + err = openapi.Deserialize(&sdmSubsModificationReq, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -61,7 +61,7 @@ func HTTPModify(c *gin.Context) { req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") rsp := producer.HandleModifyRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.SdmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -71,7 +71,7 @@ func HTTPModify(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } @@ -93,7 +93,7 @@ func HTTPModifyForSharedData(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&sharedDataSubscriptions, requestBody, "application/json") + err = openapi.Deserialize(&sharedDataSubscriptions, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -112,7 +112,7 @@ func HTTPModifyForSharedData(c *gin.Context) { rsp := producer.HandleModifyForSharedDataRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.SdmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -122,6 +122,6 @@ func HTTPModifyForSharedData(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/subscriberdatamanagement/routers.go b/subscriberdatamanagement/routers.go index 0eb12a4..18a931b 100644 --- a/subscriberdatamanagement/routers.go +++ b/subscriberdatamanagement/routers.go @@ -23,6 +23,8 @@ import ( utilLogger "github.com/omec-project/util/logger" ) +const errPageNotFound = "404 page not found" + // Route is the information for every URI. type Route struct { // Name is the name of this Route. @@ -60,7 +62,7 @@ func oneLayerPathHandlerFunc(c *gin.Context) { return } - c.String(http.StatusNotFound, "404 page not found") + c.String(http.StatusNotFound, errPageNotFound) } func twoLayerPathHandlerFunc(c *gin.Context) { @@ -93,7 +95,7 @@ func twoLayerPathHandlerFunc(c *gin.Context) { } } - c.String(http.StatusNotFound, "404 page not found") + c.String(http.StatusNotFound, errPageNotFound) } func threeLayerPathHandlerFunc(c *gin.Context) { @@ -125,7 +127,7 @@ func threeLayerPathHandlerFunc(c *gin.Context) { return } - c.String(http.StatusNotFound, "404 page not found") + c.String(http.StatusNotFound, errPageNotFound) } func AddService(engine *gin.Engine) *gin.RouterGroup { diff --git a/udmtests/udm_nf_discovery_test.go b/udmtests/udm_nf_discovery_test.go index 88e5857..ecf1052 100644 --- a/udmtests/udm_nf_discovery_test.go +++ b/udmtests/udm_nf_discovery_test.go @@ -28,6 +28,8 @@ import ( "github.com/stretchr/testify/assert" ) +const httpProto10 = "HTTP/1.0" + var ( UDMTest = &service.UDM{} nfInstanceID = "34343-4343-43-434-343" @@ -256,7 +258,7 @@ func TestCreateSubscriptionSuccess(t *testing.T) { httpResponse := http.Response{ Status: "200 OK", StatusCode: 200, - Proto: "HTTP/1.0", + Proto: httpProto10, ProtoMajor: 1, ProtoMinor: 0, Body: stringReadCloser, @@ -357,7 +359,7 @@ func TestCreateSubscriptionFail(t *testing.T) { httpResponseTemporaryDirect := http.Response{ Status: "307 Temporary Direct", StatusCode: 307, - Proto: "HTTP/1.0", + Proto: httpProto10, ProtoMajor: 1, ProtoMinor: 0, Body: stringReadCloser, @@ -365,7 +367,7 @@ func TestCreateSubscriptionFail(t *testing.T) { httpResponseSuccess := http.Response{ Status: "200 OK", StatusCode: 200, - Proto: "HTTP/1.0", + Proto: httpProto10, ProtoMajor: 1, ProtoMinor: 0, Body: stringReadCloser, diff --git a/ueauthentication/api_confirm_auth.go b/ueauthentication/api_confirm_auth.go index 8933234..8dafb4f 100644 --- a/ueauthentication/api_confirm_auth.go +++ b/ueauthentication/api_confirm_auth.go @@ -25,6 +25,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + // ConfirmAuth - Create a new confirmation event func HTTPConfirmAuth(c *gin.Context) { var authEvent models.AuthEvent @@ -43,7 +45,7 @@ func HTTPConfirmAuth(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&authEvent, requestBody, "application/json") + err = openapi.Deserialize(&authEvent, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -61,7 +63,7 @@ func HTTPConfirmAuth(c *gin.Context) { // This now contains the Status, Header, and Body rsp := producer.HandleConfirmAuthDataRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UeauLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -79,6 +81,6 @@ func HTTPConfirmAuth(c *gin.Context) { } } } - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/ueauthentication/api_generate_auth_data.go b/ueauthentication/api_generate_auth_data.go index d3e4781..35fee1c 100644 --- a/ueauthentication/api_generate_auth_data.go +++ b/ueauthentication/api_generate_auth_data.go @@ -44,7 +44,7 @@ func HttpGenerateAuthData(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&authInfoReq, requestBody, "application/json") + err = openapi.Deserialize(&authInfoReq, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -62,7 +62,7 @@ func HttpGenerateAuthData(c *gin.Context) { rsp := producer.HandleGenerateAuthDataRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UeauLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -72,6 +72,6 @@ func HttpGenerateAuthData(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/uecontextmanagement/api_amf_registration_for3_gpp_access.go b/uecontextmanagement/api_amf_registration_for3_gpp_access.go index 887047c..2323f08 100644 --- a/uecontextmanagement/api_amf_registration_for3_gpp_access.go +++ b/uecontextmanagement/api_amf_registration_for3_gpp_access.go @@ -43,7 +43,7 @@ func HTTPRegistrationAmf3gppAccess(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&amf3GppAccessRegistration, requestBody, "application/json") + err = openapi.Deserialize(&amf3GppAccessRegistration, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -65,7 +65,7 @@ func HTTPRegistrationAmf3gppAccess(c *gin.Context) { for key, val := range rsp.Header { // header response is optional c.Header(key, val[0]) } - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UecmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -75,6 +75,6 @@ func HTTPRegistrationAmf3gppAccess(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/uecontextmanagement/api_amf_registration_for_non3_gpp_access.go b/uecontextmanagement/api_amf_registration_for_non3_gpp_access.go index 20abd4f..f5e9c25 100644 --- a/uecontextmanagement/api_amf_registration_for_non3_gpp_access.go +++ b/uecontextmanagement/api_amf_registration_for_non3_gpp_access.go @@ -25,6 +25,8 @@ import ( "github.com/omec-project/util/httpwrapper" ) +const contentTypeJson = "application/json" + // Register - register as AMF for non-3GPP access func HTTPRegistrationAmfNon3gppAccess(c *gin.Context) { var amfNon3GppAccessRegistration models.AmfNon3GppAccessRegistration @@ -44,7 +46,7 @@ func HTTPRegistrationAmfNon3gppAccess(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&amfNon3GppAccessRegistration, requestBody, "application/json") + err = openapi.Deserialize(&amfNon3GppAccessRegistration, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -65,7 +67,7 @@ func HTTPRegistrationAmfNon3gppAccess(c *gin.Context) { for key, val := range rsp.Header { // header response is optional c.Header(key, val[0]) } - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UecmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -75,6 +77,6 @@ func HTTPRegistrationAmfNon3gppAccess(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/uecontextmanagement/api_parameter_update_in_the_amf_registration_for3_gpp_access.go b/uecontextmanagement/api_parameter_update_in_the_amf_registration_for3_gpp_access.go index 0e933ed..44929f8 100644 --- a/uecontextmanagement/api_parameter_update_in_the_amf_registration_for3_gpp_access.go +++ b/uecontextmanagement/api_parameter_update_in_the_amf_registration_for3_gpp_access.go @@ -44,7 +44,7 @@ func HTTPUpdateAmf3gppAccess(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&amf3GppAccessRegistrationModification, requestBody, "application/json") + err = openapi.Deserialize(&amf3GppAccessRegistrationModification, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -62,7 +62,7 @@ func HTTPUpdateAmf3gppAccess(c *gin.Context) { rsp := producer.HandleUpdateAmf3gppAccessRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UecmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -72,6 +72,6 @@ func HTTPUpdateAmf3gppAccess(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/uecontextmanagement/api_parameter_update_in_the_amf_registration_for_non3_gpp_access.go b/uecontextmanagement/api_parameter_update_in_the_amf_registration_for_non3_gpp_access.go index ba70c4e..995880e 100644 --- a/uecontextmanagement/api_parameter_update_in_the_amf_registration_for_non3_gpp_access.go +++ b/uecontextmanagement/api_parameter_update_in_the_amf_registration_for_non3_gpp_access.go @@ -43,7 +43,7 @@ func HTTPUpdateAmfNon3gppAccess(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&amfNon3GppAccessRegistrationModification, requestBody, "application/json") + err = openapi.Deserialize(&amfNon3GppAccessRegistrationModification, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -61,7 +61,7 @@ func HTTPUpdateAmfNon3gppAccess(c *gin.Context) { rsp := producer.HandleUpdateAmfNon3gppAccessRequest(req) - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UecmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -71,6 +71,6 @@ func HTTPUpdateAmfNon3gppAccess(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/uecontextmanagement/api_smf_registration.go b/uecontextmanagement/api_smf_registration.go index f36a346..a55a6c9 100644 --- a/uecontextmanagement/api_smf_registration.go +++ b/uecontextmanagement/api_smf_registration.go @@ -44,7 +44,7 @@ func HTTPRegistrationSmfRegistrations(c *gin.Context) { } // step 2: convert requestBody to openapi models - err = openapi.Deserialize(&smfRegistration, requestBody, "application/json") + err = openapi.Deserialize(&smfRegistration, requestBody, contentTypeJson) if err != nil { problemDetail := "[Request Body] " + err.Error() rsp := models.ProblemDetails{ @@ -65,7 +65,7 @@ func HTTPRegistrationSmfRegistrations(c *gin.Context) { for key, val := range rsp.Header { // header response is optional c.Header(key, val[0]) } - responseBody, err := openapi.Serialize(rsp.Body, "application/json") + responseBody, err := openapi.Serialize(rsp.Body, contentTypeJson) if err != nil { logger.UecmLog.Errorln(err) problemDetails := models.ProblemDetails{ @@ -75,6 +75,6 @@ func HTTPRegistrationSmfRegistrations(c *gin.Context) { } c.JSON(http.StatusInternalServerError, problemDetails) } else { - c.Data(rsp.Status, "application/json", responseBody) + c.Data(rsp.Status, contentTypeJson, responseBody) } } diff --git a/uecontextmanagement/routers.go b/uecontextmanagement/routers.go index 39afab4..78abc87 100644 --- a/uecontextmanagement/routers.go +++ b/uecontextmanagement/routers.go @@ -23,6 +23,13 @@ import ( utilLogger "github.com/omec-project/util/logger" ) +const ( + pathAmf3gppAccess = "/:ueId/registrations/amf-3gpp-access" + pathAmfNon3gppAccess = "/:ueId/registrations/amf-non-3gpp-access" + pathSmsf3gppAccess = "/:ueId/registrations/smsf-3gpp-access" + pathSmsfNon3gppAccess = "/:ueId/registrations/smsf-non-3gpp-access" +) + // Route is the information for every URI. type Route struct { // Name is the name of this Route. @@ -82,42 +89,42 @@ var routes = Routes{ { "GetAmf3gppAccess", strings.ToUpper("Get"), - "/:ueId/registrations/amf-3gpp-access", + pathAmf3gppAccess, HTTPGetAmf3gppAccess, }, { "GetAmfNon3gppAccess", strings.ToUpper("Get"), - "/:ueId/registrations/amf-non-3gpp-access", + pathAmfNon3gppAccess, HTTPGetAmfNon3gppAccess, }, { "RegistrationAmf3gppAccess", strings.ToUpper("Put"), - "/:ueId/registrations/amf-3gpp-access", + pathAmf3gppAccess, HTTPRegistrationAmf3gppAccess, }, { "Register", strings.ToUpper("Put"), - "/:ueId/registrations/amf-non-3gpp-access", + pathAmfNon3gppAccess, HTTPRegistrationAmfNon3gppAccess, }, { "UpdateAmf3gppAccess", strings.ToUpper("Patch"), - "/:ueId/registrations/amf-3gpp-access", + pathAmf3gppAccess, HTTPUpdateAmf3gppAccess, }, { "UpdateAmfNon3gppAccess", strings.ToUpper("Patch"), - "/:ueId/registrations/amf-non-3gpp-access", + pathAmfNon3gppAccess, HTTPUpdateAmfNon3gppAccess, }, @@ -138,42 +145,42 @@ var routes = Routes{ { "GetSmsf3gppAccess", strings.ToUpper("Get"), - "/:ueId/registrations/smsf-3gpp-access", + pathSmsf3gppAccess, HTTPGetSmsf3gppAccess, }, { "DeregistrationSmsf3gppAccess", strings.ToUpper("Delete"), - "/:ueId/registrations/smsf-3gpp-access", + pathSmsf3gppAccess, HTTPDeregistrationSmsf3gppAccess, }, { "DeregistrationSmsfNon3gppAccess", strings.ToUpper("Delete"), - "/:ueId/registrations/smsf-non-3gpp-access", + pathSmsfNon3gppAccess, HTTPDeregistrationSmsfNon3gppAccess, }, { "GetSmsfNon3gppAccess", strings.ToUpper("Get"), - "/:ueId/registrations/smsf-non-3gpp-access", + pathSmsfNon3gppAccess, HTTPGetSmsfNon3gppAccess, }, { "UpdateSMSFReg3GPP", strings.ToUpper("Put"), - "/:ueId/registrations/smsf-3gpp-access", + pathSmsf3gppAccess, HTTPUpdateSMSFReg3GPP, }, { "RegistrationSmsfNon3gppAccess", strings.ToUpper("Put"), - "/:ueId/registrations/smsf-non-3gpp-access", + pathSmsfNon3gppAccess, HTTPRegistrationSmsfNon3gppAccess, }, } diff --git a/util/init_context.go b/util/init_context.go index e6e4d8e..eb2202b 100644 --- a/util/init_context.go +++ b/util/init_context.go @@ -18,65 +18,97 @@ import ( "github.com/omec-project/util/util_3gpp/suci" ) +// InitUDMContext initializes the UDM context with configuration data. func InitUDMContext(udmContext *context.UDMContext) { config := factory.UdmConfig - logger.UtilLog.Info("udmconfig Info: Version[", config.Info.Version, "] Description[", config.Info.Description, "]") + logger.UtilLog.Infof("udmconfig Info: Version[%s] Description[%s]", config.Info.Version, config.Info.Description) + configuration := config.Configuration udmContext.NfId = uuid.New().String() if configuration.UdmName != "" { udmContext.Name = configuration.UdmName } - sbi := configuration.Sbi - udmContext.UriScheme = "" + + // Initialize default SBI values udmContext.SBIPort = factory.UDM_DEFAULT_PORT_INT udmContext.RegisterIPv4 = factory.UDM_DEFAULT_IPV4 - if sbi != nil { - if sbi.Scheme != "" { - udmContext.UriScheme = models.UriScheme(sbi.Scheme) - } - if sbi.RegisterIPv4 != "" { - udmContext.RegisterIPv4 = sbi.RegisterIPv4 - } - if sbi.Port != 0 { - udmContext.SBIPort = sbi.Port - } - - udmContext.BindingIPv4 = os.Getenv(sbi.BindingIPv4) - if udmContext.BindingIPv4 != "" { - logger.UtilLog.Info("Parsing ServerIPv4 address from ENV Variable.") - } else { - udmContext.BindingIPv4 = sbi.BindingIPv4 - if udmContext.BindingIPv4 == "" { - logger.UtilLog.Warn("Error parsing ServerIPv4 address as string. Using the 0.0.0.0 address as default.") - udmContext.BindingIPv4 = "0.0.0.0" - } - } + udmContext.UriScheme = "" + + // Refactored SBI and Binding logic + initSbiSettings(udmContext, configuration.Sbi) + + // Refactored NRF caching logic + initNrfCaching(udmContext, configuration) + + udmContext.NrfUri = configuration.NrfUri + initSuciProfiles(udmContext, configuration) + + udmContext.PlmnList = configuration.PlmnList + udmContext.InitNFService(configuration.ServiceList, config.Info.Version) +} + +// initSbiSettings handles the SBI configuration and IP binding logic. +func initSbiSettings(udmContext *context.UDMContext, sbi *factory.Sbi) { + if sbi == nil { + return } + if sbi.Scheme != "" { + udmContext.UriScheme = models.UriScheme(sbi.Scheme) + } + if sbi.RegisterIPv4 != "" { + udmContext.RegisterIPv4 = sbi.RegisterIPv4 + } + if sbi.Port != 0 { + udmContext.SBIPort = sbi.Port + } + + // Handle Binding IPv4 logic: Check ENV first, then config, then default + udmContext.BindingIPv4 = os.Getenv(sbi.BindingIPv4) + if udmContext.BindingIPv4 != "" { + logger.UtilLog.Info("Parsing ServerIPv4 address from ENV Variable.") + return + } + + udmContext.BindingIPv4 = sbi.BindingIPv4 + if udmContext.BindingIPv4 == "" { + logger.UtilLog.Warn("Error parsing ServerIPv4 address as string. Using the 0.0.0.0 address as default.") + udmContext.BindingIPv4 = "0.0.0.0" + } +} + +// initNrfCaching configures NRF caching and eviction intervals. +func initNrfCaching(udmContext *context.UDMContext, configuration *factory.Configuration) { + if configuration == nil { + return + } udmContext.EnableNrfCaching = configuration.EnableNrfCaching - if configuration.EnableNrfCaching { - if configuration.NrfCacheEvictionInterval == 0 { - udmContext.NrfCacheEvictionInterval = time.Duration(900) // 15 mins - } else { - udmContext.NrfCacheEvictionInterval = time.Duration(configuration.NrfCacheEvictionInterval) - } + if !udmContext.EnableNrfCaching { + return } - udmContext.NrfUri = configuration.NrfUri - servingNameList := configuration.ServiceList + if configuration.NrfCacheEvictionInterval == 0 { + udmContext.NrfCacheEvictionInterval = time.Duration(900) // Default: 15 mins + } else { + udmContext.NrfCacheEvictionInterval = time.Duration(configuration.NrfCacheEvictionInterval) + } +} +// initSuciProfiles sets up the SUCI protection schemes. +func initSuciProfiles(udmContext *context.UDMContext, configuration *factory.Configuration) { + if configuration == nil { + return + } udmContext.SuciProfiles = []suci.SuciProfile{ { - ProtectionScheme: "1", // Standard defined value for Protection Scheme A (TS 33.501 Annex C) + ProtectionScheme: "1", // Scheme A (TS 33.501) PrivateKey: configuration.Keys.UdmProfileAHNPrivateKey, PublicKey: configuration.Keys.UdmProfileAHNPublicKey, }, { - ProtectionScheme: "2", // Standard defined value for Protection Scheme B (TS 33.501 Annex C) + ProtectionScheme: "2", // Scheme B (TS 33.501) PrivateKey: configuration.Keys.UdmProfileBHNPrivateKey, PublicKey: configuration.Keys.UdmProfileBHNPublicKey, }, } - udmContext.PlmnList = configuration.PlmnList - udmContext.InitNFService(servingNameList, config.Info.Version) } diff --git a/util/search_nf_service.go b/util/search_nf_service.go index 6a534ff..609167d 100644 --- a/util/search_nf_service.go +++ b/util/search_nf_service.go @@ -11,46 +11,77 @@ import ( "github.com/5GC-DEV/openapi-cdac/models" ) +// SearchNFServiceUri searches for a specific service URI within an NF Profile based on service name and status. func SearchNFServiceUri(nfProfile models.NfProfile, serviceName models.ServiceName, nfServiceStatus models.NfServiceStatus, -) (nfUri string) { - if nfProfile.NfServices != nil { - for _, service := range *nfProfile.NfServices { - if service.ServiceName == serviceName && service.NfServiceStatus == nfServiceStatus { - if nfProfile.Fqdn != "" { - nfUri = nfProfile.Fqdn - } else if service.Fqdn != "" { - nfUri = service.Fqdn - } else if service.ApiPrefix != "" { - nfUri = service.ApiPrefix - } else if service.IpEndPoints != nil { - point := (*service.IpEndPoints)[0] - if point.Ipv4Address != "" { - nfUri = getSbiUri(service.Scheme, point.Ipv4Address, point.Port) - } else if len(nfProfile.Ipv4Addresses) != 0 { - nfUri = getSbiUri(service.Scheme, nfProfile.Ipv4Addresses[0], point.Port) - } - } - } - if nfUri != "" { - break +) string { + if nfProfile.NfServices == nil { + return "" + } + + for _, service := range *nfProfile.NfServices { + // Filter by service name and status + if service.ServiceName == serviceName && service.NfServiceStatus == nfServiceStatus { + // Extract URI using helper to keep nesting low + if uri := extractUri(nfProfile, service); uri != "" { + return uri } } } - return + return "" +} + +// extractUri handles the priority logic for selecting an NF URI. +func extractUri(nfProfile models.NfProfile, service models.NfService) string { + // Priority 1: Global NF FQDN + if nfProfile.Fqdn != "" { + return nfProfile.Fqdn + } + // Priority 2: Service specific FQDN + if service.Fqdn != "" { + return service.Fqdn + } + // Priority 3: API Prefix + if service.ApiPrefix != "" { + return service.ApiPrefix + } + // Priority 4: IP Endpoints + return resolveUriFromEndpoints(nfProfile, service) +} + +// resolveUriFromEndpoints extracts URI from IP endpoints or falls back to NF addresses. +func resolveUriFromEndpoints(nfProfile models.NfProfile, service models.NfService) string { + if service.IpEndPoints == nil || len(*service.IpEndPoints) == 0 { + return "" + } + + point := (*service.IpEndPoints)[0] + // Use endpoint IP if available + if point.Ipv4Address != "" { + return getSbiUri(service.Scheme, point.Ipv4Address, point.Port) + } + // Fallback to NF level IPv4 address if endpoint address is missing + if len(nfProfile.Ipv4Addresses) > 0 { + return getSbiUri(service.Scheme, nfProfile.Ipv4Addresses[0], point.Port) + } + + return "" } -func getSbiUri(scheme models.UriScheme, ipv4Address string, port int32) (uri string) { +// getSbiUri constructs a URI string based on scheme, IP, and port. +func getSbiUri(scheme models.UriScheme, ipv4Address string, port int32) string { if port != 0 { - uri = fmt.Sprintf("%s://%s:%d", scheme, ipv4Address, port) - } else { - switch scheme { - case models.UriScheme_HTTP: - uri = fmt.Sprintf("%s://%s:80", scheme, ipv4Address) - case models.UriScheme_HTTPS: - uri = fmt.Sprintf("%s://%s:443", scheme, ipv4Address) - } + return fmt.Sprintf("%s://%s:%d", scheme, ipv4Address, port) + } + + // Default ports for HTTP/HTTPS if not specified + switch scheme { + case models.UriScheme_HTTP: + return fmt.Sprintf("%s://%s:80", scheme, ipv4Address) + case models.UriScheme_HTTPS: + return fmt.Sprintf("%s://%s:443", scheme, ipv4Address) + default: + return fmt.Sprintf("%s://%s", scheme, ipv4Address) } - return }