diff --git a/internal/sbi/api_uecontextmanagement.go b/internal/sbi/api_uecontextmanagement.go index d1f159b..b6bfe66 100644 --- a/internal/sbi/api_uecontextmanagement.go +++ b/internal/sbi/api_uecontextmanagement.go @@ -2,6 +2,7 @@ package sbi import ( "net/http" + "strconv" "github.com/gin-gonic/gin" @@ -9,6 +10,7 @@ import ( "github.com/free5gc/openapi/models" Nudr_DataRepository "github.com/free5gc/openapi/udr/DataRepository" "github.com/free5gc/udm/internal/logger" + "github.com/free5gc/udm/internal/util" "github.com/free5gc/util/metrics/sbi" ) @@ -245,6 +247,21 @@ func (s *Server) HandleGetAmfNon3gppAccess(c *gin.Context) { logger.UecmLog.Infoln("Handle GetAmfNon3gppAccessRequest") ueId := c.Param("ueId") + // TS 29.503 5.3.2.5.3 + // Validate SUPI and GPSI format the UE ID (SUPI or GPSI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueId) || util.IsValidGpsi(ueId) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueId) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } supportedFeatures := c.Query("supported-features") var queryAmfContextNon3gppRequest Nudr_DataRepository.QueryAmfContextNon3gppRequest queryAmfContextNon3gppRequest.SupportedFeatures = &supportedFeatures @@ -256,6 +273,23 @@ func (s *Server) HandleGetAmfNon3gppAccess(c *gin.Context) { func (s *Server) HandleRegistrationAmfNon3gppAccess(c *gin.Context) { var amfNon3GppAccessRegistration models.AmfNon3GppAccessRegistration + ueID := c.Param("ueId") + // TS 29.503 5.3.2.2.3 + // Validate SUPI format the UE ID (SUPI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + requestBody, err := c.GetRawData() if err != nil { problemDetail := models.ProblemDetails{ @@ -284,9 +318,32 @@ func (s *Server) HandleRegistrationAmfNon3gppAccess(c *gin.Context) { return } - logger.UecmLog.Infof("Handle RegisterAmfNon3gppAccessRequest") + // TS 29.503 6.2.6.2.3 requirements check + missingIE := "" + if amfNon3GppAccessRegistration.AmfInstanceId == "" { + missingIE = "AmfInstanceId" + } else if amfNon3GppAccessRegistration.Guami == nil { + missingIE = "Guami" + } else if amfNon3GppAccessRegistration.DeregCallbackUri == "" { + missingIE = "DeregCallbackUri" + } else if amfNon3GppAccessRegistration.RatType == "" { + missingIE = "RatType" + } - ueID := c.Param("ueId") + if missingIE != "" { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE " + missingIE + " is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE " + missingIE + " is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + + logger.UecmLog.Infof("Handle RegisterAmfNon3gppAccessRequest") s.Processor().RegisterAmfNon3gppAccessProcedure(c, amfNon3GppAccessRegistration, ueID) } @@ -294,6 +351,24 @@ func (s *Server) HandleRegistrationAmfNon3gppAccess(c *gin.Context) { // RegistrationAmf3gppAccess - register as AMF for 3GPP access func (s *Server) HandleRegistrationAmf3gppAccess(c *gin.Context) { var amf3GppAccessRegistration models.Amf3GppAccessRegistration + + ueID := c.Param("ueId") + // TS 29.503 5.3.2.2.2 + // Validate SUPI format the UE ID (SUPI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + requestBody, err := c.GetRawData() if err != nil { problemDetail := models.ProblemDetails{ @@ -321,10 +396,33 @@ func (s *Server) HandleRegistrationAmf3gppAccess(c *gin.Context) { c.JSON(int(rsp.Status), rsp) return } + // TS 29.503 6.2.6.2.2 requirements check + missingIE := "" + if amf3GppAccessRegistration.AmfInstanceId == "" { + missingIE = "AmfInstanceId" + } else if amf3GppAccessRegistration.Guami == nil { + missingIE = "Guami" + } else if amf3GppAccessRegistration.DeregCallbackUri == "" { + missingIE = "DeregCallbackUri" + } else if amf3GppAccessRegistration.RatType == "" { + missingIE = "RatType" + } + + if missingIE != "" { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE " + missingIE + " is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE " + missingIE + " is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } logger.UecmLog.Infof("Handle RegistrationAmf3gppAccess") - ueID := c.Param("ueId") logger.UecmLog.Info("UEID: ", ueID) s.Processor().RegistrationAmf3gppAccessProcedure(c, amf3GppAccessRegistration, ueID) @@ -333,6 +431,24 @@ func (s *Server) HandleRegistrationAmf3gppAccess(c *gin.Context) { // UpdateAmfNon3gppAccess - update a parameter in the AMF registration for non-3GPP access func (s *Server) HandleUpdateAmfNon3gppAccess(c *gin.Context) { var amfNon3GppAccessRegistrationModification models.AmfNon3GppAccessRegistrationModification + + ueID := c.Param("ueId") + // TS 29.503 5.3.2.6.3 + // Validate SUPI format the UE ID (SUPI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + requestBody, err := c.GetRawData() if err != nil { problemDetail := models.ProblemDetails{ @@ -361,9 +477,26 @@ func (s *Server) HandleUpdateAmfNon3gppAccess(c *gin.Context) { return } - logger.UecmLog.Infof("Handle UpdateAmfNon3gppAccessRequest") + // TS 29.503 6.2.6.2.8 requirements check + missingIE := "" + if amfNon3GppAccessRegistrationModification.Guami == nil { + missingIE = "Guami" + } - ueID := c.Param("ueId") + if missingIE != "" { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE " + missingIE + " is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE " + missingIE + " is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + + logger.UecmLog.Infof("Handle UpdateAmfNon3gppAccessRequest") s.Processor().UpdateAmfNon3gppAccessProcedure(c, amfNon3GppAccessRegistrationModification, ueID) } @@ -372,6 +505,23 @@ func (s *Server) HandleUpdateAmfNon3gppAccess(c *gin.Context) { func (s *Server) HandleUpdateAmf3gppAccess(c *gin.Context) { var amf3GppAccessRegistrationModification models.Amf3GppAccessRegistrationModification + ueID := c.Param("ueId") + // TS 29.503 5.3.2.6.2 + // Validate SUPI format the UE ID (SUPI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + requestBody, err := c.GetRawData() if err != nil { problemDetail := models.ProblemDetails{ @@ -400,9 +550,26 @@ func (s *Server) HandleUpdateAmf3gppAccess(c *gin.Context) { return } - logger.UecmLog.Infof("Handle UpdateAmf3gppAccessRequest") + // TS 29.503 6.2.6.2.7 requirements check + missingIE := "" + if amf3GppAccessRegistrationModification.Guami == nil { + missingIE = "Guami" + } - ueID := c.Param("ueId") + if missingIE != "" { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE " + missingIE + " is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE " + missingIE + " is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + + logger.UecmLog.Infof("Handle UpdateAmf3gppAccessRequest") s.Processor().UpdateAmf3gppAccessProcedure(c, amf3GppAccessRegistrationModification, ueID) } @@ -442,7 +609,36 @@ func (s *Server) HandleDeregistrationSmfRegistrations(c *gin.Context) { logger.UecmLog.Infof("Handle DeregistrationSmfRegistrations") ueID := c.Params.ByName("ueId") + // TS 29.503 5.3.2.4.4 + // Validate SUPI format the UE ID (SUPI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } pduSessionID := c.Params.ByName("pduSessionId") + // TS 29.571 5.4.2 valid PDU Session ID is an integer in the range 0 to 255 + pduSessionIDInt, err := strconv.Atoi(pduSessionID) + if pduSessionIDInt < 0 || pduSessionIDInt > 255 || err != nil { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE PduSessionId is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE PduSessionId is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } s.Processor().DeregistrationSmfRegistrationsProcedure(c, ueID, pduSessionID) } @@ -451,6 +647,23 @@ func (s *Server) HandleDeregistrationSmfRegistrations(c *gin.Context) { func (s *Server) HandleRegistrationSmfRegistrations(c *gin.Context) { var smfRegistration models.SmfRegistration + ueID := c.Params.ByName("ueId") + // TS 29.503 5.3.2.2.4 + // Validate SUPI format the UE ID (SUPI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + requestBody, err := c.GetRawData() if err != nil { problemDetail := models.ProblemDetails{ @@ -479,10 +692,42 @@ func (s *Server) HandleRegistrationSmfRegistrations(c *gin.Context) { return } + // TS 29.503 6.2.6.2.4 requirements check + missingIE := "" + if smfRegistration.SmfInstanceId == "" { + missingIE = "SmfInstanceId" + } + + if missingIE != "" { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE " + missingIE + " is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE " + missingIE + " is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } + logger.UecmLog.Infof("Handle RegistrationSmfRegistrations") - ueID := c.Params.ByName("ueId") pduSessionID := c.Params.ByName("pduSessionId") + // TS 29.571 5.4.2 valid PDU Session ID is an integer in the range 0 to 255 + pduSessionIDInt, err := strconv.Atoi(pduSessionID) + if pduSessionIDInt < 0 || pduSessionIDInt > 255 || err != nil { + problemDetail := models.ProblemDetails{ + Title: "Missing or invalid parameter", + Status: http.StatusBadRequest, + Detail: "Mandatory IE PduSessionId is missing or invalid", + Cause: "MISSING_OR_INVALID_PARAMETER", + } + logger.UecmLog.Warnln("Mandatory IE PduSessionId is missing or invalid") + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } s.Processor().RegistrationSmfRegistrationsProcedure( c, @@ -497,6 +742,21 @@ func (s *Server) HandleGetAmf3gppAccess(c *gin.Context) { logger.UecmLog.Infof("Handle HandleGetAmf3gppAccessRequest") ueID := c.Param("ueId") + // TS 29.503 5.3.2.5.2 + // Validate SUPI and GPSI format the UE ID (SUPI or GPSI) shall be in the format defined in 3GPP TS 23.003 & 29.571 + valid := util.IsValidSupi(ueID) || util.IsValidGpsi(ueID) + if !valid { + problemDetail := models.ProblemDetails{ + Title: "Invalid ueID format", + Status: http.StatusBadRequest, + Detail: "The ueID format is invalid", + Cause: "INVALID_KEY", + } + logger.UecmLog.Warnf("Registration Reject: Invalid ueID format [%s]", ueID) + c.Set(sbi.IN_PB_DETAILS_CTX_STR, http.StatusText(int(problemDetail.Status))) + c.JSON(int(problemDetail.Status), problemDetail) + return + } supportedFeatures := c.Query("supported-features") s.Processor().GetAmf3gppAccessProcedure(c, ueID, supportedFeatures) diff --git a/internal/util/gpsi.go b/internal/util/gpsi.go new file mode 100644 index 0000000..45b9e5d --- /dev/null +++ b/internal/util/gpsi.go @@ -0,0 +1,35 @@ +package util + +import ( + "regexp" + "strings" +) + +// TS 29.571 5.3.2 & TS 23.003 3.3 +// MSISDN format validation +var gpsiMsisdnRegex = regexp.MustCompile(`^msisdn-[0-9]{5,15}$`) + +// TS 29.571 5.3.2 & TS 23.003 19.7.2 +// External Identifier format validation +var gpsiExtIdRegex = regexp.MustCompile(`^extid-.+@.+$`) + +// IsValidGpsi valid GPSI format (TS 29.571 5.3.2) +func IsValidGpsi(gpsi string) bool { + if len(gpsi) == 0 { + return false + } + + if strings.HasPrefix(gpsi, "msisdn-") { + return gpsiMsisdnRegex.MatchString(gpsi) + } + + if strings.HasPrefix(gpsi, "extid-") { + // External Identifier should not contain null byte + if strings.Contains(gpsi, "\x00") { + return false + } + return gpsiExtIdRegex.MatchString(gpsi) + } + + return false +} diff --git a/internal/util/gpsi_test.go b/internal/util/gpsi_test.go new file mode 100644 index 0000000..7e0e1f1 --- /dev/null +++ b/internal/util/gpsi_test.go @@ -0,0 +1,70 @@ +package util + +import ( + "testing" +) + +func TestIsValidGpsi(t *testing.T) { + type args struct { + gpsi string + } + type testCase struct { + name string + args args + want bool + } + + runTests := func(t *testing.T, tests []testCase) { + t.Helper() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsValidGpsi(tt.args.gpsi); got != tt.want { + t.Errorf("IsValidGpsi() = %v, want %v (input: %q)", got, tt.want, tt.args.gpsi) + } + }) + } + } + + // MSISDN test + t.Run("Check_MSISDN", func(t *testing.T) { + tests := []testCase{ + {"Valid MSISDN (Standard)", args{"msisdn-886912345678"}, true}, + {"Valid MSISDN (Min Length 5)", args{"msisdn-12345"}, true}, // TS 29.571 regex lower bound + {"Valid MSISDN (Max Length 15)", args{"msisdn-123456789012345"}, true}, // TS 29.571 regex upper bound + {"Invalid MSISDN (Too short)", args{"msisdn-1234"}, false}, + {"Invalid MSISDN (Too long)", args{"msisdn-1234567890123456"}, false}, + {"Invalid MSISDN (Non-digits)", args{"msisdn-886912abc"}, false}, + {"Invalid MSISDN (Null Byte)", args{"msisdn-123\x00456"}, false}, + } + runTests(t, tests) + }) + + // external identifier test + t.Run("Check_ExternalID", func(t *testing.T) { + tests := []testCase{ + {"Valid ExtID (Standard)", args{"extid-user@domain.com"}, true}, + {"Valid ExtID (Subdomain)", args{"extid-sensor1@factory.iot.org"}, true}, + {"Invalid ExtID (Missing @)", args{"extid-user-domain.com"}, false}, // TS 23.003: MUST have @ + {"Invalid ExtID (Missing LocalId)", args{"extid-@domain.com"}, false}, + {"Invalid ExtID (Missing DomainId)", args{"extid-user@"}, false}, + {"Invalid ExtID (Empty content)", args{"extid-"}, false}, + // [Security] Null Byte Injection check + {"Security: ExtID with Null Byte", args{"extid-user\x00@domain.com"}, false}, + } + runTests(t, tests) + }) + + // other invalid cases + t.Run("Check_General_Invalid", func(t *testing.T) { + tests := []testCase{ + {"Empty String", args{""}, false}, + {"Unknown Prefix", args{"unknown-12345"}, false}, + {"Just Prefix (msisdn-)", args{"msisdn-"}, false}, + {"Just Prefix (extid-)", args{"extid-"}, false}, + // [Security] Fuzzing / Garbage + {"Security: Raw Null Bytes", args{"\x00\x00\x00"}, false}, + {"Security: Random String", args{"not_a_gpsi"}, false}, + } + runTests(t, tests) + }) +} diff --git a/internal/util/supi.go b/internal/util/supi.go new file mode 100644 index 0000000..cfaf078 --- /dev/null +++ b/internal/util/supi.go @@ -0,0 +1,41 @@ +package util + +import ( + "regexp" + "strings" +) + +// TS 29.571 5.3.2 & TS 23.003 +// SUPI format validation +var supiImsiRegex = regexp.MustCompile(`^imsi-[0-9]{5,15}$`) + +// TS 29.571 5.3.2 & TS 23.003 28.7.2 +// NAI format validation +var supiNaiRegex = regexp.MustCompile(`^nai-.+@.+$`) + +// TS 29.571 5.3.2 & TS 23.003 28.15.2(gci) & TS 23.003 28.16.2(gli) +// GCI/GLI format validation +var supiGciGliRegex = regexp.MustCompile(`^(gci|gli)-.+$`) + +// IsValidSupi checks if the given SUPI is valid according to 3GPP specifications +func IsValidSupi(supi string) bool { + if len(supi) == 0 { + return false + } + + if strings.HasPrefix(supi, "imsi-") { + return supiImsiRegex.MatchString(supi) + } + + if strings.HasPrefix(supi, "nai-") { + if strings.Contains(supi, "\x00") { + return false + } + return supiNaiRegex.MatchString(supi) + } + if strings.HasPrefix(supi, "gci-") || strings.HasPrefix(supi, "gli-") { + return supiGciGliRegex.MatchString(supi) + } + + return false +} diff --git a/internal/util/supi_test.go b/internal/util/supi_test.go new file mode 100644 index 0000000..fdb7627 --- /dev/null +++ b/internal/util/supi_test.go @@ -0,0 +1,75 @@ +package util + +import ( + "testing" +) + +func TestIsValidSupi(t *testing.T) { + type Args struct { + supi string + } + + type testCase struct { + name string + Args Args + Want bool + } + runTests := func(t *testing.T, tests []testCase) { + t.Helper() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsValidSupi(tt.Args.supi); got != tt.Want { + t.Errorf("IsValidSupi() = %v, Want %v (input: %q)", got, tt.Want, tt.Args.supi) + } + }) + } + } + // IMSI test + t.Run("Check_IMSI", func(t *testing.T) { + tests := []testCase{ + {"Valid IMSI (15 digits)", Args{"imsi-208930000000003"}, true}, + {"Valid IMSI (5 digits)", Args{"imsi-12345"}, true}, + {"Invalid IMSI (Too short)", Args{"imsi-1234"}, false}, + {"Invalid IMSI (Too long)", Args{"imsi-1234567890123456"}, false}, + {"Invalid IMSI (Non-digits)", Args{"imsi-20893abc000003"}, false}, + {"Invalid IMSI (Null Byte)", Args{"imsi-123\x00456"}, false}, + } + runTests(t, tests) + }) + + // NAI test + t.Run("Check_NAI", func(t *testing.T) { + tests := []testCase{ + {"Valid NAI (Standard)", Args{"nai-user@realm.com"}, true}, + {"Valid NAI (3GPP Style)", Args{"nai-type0.rid123.schid0.userid1@5gc.mnc001.mcc001.org"}, true}, + {"Invalid NAI (Missing @)", Args{"nai-userrealm.com"}, false}, + {"Invalid NAI (Missing Realm)", Args{"nai-user@"}, false}, + {"Invalid NAI (Missing User)", Args{"nai-@realm"}, false}, + {"Security: NAI with Null Byte", Args{"nai-user\x00@realm"}, false}, + } + runTests(t, tests) + }) + + // GCI/GLI test + t.Run("Check_GCI_GLI", func(t *testing.T) { + tests := []testCase{ + {"Valid GCI", Args{"gci-cable-mac-1234"}, true}, + {"Valid GLI", Args{"gli-fiber-line-5678"}, true}, + {"Invalid GCI (Empty body)", Args{"gci-"}, false}, + {"Invalid GLI (Empty body)", Args{"gli-"}, false}, + } + runTests(t, tests) + }) + + // General invalid test + t.Run("Check_General_Invalid", func(t *testing.T) { + tests := []testCase{ + {"Empty String", Args{""}, false}, + {"Unknown Prefix", Args{"unknown-12345"}, false}, + {"Just Prefix", Args{"imsi-"}, false}, + {"Security: Raw Null Bytes", Args{"\x00\x00\x00"}, false}, + {"Security: Garbage", Args{"fuzzing_payload"}, false}, + } + runTests(t, tests) + }) +}