PCF is an abbreviation for Policy Control Function. As stated in the name, PCF responsible for managing network policies, ensuring efficient and proper network operations. PCF play a vital role in network slicing, policy enforcement, Charging rules and Quality of service management. Due to the existance of PCF, a network could:
a. Enhance User Experience by managing QoS and ensure that resource are allocated efficiently.
b. Increase Network Utilization by helping to optimize the use of network resources by dynamically adjusting policies based on network conditions.
c. Support for Network Slicing by create and manage QoS for each slices that of course has it's own specific QoS.
Consider this scenario, there are two customer let's say A and B. Customer A want to backup terabytes of file to a cloud while consumer B operate a Wireless Sensor Network. Both use case need different QoS. PCF will handle both customer by:
Policy Creation, provide a storage and API to create and store policies created by network operators
Apply policies by evaluating request based on predefined policies and real-time network conditions
Enforcing Policy by communicating to other network such as AMF and SMF that enforce these policies. It ensures each use case requirement is satisfied.
Steps:
a. The AMF receives the registration request from the AN. Based on local policy, the AMF selects to contact the (V-) PCF to create the policy association with the (V-) PCF and to retrieve Access and Mobility control policy. The AMF selects the PCF as described in subclause 8.2 and invokes the Npcf_AMPolicyControl_Create service operation by sending the HTTP POST request to the "AM Policy Associations" resource as defined in subclause 4.2.2 and subclause 5.3.2.3.1 of 3GPP TS 29.507. The request operation provides the SUPI and the allowed NSSAI if applicable, and if received from the UDM, the Service Area Restrictions, RFSP index, GPSI and a list of Internal Group Identifiers, and may provide the access type, the PEI if received in the AMF, the User Location Information if available, the UE Time Zone if available, Serving Network, RAT type, GUAMI of AMF, alternative or backup address(es) of AMF and trace control and configuration parameters information. The request includes a Notification URI to indicate to the PCF where to send a notification when the policy is updated.
b.If the PCF does not have the subscription data, it invokes the Nudr_DataRepository_Query service operation to the UDR by sending an HTTP GET request to the "AccessAndMobilityPolicyData" resource as specified in TS 29.519.
c. The UDR sends an HTTP "200 OK" response to the PCF with the subscription data.
d. The PCF may request notifications from the UDR on changes in the subscription information by invoking Nudr_DataRepository_Subscribe service operation by sending an HTTP POST request to the
"PolicyDataSubscriptions" resource as specified in 3GPP TS 29.519.
e. The UDR sends an HTTP "201 Created" response to acknowledge the subscription from the PCF.
f. The (V-)PCF makes the requested policy decision including Access and Mobility control policy information, and may determine applicable Policy Control Request Trigger(s).
g. The (V)PCF sends an HTTP "201 Created" response to the AMF with the determined policies as described in subclause 4.2.2 of 3GPP TS 29.507:
h. The AMF deploys the Access and Mobility control policy information if received which includes storing the Service Area Restrictions, provisioning the Service Area Restrictions to the UE and/ or provisioning the RFSP index and Service Area Restrictions to the NG-RAN.
Steps:
a. The AMF detects a Policy Control Request Trigger condition is met or other condition is met, e.g. trace control configuration needs to be updated, as defined in subclause 4.2.3.1 of 3GPP TS 29.507.
b. The AMF invokes the Npcf_AMPolicyControl_Update service operation to the (V-) PCF by sending the HTTP POST request to the "Individual AM Policy Association" resource with information on the conditions that have
changed.
c. The (V)PCF stores the information received in step 2 and makes the policy decision.
d. The (V)PCF sends an HTTP "200 OK" response to the AMF with the updated Access and Mobility control policy information and/ or the updated Policy Control Request Trigger parameters.
e. The AMF deploys the Access and Mobility control policy if received, which includes, e.g. storing the Service Area Restrictions, provisioning the Service Area Restrictions to the NG-RAN and UE, and/or provisioning the RFSP index to the NG-RAN.
Step:
a. The AMF invokes the Npcf_AMPolicyControl_Delete service operation to delete the policy context in the (V-) PCF by sending the HTTP DELETE request to the "Individual AM Policy Association" resource.
b. The AMF removes the UE context for this UE, including the Access and Mobility Control Policy related to the UE and/or policy control request triggers.
c. The (V-)PCF removes the policy context for the UE and sends an HTTP "204 No Content" response to the AMF.
d. The PCF invokes the Nudr_DataRepository_Unsubscribe service operation to unsubscribe the notification of
subscriber policy data modification from the UDR by sending the HTTP DELETE request to the
"IndividualPolicyDataSubscription" resource if it has subscribed such notification.5. The UDR sends an HTTP "204 No Content" response to the PCF.
a. The SMF receives a PDU session establishment request from the UE. The SMF selects the PCF as described in subclause 8.3 and invokes the Npcf_SMPolicyControl_Create service operation by sending the HTTP POST request to the "SM Policies" resource as defined in subclause 4.2.2.2 of 3GPP TS 29.512. The request
operation provides the SUPI, the PDU session ID, PDU Session Type, DNN, and S-NSSAI, and may provide the GPSI, the Internal Group Identifier, the Access Type (and additional access type, in case of MA PDU session), the IPv4 address or the IPv6 network prefix (if available), the MA PDU session indication and the ATSSS capability, if available, the PEI if received in the SMF, the User Location Information, the UE Time Zone, Serving Network, RAT type, charging information, the Session-AMBR, the DN-AAA authorization profile index if available, one or more framed routes if available, the subscribed default QoS, if available, etc, as defined in subclause 4.2.2 of 3GPP TS 29.512. The request operation also includes a Notification URI to indicate to the PCF where to send a notification when the SM related policies are updated.
b. If PCF does not have the subscription data for the SUPI, DNN and S-NSSAI, the PCF invokes the Nudr_DataRepository_Query service operation to the UDR by sending the HTTP GET request to the "SessionManagementPolicyData" resource as specified in 3GPP TS 29.519. The UDR sends an HTTP "200 OK" response to the PCF with the policy control subscription data. If BDT Reference ID(s) is included in the response from the UDR, the PCF shall invoke the Nudr_DataRepository_Query service operation to the UDR toretrieve the Background Data Transfer policy corresponding to the BDT Reference ID(s) by sending the HTTP GET request to the " IndividualBdtData" resource or the "BdtData" collection resource with the URI query parameter "bdt-ref-ids" as specified in 3GPP TS 29.519, and the UDR sends an HTTP "200 OK" response to the PCF with the Background Data Transfer policy. Additionally, if the TSC feature defined in 3GPP TS 29.512 is supported, the PCF invokes the Nudr_DataRepository_Query service operation to retrieve the stored AF influence data in the UDR by sending the HTTP GET request to the "Influence Data" resource as specified in 3GPP TS 29.519. The UDR sends an HTTP "200 OK" response with the stored AF request. Additionally, if the ATSSS feature defined in 3GPP TS 29.512 is supported, and the SDF template of the PCC rule includes an application identifier, the PCF invokes the Nudr_DataRepository_Query service operation
to retrieve the stored OS Id(s) supported by the UE from the UDR by sending the HTTP GET request to the "UePolicySet" resource as specified in 3GPP TS 29.519 . The UDR sends an HTTP "200 OK" response with the stored UE Policy data. The PCF determines the application descriptors based on the retrieved OS Id(s), if available, and local configuration, as specified in 3GPP TS 29.512. Additionally, if the WWC feature defined in 3GPP TS 29.512 is supported, the PCF invokes the Nudr_DataRepository_Query service operation to retrieve the stored IPTV configuration from the UDR by sending the HTTP GET request to the "IPTV Configurations" resource as specified in 3GPP TS 29.519.The UDR sends an HTTP "200 OK" response with the stored IPTV configuration. The PCF determines Multicast Access Control information (i.e., whether the multicast channel represented by the SDF of the PCC rule is allowed or not) based on the retrieved IPTV configuration as specified in 3GPP TS 29.512.
c. To request notifications from the UDR on changes in the subscription information, the PCF invokes the Nudr_DataRepository_Subscribe service operation by sending an HTTP POST request to the "PolicyDataSubscriptions" resource. The UDR sends an HTTP "201 Created" response to acknowledge the subscription. Additionally, if the TSC feature defined in 3GPP TS 29.512 is supported, to request notifications from the UDR on changes in the AF influence data, the PCF invokes the Nudr_DataRepository_Subscribe service operation by sending an HTTP POST request to the "Influence Data Subscription" resource. The UDR sends an HTTP "201 Created" response to acknowledge the subscription. Additionally, if the WWC feature defined in 3GPP TS 29.512 is supported, to request notifications from the UDR on changes in the IPTV configuration, the PCF invokes the Nudr_DataRepository_Subscribe service operation by sending an HTTP POST request to the "ApplicationDataSubscriptions" resource. The UDR sends an HTTP "201 Created" response to acknowledge the subscription.
d.If the PCF determines that the policy decision depends on the status of the policy counters available at the CHF, and such reporting is not established for the subscriber, the PCF initiates an Initial Spending Limit Report Retrieval as defined in subclause 5.3.2. If policy counter status reporting is already established for the subscriber, and the PCF determines that the status of additional policy counters are required, the PCF initiates an Intermediate Spending Limit Report Retrieval as defined in subclause 5.3.3.
e. The PCF makes the policy decision to determine the information provided in step 10.
When the feature "TimeSensitiveNetworking" is supported and the PCF detects that the request relates to TSC traffic based on the received DNN and S-NSSAI, the PCF determines to provide the the "TSN_BRIDGE_INFO" policy control request trigger in step 10.
f. In the case that the BSF is to be used and that either the IP address/prefix or MAC address is available, the PCF invokes the Nbsf_Management_Register service operation by sending HTTP POST request to create the PDU session binding information for a UE in the BSF as detailed in subclause 8 .5.2.
g. The PCF receives an HTTP "201 Created" response from the BSF with the created binding information as detailed in subclause 8.5.2.
h. The PCF sends an HTTP "201 Created" response to the SMF with the determined policies as described in subclause 4.2.2 of 3GPP TS 29.512.
a. The PCF receives an internal or external trigger to re-evaluate PCC Rules and policy decision for a PDU Session. Possible external trigger events are described in subclause 5.2.2.2.2. In addition, this procedure is triggered by the following cases:
b. If the PCF determines that the policy decision depends on the status of the policy counters available at the CHF and such reporting is not established for the subscriber, the PCF initiates an Initial Spending Limit Report as defined in subclause 5.3.2. If policy counter status reporting is already established for the subscriber, and the PCF decides to modify the list of subscribed policy counters, the PCF sends an Intermediate Spending Limit Report as defined in subclause 5.3.3. If the PCF decides to unsubscribe any future status notification of policy counters, it sends a Final Spending Limit Report Request to cancel the request for reporting the change of the status of the policy counters available at the CHF as defined in subclause 5.3.4.
c. The PCF makes a policy decision. The PCF can determine that updated or new policy information need to be sent to the SMF.
d. The PCF invokes the Npcf_SMPolicyControl_UpdateNotify service operation by sending the HTTP POST
request with "{notificationUri}/update" as the resource URI to the SMF that has previously subscribed. The request operation provides the PDU session ID and the updated policies, as described in subclause 4.2.3 of 3GPP TS 29.512.
e. The SMF sends an HTTP "200 OK" to the PCF
a. The SMF invokes the Npcf_SMPolicyControl_Delete service operation by sending the HTTP POST request to the "Individual SM Policy" resource to request the PCF to delete the context of the SM related policy as defined in subclause 4.2.5.2 of 3GPP TS 29.512. The request operation may include usage monitoring information (if applicable) and access network information.
b. Upon receipt of Npcf_SMPolicyControl_Delete service operation, the PCF identifies the PCC Rules that require an AF to be notified and removes PCC Rules for the PDU Session.
c. The SMF removes all the PCC Rules which are applied to the PDU session.
d. The PCF invokes the Npcf_PolicyAuthorization_Notify service operation by sending the HTTP POST request with "{notifUri}/terminate" as the resource URI to the AF to trigger the AF to request the application session context termination.
e. The AF sends an HTTP "204 No Content" response to the PCF
f. The AF invokes the Npcf_PolicyAuthorization_Delete service operation by sending the HTTP POST request to the "Individual Application Session Context" resource. The request may include the events to subscribe to
g. The PCF removes the AF application session context and sends an HTTP "204 No Content" response to the AF. If the PCF needs to report usage data or the access network information, it sends an HTTP "200 OK" response. If usage thresholds were provided by the AF earlier, and the PCF has usage data that has not yet been reported to the AF, the PCF informs the AF about the resources that have been consumed by the user since the last report. If the SMF in step 1 reports the access network information and if the AF requested the PCF to report access network information in step 6 and/or the RAN-NAS-Cause feature is supported, the PCF informs the AF about the access network information. The PCF also deletes the subscription to PCF detected events for that AF application Session.
h. If this is the last PDU session for this subscriber the Final Spending Limit Report Request as defined in subclause 5.3.4 is sent. If any existing PDU sessions for this subscriber require policy counter status reporting, the Intermediate Spending Limit Report Request as defined in subclause 5.3.3 can be sent to alter the list of subscribed policy counters.
i. The PCF removes PCC Rules for the terminated PDU Session and sends an HTTP "204 No Content" response to the SMF.
j. The PCF invokes the Nudr_DataRepository_Update service operation by sending the HTTP PATCH request to the "SessionManagementPolicyData" resource to store the remaining usage allowance in the UDR, if all PDU sessions of the user to the same DNN and S-NSSAI are terminated.
k. The UDR sends an HTTP "204 No Content" response to the PCF.
l. To unsubscribe the notification of the PDU session related data modification from the UDR, the PCF invokes the Nudr_DataRepository_Unsubscribe service operation by sending the HTTP DELETE request to the "IndividualPolicyDataSubscription" resource if it has subscribed such notification. The UDR sends an HTTP "204 No Content" response to the PCF. Additionally, to unsubscribe the notification of the AF influence data from the UDR, the PCF invokes the Nudr_DataRepository_Unsubscribe service operation by sending the HTTP DELETE request to the "Individual Influence Data Subscription" resource if it has subscribed such notification. The UDR sends an HTTP "204 No Content" response to the PCF.
m. In the case that binding information has been previously registered in the BSF the PCF invokes the
Nbsf_Management_Deregister service operation by sending an HTTP DELETE request to the BSF to delete
binding information as detailed in subclause 8.5.3.
n. The PCF receives an HTTP "204 No Content response from the BSF as detailed in subclause 8.5.3.
- pcf
- cmd
- main.go
- internal
- context
- context.go
- ue.go
- logger
- logger.go
- sbi
- consumer
- amf_service.go
- consumer.go
- nrf_service.go
- udr_service.go
- processor
- ampolicy.go
- bdtpolicy.go
- notifier.go
- oam.go
- policyauthorization.go
- processor.go
- smpolicy.go
- api_ampolicy.go
- api_bdtpolicy.go
- api_httpcallback.go
- api_oam.go
- api_policyauthorization.go
- api_smpolicy.go
- api_uepolicy.go
- server.go
- util
- convert.go
- init_context.go
- pcc_rule.go
- pcf_util.go
- router_auth_check.go
- router_auth_check_test.go
- search_nf_service.go
- pkg
- app
- app.go
- factory
- config.go
- factory.go
- service
- init.go
a. cmd/main.go
package main
import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime/debug"
"syscall"
"github.com/urfave/cli"
"github.com/free5gc/pcf/internal/logger"
"github.com/free5gc/pcf/pkg/factory"
"github.com/free5gc/pcf/pkg/service"
logger_util "github.com/free5gc/util/logger"
"github.com/free5gc/util/version"
)
var PCF *service.PcfApp
func main() {
defer func() {
if p := recover(); p != nil {
// Print stack for panic to log. Fatalf() will let program exit.
logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack()))
}
}()
app := cli.NewApp()
app.Name = "pcf"
app.Usage = "5G Policy Control Function (PCF)"
app.Action = action
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config, c",
Usage: "Load configuration from `FILE`",
},
cli.StringSliceFlag{
Name: "log, l",
Usage: "Output NF log to `FILE`",
},
}
if err := app.Run(os.Args); err != nil {
fmt.Printf("PCF Run Error: %v\n", err)
}
}
func action(cliCtx *cli.Context) error {
tlsKeyLogPath, err := initLogFile(cliCtx.StringSlice("log"))
if err != nil {
return err
}
logger.MainLog.Infoln(cliCtx.App.Name)
logger.MainLog.Infoln("PCF version: ", version.GetVersion())
ctx, cancel := context.WithCancel(context.Background())
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigCh // Wait for interrupt signal to gracefully shutdown UPF
cancel() // Notify each goroutine and wait them stopped
}()
cfg, err := factory.ReadConfig(cliCtx.String("config"))
if err != nil {
sigCh <- nil
return err
}
factory.PcfConfig = cfg
pcf, err := service.NewApp(ctx, cfg, tlsKeyLogPath)
if err != nil {
sigCh <- nil
return err
}
PCF = pcf
if pcf == nil {
logger.MainLog.Infoln("pcf is nil")
}
pcf.Start()
return nil
}
func initLogFile(logNfPath []string) (string, error) {
logTlsKeyPath := ""
for _, path := range logNfPath {
if err := logger_util.LogFileHook(logger.Log, path); err != nil {
return "", err
}
if logTlsKeyPath != "" {
continue
}
nfDir, _ := filepath.Split(path)
tmpDir := filepath.Join(nfDir, "key")
if err := os.MkdirAll(tmpDir, 0o775); err != nil {
logger.InitLog.Errorf("Make directory %s failed: %+v", tmpDir, err)
return "", err
}
_, name := filepath.Split(factory.PcfDefaultTLSKeyLogPath)
logTlsKeyPath = filepath.Join(tmpDir, name)
}
return logTlsKeyPath, nil
}
This file contain 3 functions:
logic explanation:
app variable is an object that has attributes such as: name, usage, action and flags. The action attributes are a pointer to function that start the PCF. The action function works explain below
b. pkg/app/app.go
package app
import (
pcf_context "github.com/free5gc/pcf/internal/context"
"github.com/free5gc/pcf/pkg/factory"
)
type App interface {
SetLogEnable(enable bool)
SetLogLevel(level string)
SetReportCaller(reportCaller bool)
Start()
Terminate()
Context() *pcf_context.PCFContext
Config() *factory.Config
}
This file containing an interface that provide a common type for detailed implementation of PCF components. Each PCF components should have these function:
c. pkg/factory/config.go
/*
* PCF Configuration Factory
*/
package factory
import (
"fmt"
"os"
"strconv"
"sync"
"github.com/asaskevich/govalidator"
"github.com/free5gc/pcf/internal/logger"
)
const (
PcfTimeFormatLayout = "2006-01-02 15:04:05"
PcfDefaultTLSKeyLogPath = "./log/pcfsslkey.log"
PcfDefaultCertPemPath = "./cert/pcf.pem"
PcfDefaultPrivateKeyPath = "./cert/pcf.key"
PcfDefaultConfigPath = "./config/pcfcfg.yaml"
PcfSbiDefaultIPv4 = "127.0.0.7"
PcfSbiDefaultPort = 8000
PcfSbiDefaultScheme = "https"
PcfDefaultNrfUri = "https://127.0.0.10:8000"
PcfPolicyAuthResUriPrefix = "/npcf-policyauthorization/v1"
PcfAMpolicyCtlResUriPrefix = "/npcf-am-policy-control/v1"
PcfCallbackResUriPrefix = "/npcf-callback/v1"
PcfSMpolicyCtlResUriPrefix = "/npcf-smpolicycontrol/v1"
PcfBdtPolicyCtlResUriPrefix = "/npcf-bdtpolicycontrol/v1"
PcfOamResUriPrefix = "/npcf-oam/v1"
PcfUePolicyCtlResUriPrefix = "/npcf-ue-policy-control/v1/"
)
type Config struct {
Info *Info `yaml:"info" valid:"required"`
Configuration *Configuration `yaml:"configuration" valid:"required"`
Logger *Logger `yaml:"logger" valid:"required"`
sync.RWMutex
}
func (c *Config) Validate() (bool, error) {
if configuration := c.Configuration; configuration != nil {
if result, err := configuration.validate(); err != nil {
return result, err
}
}
result, err := govalidator.ValidateStruct(c)
return result, appendInvalid(err)
}
type Info struct {
Version string `yaml:"version,omitempty" valid:"required,in(1.0.2)"`
Description string `yaml:"description,omitempty" valid:"type(string)"`
}
type Configuration struct {
PcfName string `yaml:"pcfName,omitempty" valid:"required, type(string)"`
Sbi *Sbi `yaml:"sbi,omitempty" valid:"required"`
TimeFormat string `yaml:"timeFormat,omitempty" valid:"required"`
DefaultBdtRefId string `yaml:"defaultBdtRefId,omitempty" valid:"required, type(string)"`
NrfUri string `yaml:"nrfUri,omitempty" valid:"required, url"`
NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"`
ServiceList []Service `yaml:"serviceList,omitempty" valid:"required"`
Mongodb *Mongodb `yaml:"mongodb" valid:"required"`
Locality string `yaml:"locality,omitempty" valid:"-"`
}
type Logger struct {
Enable bool `yaml:"enable" valid:"type(bool)"`
Level string `yaml:"level" valid:"required,in(trace|debug|info|warn|error|fatal|panic)"`
ReportCaller bool `yaml:"reportCaller" valid:"type(bool)"`
}
func (c *Configuration) validate() (bool, error) {
if c.Sbi != nil {
if _, err := c.Sbi.validate(); err != nil {
return false, err
}
}
if result := govalidator.IsTime(c.TimeFormat, PcfTimeFormatLayout); !result {
err := fmt.Errorf("Invalid TimeFormat: %s, should be in 2019-01-02 15:04:05 format.", c.TimeFormat)
return false, err
}
if c.ServiceList != nil {
var errs govalidator.Errors
for _, v := range c.ServiceList {
if _, err := v.validate(); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return false, error(errs)
}
}
if c.Mongodb != nil {
if _, err := c.Mongodb.validate(); err != nil {
return false, err
}
}
if _, err := govalidator.ValidateStruct(c); err != nil {
return false, appendInvalid(err)
}
return true, nil
}
type Service struct {
ServiceName string `yaml:"serviceName" valid:"required, service"`
SuppFeat string `yaml:"suppFeat,omitempty" valid:"-"`
}
func (s *Service) validate() (bool, error) {
govalidator.TagMap["service"] = govalidator.Validator(func(str string) bool {
switch str {
case "npcf-am-policy-control":
case "npcf-smpolicycontrol":
case "npcf-bdtpolicycontrol":
case "npcf-policyauthorization":
case "npcf-eventexposure":
case "npcf-ue-policy-control":
default:
return false
}
return true
})
if s.ServiceName == "npcf-smpolicycontrol" {
if sf, e := strconv.ParseUint(s.SuppFeat, 16, 40); e != nil {
err := fmt.Errorf("Invalid SuppFeat: %s, range of the value should be 0~3fff", s.SuppFeat)
return false, err
} else {
if sf2, e := strconv.ParseUint("3fff", 16, 20); e == nil {
if sf > sf2 {
err := fmt.Errorf("Invalid SuppFeat: %s, range of the value should be 0~3fff", s.SuppFeat)
return false, err
}
}
}
} else if s.ServiceName == "npcf-policyauthorization" {
if s.SuppFeat != "0" && s.SuppFeat != "1" && s.SuppFeat != "2" && s.SuppFeat != "3" {
err := fmt.Errorf("Invalid SuppFeat: %s, range of the value should be 0~3", s.SuppFeat)
return false, err
}
}
if _, err := govalidator.ValidateStruct(s); err != nil {
return false, appendInvalid(err)
}
return true, nil
}
type Sbi struct {
Scheme string `yaml:"scheme" valid:"required,scheme"`
RegisterIPv4 string `yaml:"registerIPv4,omitempty" valid:"required,host"` // IP that is registered at NRF.
// IPv6Addr string `yaml:"ipv6Addr,omitempty"`
BindingIPv4 string `yaml:"bindingIPv4,omitempty" valid:"required,host"` // IP used to run the server in the node.
Port int `yaml:"port,omitempty" valid:"required,port"`
Tls *Tls `yaml:"tls,omitempty" valid:"optional"`
}
func (s *Sbi) validate() (bool, error) {
govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool {
return str == "https" || str == "http"
})
if tls := s.Tls; tls != nil {
if result, err := tls.validate(); err != nil {
return result, err
}
}
if _, err := govalidator.ValidateStruct(s); err != nil {
return false, appendInvalid(err)
}
return true, nil
}
type Tls struct {
Pem string `yaml:"pem,omitempty" valid:"type(string),minstringlength(1),required"`
Key string `yaml:"key,omitempty" valid:"type(string),minstringlength(1),required"`
}
func (t *Tls) validate() (bool, error) {
result, err := govalidator.ValidateStruct(t)
return result, err
}
type Mongodb struct {
Name string `yaml:"name" valid:"required, type(string)"`
Url string `yaml:"url" valid:"required"`
}
func (m *Mongodb) validate() (bool, error) {
pattern := `[-a-zA-Z0-9@:%._\+~#=]{1,256}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)`
if result := govalidator.StringMatches(m.Url, pattern); !result {
err := fmt.Errorf("Invalid Url: %s", m.Url)
return false, err
}
if _, err := govalidator.ValidateStruct(m); err != nil {
return false, appendInvalid(err)
}
return true, nil
}
func appendInvalid(err error) error {
var errs govalidator.Errors
if err == nil {
return nil
}
es := err.(govalidator.Errors).Errors()
for _, e := range es {
errs = append(errs, fmt.Errorf("Invalid %w", e))
}
return error(errs)
}
func (c *Config) GetSbiBindingIP() string {
c.RLock()
defer c.RUnlock()
bindIP := "0.0.0.0"
if c.Configuration == nil || c.Configuration.Sbi == nil {
return bindIP
}
if c.Configuration.Sbi.BindingIPv4 != "" {
if bindIP = os.Getenv(c.Configuration.Sbi.BindingIPv4); bindIP != "" {
logger.CfgLog.Infof("Parsing ServerIPv4 [%s] from ENV Variable", bindIP)
} else {
bindIP = c.Configuration.Sbi.BindingIPv4
}
}
return bindIP
}
func (c *Config) GetSbiPort() int {
c.RLock()
defer c.RUnlock()
if c.Configuration != nil && c.Configuration.Sbi != nil && c.Configuration.Sbi.Port != 0 {
return c.Configuration.Sbi.Port
}
return PcfSbiDefaultPort
}
func (c *Config) GetSbiBindingAddr() string {
c.RLock()
defer c.RUnlock()
return c.GetSbiBindingIP() + ":" + strconv.Itoa(c.GetSbiPort())
}
func (c *Config) GetSbiScheme() string {
c.RLock()
defer c.RUnlock()
if c.Configuration != nil && c.Configuration.Sbi != nil && c.Configuration.Sbi.Scheme != "" {
return c.Configuration.Sbi.Scheme
}
return PcfSbiDefaultScheme
}
func (c *Config) GetCertPemPath() string {
c.RLock()
defer c.RUnlock()
return c.Configuration.Sbi.Tls.Pem
}
func (c *Config) GetCertKeyPath() string {
c.RLock()
defer c.RUnlock()
return c.Configuration.Sbi.Tls.Key
}
func (c *Config) GetVersion() string {
c.RLock()
defer c.RUnlock()
if c.Info.Version != "" {
return c.Info.Version
}
return ""
}
func (c *Config) SetLogEnable(enable bool) {
c.Lock()
defer c.Unlock()
if c.Logger == nil {
logger.CfgLog.Warnf("Logger should not be nil")
c.Logger = &Logger{
Enable: enable,
Level: "info",
}
} else {
c.Logger.Enable = enable
}
}
func (c *Config) SetLogLevel(level string) {
c.Lock()
defer c.Unlock()
if c.Logger == nil {
logger.CfgLog.Warnf("Logger should not be nil")
c.Logger = &Logger{
Level: level,
}
} else {
c.Logger.Level = level
}
}
func (c *Config) SetLogReportCaller(reportCaller bool) {
c.Lock()
defer c.Unlock()
if c.Logger == nil {
logger.CfgLog.Warnf("Logger should not be nil")
c.Logger = &Logger{
Level: "info",
ReportCaller: reportCaller,
}
} else {
c.Logger.ReportCaller = reportCaller
}
}
func (c *Config) GetLogEnable() bool {
c.RLock()
defer c.RUnlock()
if c.Logger == nil {
logger.CfgLog.Warnf("Logger should not be nil")
return false
}
return c.Logger.Enable
}
func (c *Config) GetLogLevel() string {
c.RLock()
defer c.RUnlock()
if c.Logger == nil {
logger.CfgLog.Warnf("Logger should not be nil")
return "info"
}
return c.Logger.Level
}
func (c *Config) GetLogReportCaller() bool {
c.RLock()
defer c.RUnlock()
if c.Logger == nil {
logger.CfgLog.Warnf("Logger should not be nil")
return false
}
return c.Logger.ReportCaller
}
This file contains:
Each of the struct/object method called "Validate". This method are called to check whether an object is valid to be created or not.
This file contain object to store configuration of the PCF service and apps.
d. pkg/factory/factory.go
package factory
import (
"fmt"
"os"
"github.com/asaskevich/govalidator"
"gopkg.in/yaml.v2"
"github.com/free5gc/pcf/internal/logger"
)
var PcfConfig *Config
// TODO: Support configuration update from REST api
func InitConfigFactory(f string, cfg *Config) error {
if f == "" {
// Use default config path
f = PcfDefaultConfigPath
}
if content, err := os.ReadFile(f); err != nil {
return fmt.Errorf("[Factory] %+v", err)
} else {
logger.CfgLog.Infof("Read config from [%s]", f)
if yamlErr := yaml.Unmarshal(content, cfg); yamlErr != nil {
return fmt.Errorf("[Factory] %+v", yamlErr)
}
}
return nil
}
func ReadConfig(cfgPath string) (*Config, error) {
cfg := &Config{}
if err := InitConfigFactory(cfgPath, cfg); err != nil {
return nil, fmt.Errorf("ReadConfig [%s] Error: %+v", cfgPath, err)
}
if _, err := cfg.Validate(); err != nil {
validErrs := err.(govalidator.Errors).Errors()
for _, validErr := range validErrs {
logger.CfgLog.Errorf("%+v", validErr)
}
logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]")
return nil, fmt.Errorf("Config validate Error")
}
return cfg, nil
}
This file contains two functions:
InitConfig, this function is called to check the yaml file format, ensuring there is no syntax error in the configuration, hence validate the configuration file
ReadConfig, this function is called to load value from config file to the config object. As seen in the code, the "config" object is used in the function
e. pkg/service/init.go
package service
import (
"context"
"io"
"os"
"runtime/debug"
"sync"
"github.com/sirupsen/logrus"
pcf_context "github.com/free5gc/pcf/internal/context"
"github.com/free5gc/pcf/internal/logger"
"github.com/free5gc/pcf/internal/sbi"
"github.com/free5gc/pcf/internal/sbi/consumer"
"github.com/free5gc/pcf/internal/sbi/processor"
"github.com/free5gc/pcf/pkg/app"
"github.com/free5gc/pcf/pkg/factory"
)
var PCF *PcfApp //defined at service.go
var _ app.App = &PcfApp{}
type PcfApp struct {
app.App
cfg *factory.Config
pcfCtx *pcf_context.PCFContext //defined at context.go
ctx context.Context
cancel context.CancelFunc
consumer *consumer.Consumer
processor *processor.Processor
sbiServer *sbi.Server
wg sync.WaitGroup
}
func NewApp(
ctx context.Context,
cfg *factory.Config,
tlsKeyLogPath string,
) (*PcfApp, error) {
pcf := &PcfApp{
cfg: cfg,
wg: sync.WaitGroup{},
}
pcf.SetLogEnable(cfg.GetLogEnable())
pcf.SetLogLevel(cfg.GetLogLevel())
pcf.SetReportCaller(cfg.GetLogReportCaller())
pcf.ctx, pcf.cancel = context.WithCancel(ctx)
pcf_context.Init() //create pcf context
pcf.pcfCtx = pcf_context.GetSelf()
//context defined at internal/context/context.go
// initialize consumer
consumer, err := consumer.NewConsumer(pcf)
if err != nil {
return pcf, err
}
pcf.consumer = consumer
// initialize processor
p, err := processor.NewProcessor(pcf, consumer)
if err != nil {
return pcf, err
}
pcf.processor = p
//NewServer defined at sbi/server.go
if pcf.sbiServer, err = sbi.NewServer(pcf, tlsKeyLogPath); err != nil {
return nil, err
}
PCF = pcf
return pcf, nil
}
func (a *PcfApp) Config() *factory.Config {
return a.cfg
}
func (a *PcfApp) Context() *pcf_context.PCFContext {
return a.pcfCtx
}
func (a *PcfApp) CancelContext() context.Context {
return a.ctx
}
func (a *PcfApp) Consumer() *consumer.Consumer {
return a.consumer
}
func (a *PcfApp) Processor() *processor.Processor {
return a.processor
}
func (a *PcfApp) SetLogEnable(enable bool) {
logger.MainLog.Infof("Log enable is set to [%v]", enable)
if enable && logger.Log.Out == os.Stderr {
return
} else if !enable && logger.Log.Out == io.Discard {
return
}
a.cfg.SetLogEnable(enable)
if enable {
logger.Log.SetOutput(os.Stderr)
} else {
logger.Log.SetOutput(io.Discard)
}
}
func (a *PcfApp) SetLogLevel(level string) {
lvl, err := logrus.ParseLevel(level)
if err != nil {
logger.MainLog.Warnf("Log level [%s] is invalid", level)
return
}
logger.MainLog.Infof("Log level is set to [%s]", level)
if lvl == logger.Log.GetLevel() {
return
}
a.cfg.SetLogLevel(level)
logger.Log.SetLevel(lvl)
}
func (a *PcfApp) SetReportCaller(reportCaller bool) {
logger.MainLog.Infof("Report Caller is set to [%v]", reportCaller)
if reportCaller == logger.Log.ReportCaller {
return
}
a.cfg.SetLogReportCaller(reportCaller)
logger.Log.SetReportCaller(reportCaller)
}
func (a *PcfApp) Start() { //called by cmd/init.go
logger.InitLog.Infoln("Server started")
a.wg.Add(1)
go a.listenShutdownEvent()
if err := a.sbiServer.Run(context.Background(), &a.wg); err != nil {
logger.InitLog.Fatalf("Run SBI server failed: %+v", err)
}
a.WaitRoutineStopped()
}
func (a *PcfApp) listenShutdownEvent() {
defer func() {
if p := recover(); p != nil {
// Print stack for panic to log. Fatalf() will let program exit.
logger.InitLog.Fatalf("panic: %v\n%s", p, string(debug.Stack()))
}
a.wg.Done()
}()
<-a.ctx.Done()
a.terminateProcedure()
}
func (a *PcfApp) CallServerStop() {
if a.sbiServer != nil {
a.sbiServer.Shutdown(context.Background())
}
}
func (a *PcfApp) Terminate() {
a.cancel()
}
func (a *PcfApp) terminateProcedure() {
logger.MainLog.Infof("Terminating PCF...")
a.CallServerStop()
// deregister with NRF
problemDetails, err := a.Consumer().SendDeregisterNFInstance()
if problemDetails != nil {
logger.InitLog.Errorf("Deregister NF instance Failed Problem[%+v]", problemDetails)
} else if err != nil {
logger.InitLog.Errorf("Deregister NF instance Error[%+v]", err)
} else {
logger.InitLog.Infof("Deregister from NRF successfully")
}
logger.InitLog.Infof("PCF terminated")
}
func (a *PcfApp) WaitRoutineStopped() {
a.wg.Wait()
logger.MainLog.Infof("PCF App is terminated")
}
This file contains:
Global variable called PCF
that holds *PcfApp object pointer
PcfApp struct holf the information of PCF. Those information include: PCF configuration (cfg), context (pcfCtx, ctx, cancel), consumer, processor, SBI server
NewApp function: this function receive parameter with type of context, config and path to TLSKeyLog. This function return the app instance and the error if failed. This function parse the parameter to the pcfApp structs and return the instance or error. This method will create PCFContext (defined at /internal/context/context.go) set a consumer object, a processor object and create the SBI server using NewServer().
Getter methods, this methods provide a secure way to retrieve pcfApp object fields such as Config, Context, cancel context, consumer and processor.
Logger methods, these methods provide the implementation of enable logging, setting a logging level, and set the report caller.
Lifecycle methods such as: Start() to starts the PCF application ,listenShutdownEvent() to wait for the context to be canceled then initiate termination procedure, CallServerStop() to stop sbi server, Terminate() to cancels the context to trigger shutdown , terminateProcedure() to handles NF deregistration process, and WaitRoutineStopped() to wait for all go routines to be terminated.
f. internal/logger/logger.go
package logger
import (
"github.com/sirupsen/logrus"
logger_util "github.com/free5gc/util/logger"
)
var (
Log *logrus.Logger
NfLog *logrus.Entry
MainLog *logrus.Entry
InitLog *logrus.Entry
CfgLog *logrus.Entry
CtxLog *logrus.Entry
GinLog *logrus.Entry
SBILog *logrus.Entry
AmPolicyLog *logrus.Entry
BdtPolicyLog *logrus.Entry
ConsumerLog *logrus.Entry
CallbackLog *logrus.Entry
OamLog *logrus.Entry
PolicyAuthLog *logrus.Entry
ProcLog *logrus.Entry
SmPolicyLog *logrus.Entry
UtilLog *logrus.Entry
)
func init() {
fieldsOrder := []string{
logger_util.FieldNF,
logger_util.FieldCategory,
}
Log = logger_util.New(fieldsOrder)
NfLog = Log.WithField(logger_util.FieldNF, "PCF")
MainLog = NfLog.WithField(logger_util.FieldCategory, "Main")
InitLog = NfLog.WithField(logger_util.FieldCategory, "Init")
CfgLog = NfLog.WithField(logger_util.FieldCategory, "CFG")
CtxLog = NfLog.WithField(logger_util.FieldCategory, "CTX")
GinLog = NfLog.WithField(logger_util.FieldCategory, "GIN")
SBILog = NfLog.WithField(logger_util.FieldCategory, "SBI")
AmPolicyLog = NfLog.WithField(logger_util.FieldCategory, "AmPol")
BdtPolicyLog = NfLog.WithField(logger_util.FieldCategory, "BdtPol")
ConsumerLog = NfLog.WithField(logger_util.FieldCategory, "Consumer")
CallbackLog = NfLog.WithField(logger_util.FieldCategory, "Callback")
OamLog = NfLog.WithField(logger_util.FieldCategory, "Oam")
PolicyAuthLog = NfLog.WithField(logger_util.FieldCategory, "PolAuth")
ProcLog = NfLog.WithField(logger_util.FieldCategory, "Proc")
SmPolicyLog = NfLog.WithField(logger_util.FieldCategory, "SMpolicy")
UtilLog = NfLog.WithField(logger_util.FieldCategory, "Util")
}
This file contains method to initiate logging feature for the PCF services. There is variable initiation for each service under var
section and function init
to create logging for each service.
g. internal/context/context.go
package context
import (
"context"
"fmt"
"math"
"os"
"strconv"
"strings"
"sync"
"github.com/google/uuid"
"github.com/free5gc/openapi"
"github.com/free5gc/openapi/models"
"github.com/free5gc/openapi/oauth"
"github.com/free5gc/pcf/internal/logger"
"github.com/free5gc/pcf/pkg/factory"
"github.com/free5gc/util/idgenerator"
"github.com/free5gc/util/mongoapi"
)
type PCFContext struct {
NfId string
Name string
UriScheme models.UriScheme
BindingIPv4 string
RegisterIPv4 string
SBIPort int
TimeFormat string
DefaultBdtRefId string
NfService map[models.ServiceName]models.NfService
PcfServiceUris map[models.ServiceName]string
PcfSuppFeats map[models.ServiceName]openapi.SupportedFeature
NrfUri string
NrfCertPem string
DefaultUdrURI string
Locality string
// UePool map[string]*UeContext
UePool sync.Map
// Bdt Policy related
BdtPolicyPool sync.Map
BdtPolicyIDGenerator *idgenerator.IDGenerator
// App Session related
AppSessionPool sync.Map
// AMF Status Change Subscription related
AMFStatusSubsData sync.Map // map[string]AMFStatusSubscriptionData; subscriptionID as key
// lock
DefaultUdrURILock sync.RWMutex
// Charging
RatingGroupIdGenerator *idgenerator.IDGenerator
OAuth2Required bool
}
type AMFStatusSubscriptionData struct {
AmfUri string
AmfStatusUri string
GuamiList []models.Guami
}
type AppSessionData struct {
AppSessionId string
AppSessionContext *models.AppSessionContext
// (compN/compN-subCompN/appId-%s) map to PccRule
RelatedPccRuleIds map[string]string
PccRuleIdMapToCompId map[string]string
// EventSubscription
Events map[models.AfEvent]models.AfNotifMethod
EventUri string
// related Session
SmPolicyData *UeSmPolicyData
}
var pcfContext = PCFContext{}
type NFContext interface {
AuthorizationCheck(token string, serviceName models.ServiceName) error
}
var _ NFContext = &PCFContext{}
func InitPcfContext(context *PCFContext) {
config := factory.PcfConfig
logger.UtilLog.Infof("pcfconfig Info: Version[%s] Description[%s]", config.Info.Version, config.Info.Description)
configuration := config.Configuration
context.NfId = uuid.New().String()
if configuration.PcfName != "" {
context.Name = configuration.PcfName
}
mongodb := config.Configuration.Mongodb
// Connect to MongoDB
if err := mongoapi.SetMongoDB(mongodb.Name, mongodb.Url); err != nil {
logger.UtilLog.Errorf("InitpcfContext err: %+v", err)
return
}
sbi := configuration.Sbi
context.NrfUri = configuration.NrfUri
context.NrfCertPem = configuration.NrfCertPem
context.UriScheme = ""
context.RegisterIPv4 = factory.PcfSbiDefaultIPv4 // default localhost
context.SBIPort = factory.PcfSbiDefaultPort // default port
if sbi != nil {
if sbi.Scheme != "" {
context.UriScheme = models.UriScheme(sbi.Scheme)
}
if sbi.RegisterIPv4 != "" {
context.RegisterIPv4 = sbi.RegisterIPv4
}
if sbi.Port != 0 {
context.SBIPort = sbi.Port
}
if sbi.Scheme == "https" {
context.UriScheme = models.UriScheme_HTTPS
} else {
context.UriScheme = models.UriScheme_HTTP
}
context.BindingIPv4 = os.Getenv(sbi.BindingIPv4)
if context.BindingIPv4 != "" {
logger.UtilLog.Info("Parsing ServerIPv4 address from ENV Variable.")
} else {
context.BindingIPv4 = sbi.BindingIPv4
if context.BindingIPv4 == "" {
logger.UtilLog.Warn("Error parsing ServerIPv4 address as string. Using the 0.0.0.0 address as default.")
context.BindingIPv4 = "0.0.0.0"
}
}
}
serviceList := configuration.ServiceList
context.InitNFService(serviceList, config.Info.Version)
context.TimeFormat = configuration.TimeFormat
context.DefaultBdtRefId = configuration.DefaultBdtRefId
for _, service := range context.NfService {
var err error
context.PcfServiceUris[service.ServiceName] = service.ApiPrefix +
"/" + string(service.ServiceName) + "/" + (*service.Versions)[0].ApiVersionInUri
context.PcfSuppFeats[service.ServiceName], err = openapi.NewSupportedFeature(service.SupportedFeatures)
if err != nil {
logger.UtilLog.Errorf("openapi NewSupportedFeature error: %+v", err)
}
}
context.Locality = configuration.Locality
}
func Init() {
pcfContext.Name = "pcf"
pcfContext.UriScheme = models.UriScheme_HTTPS
pcfContext.TimeFormat = "2006-01-02 15:04:05"
pcfContext.DefaultBdtRefId = "BdtPolicyId-"
pcfContext.NfService = make(map[models.ServiceName]models.NfService)
pcfContext.PcfServiceUris = make(map[models.ServiceName]string)
pcfContext.PcfSuppFeats = make(map[models.ServiceName]openapi.SupportedFeature)
pcfContext.BdtPolicyIDGenerator = idgenerator.NewGenerator(1, math.MaxInt64)
pcfContext.RatingGroupIdGenerator = idgenerator.NewGenerator(1, math.MaxInt64)
InitPcfContext(&pcfContext)
}
// Create new PCF context
func GetSelf() *PCFContext {
return &pcfContext
}
func GetTimeformat() string {
return pcfContext.TimeFormat
}
func GetUri(name models.ServiceName) string {
return pcfContext.PcfServiceUris[name]
}
var (
PolicyAuthorizationUri = factory.PcfPolicyAuthResUriPrefix + "/app-sessions/"
SmUri = factory.PcfSMpolicyCtlResUriPrefix
IPv4Address = "192.168."
IPv6Address = "ffab::"
PolicyDataChangeNotifyUri = factory.PcfCallbackResUriPrefix + "/nudr-notify/policy-data"
InfluenceDataUpdateNotifyUri = factory.PcfCallbackResUriPrefix + "/nudr-notify/influence-data"
Ipv4_pool = make(map[string]string)
Ipv6_pool = make(map[string]string)
)
// BdtPolicy default value
const DefaultBdtRefId = "BdtPolicyId-"
func (c *PCFContext) GetIPv4Uri() string {
return fmt.Sprintf("%s://%s:%d", c.UriScheme, c.RegisterIPv4, c.SBIPort)
}
// Init NfService with supported service list ,and version of services
func (c *PCFContext) InitNFService(serviceList []factory.Service, version string) {
tmpVersion := strings.Split(version, ".")
versionUri := "v" + tmpVersion[0]
for index, service := range serviceList {
name := models.ServiceName(service.ServiceName)
c.NfService[name] = models.NfService{
ServiceInstanceId: strconv.Itoa(index),
ServiceName: name,
Versions: &[]models.NfServiceVersion{
{
ApiFullVersion: version,
ApiVersionInUri: versionUri,
},
},
Scheme: c.UriScheme,
NfServiceStatus: models.NfServiceStatus_REGISTERED,
ApiPrefix: c.GetIPv4Uri(),
IpEndPoints: &[]models.IpEndPoint{
{
Ipv4Address: c.RegisterIPv4,
Transport: models.TransportProtocol_TCP,
Port: int32(c.SBIPort),
},
},
SupportedFeatures: service.SuppFeat,
}
}
}
// Allocate PCF Ue with supi and add to pcf Context and returns allocated ue
func (c *PCFContext) NewPCFUe(Supi string) (*UeContext, error) {
if strings.HasPrefix(Supi, "imsi-") {
newUeContext := &UeContext{}
newUeContext.SmPolicyData = make(map[string]*UeSmPolicyData)
newUeContext.AMPolicyData = make(map[string]*UeAMPolicyData)
newUeContext.PolAssociationIDGenerator = 1
newUeContext.AppSessionIDGenerator = idgenerator.NewGenerator(1, math.MaxInt64)
newUeContext.Supi = Supi
c.UePool.Store(Supi, newUeContext)
return newUeContext, nil
} else {
return nil, fmt.Errorf(" add Ue context fail ")
}
}
// Return Bdt Policy Id with format "BdtPolicyId-%d" which be allocated
func (c *PCFContext) AllocBdtPolicyID() (bdtPolicyID string, err error) {
var allocID int64
if allocID, err = c.BdtPolicyIDGenerator.Allocate(); err != nil {
logger.CtxLog.Warnf("Allocate pathID error: %+v", err)
return "", err
}
bdtPolicyID = fmt.Sprintf("BdtPolicyId-%d", allocID)
return bdtPolicyID, nil
}
// Find PcfUe which the policyId belongs to
func (c *PCFContext) PCFUeFindByPolicyId(PolicyId string) *UeContext {
index := strings.LastIndex(PolicyId, "-")
if index == -1 {
return nil
}
supi := PolicyId[:index]
if supi != "" {
if value, ok := c.UePool.Load(supi); ok {
ueContext := value.(*UeContext)
return ueContext
}
}
return nil
}
// Find PcfUe which the AppSessionId belongs to
func (c *PCFContext) PCFUeFindByAppSessionId(appSessionId string) *UeContext {
index := strings.LastIndex(appSessionId, "-")
if index == -1 {
return nil
}
supi := appSessionId[:index]
if supi != "" {
if value, ok := c.UePool.Load(supi); ok {
ueContext := value.(*UeContext)
return ueContext
}
}
return nil
}
// Find PcfUe which Ipv4 belongs to
func (c *PCFContext) PcfUeFindByIPv4(v4 string) *UeContext {
var ue *UeContext
c.UePool.Range(func(key, value interface{}) bool {
ue = value.(*UeContext)
if ue.SMPolicyFindByIpv4(v4) != nil {
return false
} else {
return true
}
})
return ue
}
// Find PcfUe which Ipv6 belongs to
func (c *PCFContext) PcfUeFindByIPv6(v6 string) *UeContext {
var ue *UeContext
c.UePool.Range(func(key, value interface{}) bool {
ue = value.(*UeContext)
if ue.SMPolicyFindByIpv6(v6) != nil {
return false
} else {
return true
}
})
return ue
}
// Find SMPolicy with AppSessionContext
func ueSMPolicyFindByAppSessionContext(ue *UeContext, req *models.AppSessionContextReqData) (*UeSmPolicyData, error) {
var policy *UeSmPolicyData
var err error
if req.UeIpv4 != "" {
policy = ue.SMPolicyFindByIdentifiersIpv4(req.UeIpv4, req.SliceInfo, req.Dnn, req.IpDomain)
if policy == nil {
err = fmt.Errorf("Can't find Ue with Ipv4[%s]", req.UeIpv4)
}
} else if req.UeIpv6 != "" {
policy = ue.SMPolicyFindByIdentifiersIpv6(req.UeIpv6, req.SliceInfo, req.Dnn)
if policy == nil {
err = fmt.Errorf("Can't find Ue with Ipv6 prefix[%s]", req.UeIpv6)
}
} else {
// TODO: find by MAC address
err = fmt.Errorf("Ue finding by MAC address does not support")
}
return policy, err
}
// SessionBinding from application request to get corresponding Sm policy
func (c *PCFContext) SessionBinding(req *models.AppSessionContextReqData) (*UeSmPolicyData, error) {
var selectedUE *UeContext
var policy *UeSmPolicyData
var err error
if req.Supi != "" {
if val, exist := c.UePool.Load(req.Supi); exist {
selectedUE = val.(*UeContext)
}
}
if req.Gpsi != "" && selectedUE == nil {
c.UePool.Range(func(key, value interface{}) bool {
ue := value.(*UeContext)
if ue.Gpsi == req.Gpsi {
selectedUE = ue
return false
} else {
return true
}
})
}
if selectedUE != nil {
policy, err = ueSMPolicyFindByAppSessionContext(selectedUE, req)
} else {
c.UePool.Range(func(key, value interface{}) bool {
ue := value.(*UeContext)
policy, err = ueSMPolicyFindByAppSessionContext(ue, req)
return true
})
}
if policy == nil && err == nil {
err = fmt.Errorf("No SM policy found")
}
return policy, err
}
// SetDefaultUdrURI ... function to set DefaultUdrURI
func (c *PCFContext) SetDefaultUdrURI(uri string) {
c.DefaultUdrURILock.Lock()
defer c.DefaultUdrURILock.Unlock()
c.DefaultUdrURI = uri
}
func Ipv4Pool(ipindex int32) string {
ipv4address := IPv4Address + fmt.Sprint((int(ipindex)/255)+1) + "." + fmt.Sprint(int(ipindex)%255)
return ipv4address
}
func Ipv4Index() int32 {
if len(Ipv4_pool) == 0 {
Ipv4_pool["1"] = Ipv4Pool(1)
} else {
for i := 1; i <= len(Ipv4_pool); i++ {
if Ipv4_pool[fmt.Sprint(i)] == "" {
Ipv4_pool[fmt.Sprint(i)] = Ipv4Pool(int32(i))
return int32(i)
}
}
Ipv4_pool[fmt.Sprint(int32(len(Ipv4_pool)+1))] = Ipv4Pool(int32(len(Ipv4_pool) + 1))
return int32(len(Ipv4_pool))
}
return 1
}
func GetIpv4Address(ipindex int32) string {
return Ipv4_pool[fmt.Sprint(ipindex)]
}
func DeleteIpv4index(Ipv4index int32) {
delete(Ipv4_pool, fmt.Sprint(Ipv4index))
}
func Ipv6Pool(ipindex int32) string {
ipv6address := IPv6Address + fmt.Sprintf("%x\n", ipindex)
return ipv6address
}
func Ipv6Index() int32 {
if len(Ipv6_pool) == 0 {
Ipv6_pool["1"] = Ipv6Pool(1)
} else {
for i := 1; i <= len(Ipv6_pool); i++ {
if Ipv6_pool[fmt.Sprint(i)] == "" {
Ipv6_pool[fmt.Sprint(i)] = Ipv6Pool(int32(i))
return int32(i)
}
}
Ipv6_pool[fmt.Sprint(int32(len(Ipv6_pool)+1))] = Ipv6Pool(int32(len(Ipv6_pool) + 1))
return int32(len(Ipv6_pool))
}
return 1
}
func GetIpv6Address(ipindex int32) string {
return Ipv6_pool[fmt.Sprint(ipindex)]
}
func DeleteIpv6index(Ipv6index int32) {
delete(Ipv6_pool, fmt.Sprint(Ipv6index))
}
func (c *PCFContext) NewAmfStatusSubscription(subscriptionID string, subscriptionData AMFStatusSubscriptionData) {
c.AMFStatusSubsData.Store(subscriptionID, subscriptionData)
}
func (c *PCFContext) GetTokenCtx(serviceName models.ServiceName, targetNF models.NfType) (
context.Context, *models.ProblemDetails, error,
) {
if !c.OAuth2Required {
return context.TODO(), nil, nil
}
return oauth.GetTokenCtx(models.NfType_PCF, targetNF,
c.NfId, c.NrfUri, string(serviceName))
}
func (c *PCFContext) AuthorizationCheck(token string, serviceName models.ServiceName) error {
if !c.OAuth2Required {
logger.UtilLog.Debugf("PCFContext::AuthorizationCheck: OAuth2 not required\n")
return nil
}
logger.UtilLog.Debugf("PCFContext::AuthorizationCheck: token[%s] serviceName[%s]\n", token, serviceName)
return oauth.VerifyOAuth(token, string(serviceName), c.NrfCertPem)
}
This file contains
pcfContext variable declaration with PCFContext as data type
InitPcfContext(), this function will create a pcfConfig object called config(a variable defined at pkg/factory/factory.go with type config) as a buffer for configuration data, emitt Version and description that stored in config. The configuration variable also passed from config variable, generate NfId, connect to mongodb server, load NRF URI, NRF cert pem, load default registerIPv4 and SBI Port from pkg/factory/config.go. Then the function, will load the SBI configuration from config object. The function also load serviceList, initialize NF services, configure the time format and default BDT ref ID. Iterate the NF service and put each service to the PcfServiceUris map
Init(), this function contain default value of each pcfContext field value, and map object creation
Getter function, there are GetSelf()
that return PCFContext object, GetTimeFormat()
that return time format string, GetUri()
that return URI for a specific service name and GetIPv4Uri()
that return PCF URI.
Var clause that define the constant for URI string concatenation, and ip addres pool map definition
InitNFService()
, this function initialize service with service list that passed on to the function (the service object definition located at pkg/factory/config.go). This function also set up the service details such as name, version, scheme, status, API prefix, and supported features.
NewPCFUe()
, this function initialize an object that represents user equipment. A User Equipment has Context, SM Policy, AM Policy, SUPI, and IDs. This function also store UeContext and it's SUPI on a UePool
UE Context Searching Method such as PCFUeFindByPolicyId
that retrieve an UEContext based on SUPI, PCFUeFindByAppSessionId
that retrieve UEContext based on App Session, PcfUeFindByIPv4
that return UEContext by IPv4 Address.
Method to retrieve UE SM Policy based on App Session Context.
SessionBinding()
method to binds the session by finding the corresponding SM Policy for the application session request data and search for UE Context using SUPI or GPSI and retrieve the SM policy data
IPv4Pool()
, IPv4Index()
, GetIPv4Address()
, DeleteIPv4index()
. The IPv4Pool are used to generate and return IPv4 address based on provided index, IPv4Index will allocate new IPv4 index and return it, GetIPv4Address to retrieve IPv4 address for the given index and DeleteIPv4index to delete the IPv4 address given the index
p.
q.
r. Summary:
This file contains the main implementation of PCF. If developer wants to move the PCF to another NF, they should look at this file and copy this file to another context.go to maintain code structure consistency.
h. internal/context/ue.go
package context
import (
"fmt"
"math"
"reflect"
"strconv"
"strings"
"github.com/free5gc/openapi/models"
"github.com/free5gc/pcf/internal/logger"
"github.com/free5gc/util/idgenerator"
)
// key is supi
type UeContext struct {
// Ue Context
Supi string
Gpsi string
Pei string
GroupIds []string
PolAssociationIDGenerator uint32
AMPolicyData map[string]*UeAMPolicyData // use PolAssoId(ue.Supi-numPolId) as key
// Udr Ref
UdrUri string
// SMPolicy
SmPolicyData map[string]*UeSmPolicyData // use smPolicyId(ue.Supi-pduSessionId) as key
// App Session Related
// AppSessionIDGenerator uint64
AppSessionIDGenerator *idgenerator.IDGenerator
// PolicyAuth
AfRoutReq *models.AfRoutingRequirement
AspId string
// Policy Decision
AppSessionIdStore *AppSessionIdStore
PolicyDataSubscriptionStore *models.PolicyDataSubscription
PolicyDataChangeStore *models.PolicyDataChangeNotification
// ChargingRatingGroup
RatingGroupData map[string][]int32 // use smPolicyId(ue.Supi-pduSessionId) as key
}
type UeAMPolicyData struct {
PolAssoId string
AccessType models.AccessType
NotificationUri string
ServingPlmn *models.NetworkId
AltNotifIpv4Addrs []string
AltNotifIpv6Addrs []string
// TODO: AMF Status Change
AmfStatusUri string
Guami *models.Guami
ServiveName string
// TraceReq *TraceData
// Policy Association
Triggers []models.RequestTrigger
ServAreaRes *models.ServiceAreaRestriction
Rfsp int32
UserLoc *models.UserLocation
TimeZone string
SuppFeat string
// about AF request
Pras map[string]models.PresenceInfo
// related to UDR Subscription Data
AmPolicyData *models.AmPolicyData // Svbscription Data
// Corresponding UE
PcfUe *UeContext
}
type UeSmPolicyData struct {
// PduSessionId int32
// DNN string
// NotificationUri string
// Snssai models.Snssai
// PduSessionType models.PduSessionType
// IPAddress models.IpAddress
// IPDomain string
// Var3gppPsDataOffStatus bool
// SmfId string
// TraceReq *TraceData
// RecoveryTime *time.Time
PackFiltIdGenerator int32
PccRuleIdGenerator int32
ChargingIdGenerator int32
// FlowMapsToPackFiltIds map[string][]string // use Flow Description(in TS 29214) as key map to pcc rule ids
PackFiltMapToPccRuleId map[string]string // use PackFiltId as Key
// Related to GBR
RemainGbrUL *float64
RemainGbrDL *float64
// related to UDR Subscription Data
SmPolicyData *models.SmPolicyData // Svbscription Data
// related to Policy
PolicyContext *models.SmPolicyContextData
PolicyDecision *models.SmPolicyDecision
// related to AppSession
AppSessions map[string]bool // related appSessionId
// Corresponding UE
PcfUe *UeContext
InfluenceDataToPccRule map[string]string
SubscriptionID string
}
// NewUeAMPolicyData returns created UeAMPolicyData data and insert this data to Ue.AMPolicyData with assolId as key
func (ue *UeContext) NewUeAMPolicyData(assolId string, req models.PolicyAssociationRequest) *UeAMPolicyData {
ue.Gpsi = req.Gpsi
ue.Pei = req.Pei
ue.GroupIds = req.GroupIds
ue.AMPolicyData[assolId] = &UeAMPolicyData{
PolAssoId: assolId,
ServAreaRes: req.ServAreaRes,
AltNotifIpv4Addrs: req.AltNotifIpv4Addrs,
AltNotifIpv6Addrs: req.AltNotifIpv6Addrs,
AccessType: req.AccessType,
NotificationUri: req.NotificationUri,
ServingPlmn: req.ServingPlmn,
TimeZone: req.TimeZone,
Rfsp: req.Rfsp,
Guami: req.Guami,
UserLoc: req.UserLoc,
ServiveName: req.ServiveName,
PcfUe: ue,
}
ue.AMPolicyData[assolId].Pras = make(map[string]models.PresenceInfo)
return ue.AMPolicyData[assolId]
}
// returns UeSmPolicyData and insert related info to Ue with smPolId
func (ue *UeContext) NewUeSmPolicyData(
key string, request models.SmPolicyContextData, smData *models.SmPolicyData,
) *UeSmPolicyData {
if smData == nil {
return nil
}
data := UeSmPolicyData{}
data.PolicyContext = &request
// data.DNN = request.Dnn
// data.Snssai = *request.SliceInfo
// data.PduSessionId = request.PduSessionId
// data.PduSessionType = request.PduSessionType
// switch request.PduSessionType {
// case models.PduSessionType_IPV4:
// data.IPAddress.Ipv4Addr = request.Ipv4Address
// data.IPDomain = request.IpDomain
// case models.PduSessionType_IPV6:
// data.IPAddress.Ipv6Prefix = request.Ipv6AddressPrefix
// case models.PduSessionType_IPV4_V6:
// data.IPAddress.Ipv4Addr = request.Ipv4Address
// data.IPAddress.Ipv6Prefix = request.Ipv6AddressPrefix
// data.IPDomain = request.IpDomain
// }
// data.NotificationUri = request.NotificationUri
// data.SmfId = request.SmfId
// data.Var3gppPsDataOffStatus = request.Var3gppPsDataOffStatus
data.SmPolicyData = smData
data.PackFiltIdGenerator = 1
data.PackFiltMapToPccRuleId = make(map[string]string)
data.AppSessions = make(map[string]bool)
// data.RefToAmPolicy = amData
data.PccRuleIdGenerator = 1
data.ChargingIdGenerator = 1
data.PcfUe = ue
ue.SmPolicyData[key] = &data
data.InfluenceDataToPccRule = make(map[string]string)
return &data
}
// Remove Pcc rule which PccRuleId in the policy
func (policy *UeSmPolicyData) RemovePccRule(pccRuleId string, deletedSmPolicyDec *models.SmPolicyDecision) error {
decision := policy.PolicyDecision
if decision == nil {
return fmt.Errorf("Can't find the Policy Decision")
}
if rule, exist := decision.PccRules[pccRuleId]; exist {
if deletedSmPolicyDec != nil {
if deletedSmPolicyDec.PccRules == nil {
deletedSmPolicyDec.PccRules = make(map[string]*models.PccRule)
}
deletedSmPolicyDec.PccRules[pccRuleId] = nil
}
for _, info := range rule.FlowInfos {
delete(policy.PackFiltMapToPccRuleId, info.PackFiltId)
}
for _, id := range rule.RefQosData {
if decision.QosDecs != nil {
policy.IncreaseRemainGBR(id)
delete(decision.QosDecs, id)
if len(decision.QosDecs) == 0 {
decision.QosDecs = nil
}
} else {
break
}
}
if rule.RefCondData != "" {
if decision.Conds != nil {
delete(decision.Conds, rule.RefCondData)
if len(decision.Conds) == 0 {
decision.Conds = nil
}
if deletedSmPolicyDec != nil {
if deletedSmPolicyDec.Conds == nil {
deletedSmPolicyDec.Conds = make(map[string]*models.ConditionData)
}
deletedSmPolicyDec.Conds[rule.RefCondData] = nil
}
}
}
for _, id := range rule.RefChgData {
if decision.ChgDecs != nil {
delete(decision.ChgDecs, id)
if len(decision.ChgDecs) == 0 {
decision.ChgDecs = nil
}
if deletedSmPolicyDec != nil {
if deletedSmPolicyDec.ChgDecs == nil {
deletedSmPolicyDec.ChgDecs = make(map[string]*models.ChargingData)
}
deletedSmPolicyDec.ChgDecs[id] = nil
}
} else {
break
}
}
for _, id := range rule.RefTcData {
if decision.TraffContDecs != nil {
delete(decision.TraffContDecs, id)
if len(decision.TraffContDecs) == 0 {
decision.TraffContDecs = nil
}
if deletedSmPolicyDec != nil {
if deletedSmPolicyDec.TraffContDecs == nil {
deletedSmPolicyDec.TraffContDecs = make(map[string]*models.TrafficControlData)
}
deletedSmPolicyDec.TraffContDecs[id] = nil
}
} else {
break
}
}
for _, id := range rule.RefUmData {
if decision.UmDecs != nil {
delete(decision.UmDecs, id)
if len(decision.UmDecs) == 0 {
decision.UmDecs = nil
}
if deletedSmPolicyDec != nil {
if deletedSmPolicyDec.UmDecs == nil {
deletedSmPolicyDec.UmDecs = make(map[string]*models.UsageMonitoringData)
}
deletedSmPolicyDec.UmDecs[id] = nil
}
} else {
break
}
}
delete(decision.PccRules, pccRuleId)
} else {
return fmt.Errorf("Can't find the pccRuleId[%s] in Session[%d]", pccRuleId, policy.PolicyContext.PduSessionId)
}
return nil
}
// Check if the afEvent exists in smPolicy
func (policy *UeSmPolicyData) CheckRelatedAfEvent(event models.AfEvent) (found bool) {
for appSessionId := range policy.AppSessions {
if val, ok := GetSelf().AppSessionPool.Load(appSessionId); ok {
appSession := val.(*AppSessionData)
for afEvent := range appSession.Events {
if afEvent == event {
return true
}
}
}
}
return false
}
// Arrange Exist Event policy Sm policy about afevents and return if it changes or not and
func (policy *UeSmPolicyData) ArrangeExistEventSubscription() (changed bool) {
triggers := []models.PolicyControlRequestTrigger{}
for _, trigger := range policy.PolicyDecision.PolicyCtrlReqTriggers {
var afEvent models.AfEvent
switch trigger {
case models.PolicyControlRequestTrigger_PLMN_CH: // PLMN Change
afEvent = models.AfEvent_PLMN_CHG
case models.PolicyControlRequestTrigger_QOS_NOTIF:
// SMF notify PCF when receiving from RAN that QoS can/can't be guaranteed (subsclause 4.2.4.20 in TS29512) (always)
afEvent = models.AfEvent_QOS_NOTIF
case models.PolicyControlRequestTrigger_SUCC_RES_ALLO:
// Successful resource allocation (subsclause 4.2.6.5.5, 4.2.4.14 in TS29512)
afEvent = models.AfEvent_SUCCESSFUL_RESOURCES_ALLOCATION
case models.PolicyControlRequestTrigger_AC_TY_CH: // Change of RatType
afEvent = models.AfEvent_ACCESS_TYPE_CHANGE
case models.PolicyControlRequestTrigger_US_RE: // UMC
afEvent = models.AfEvent_USAGE_REPORT
}
if afEvent != "" && !policy.CheckRelatedAfEvent(afEvent) {
changed = true
} else {
triggers = append(triggers, trigger)
}
}
policy.PolicyDecision.PolicyCtrlReqTriggers = triggers
return
}
// Increase remain GBR of this policy and returns original UL DL GBR for resume case
func (policy *UeSmPolicyData) IncreaseRemainGBR(qosId string) (origUl, origDl *float64) {
decision := policy.PolicyDecision
if decision == nil {
return
}
if qos, exist := decision.QosDecs[qosId]; exist {
if qos.Var5qi <= 4 {
// Add GBR
origUl = IncreaseRamainBitRate(policy.RemainGbrUL, qos.GbrUl)
origDl = IncreaseRamainBitRate(policy.RemainGbrDL, qos.GbrDl)
}
}
return
}
// Increase remain Bit Rate and returns original Bit Rate
func IncreaseRamainBitRate(remainBitRate *float64, reqBitRate string) (orig *float64) {
if remainBitRate != nil && reqBitRate != "" {
bitRate, err := ConvertBitRateToKbps(reqBitRate)
if err == nil {
orig = new(float64)
*orig = *remainBitRate
*remainBitRate += bitRate
}
}
return
}
// Decrease remain GBR of this policy and returns UL DL GBR
func (policy *UeSmPolicyData) DecreaseRemainGBR(req *models.RequestedQos) (gbrDl, gbrUl string, err error) {
if req == nil {
return "", "", nil
}
if req.Var5qi <= 4 {
err = DecreaseRamainBitRate(policy.RemainGbrDL, req.GbrDl)
if err != nil {
return
}
gbrDl = req.GbrDl
err = DecreaseRamainBitRate(policy.RemainGbrUL, req.GbrUl)
if err != nil {
return
}
gbrUl = req.GbrUl
}
return
}
// Decrease remain Bit Rate
func DecreaseRamainBitRate(remainBitRate *float64, reqBitRate string) error {
if reqBitRate != "" {
bitRate, err := ConvertBitRateToKbps(reqBitRate)
if err != nil {
return err
}
if remainBitRate != nil {
if *remainBitRate < bitRate {
return fmt.Errorf("Request BitRate exceed Dnn Aggregate BitRate of UE")
}
*remainBitRate -= bitRate
}
}
return nil
}
// Returns remin Bit rate string and decrease ir to zero
func DecreaseRamainBitRateToZero(remainBitRate *float64) string {
if remainBitRate != nil {
bitRate := ConvertBitRateToString(*remainBitRate)
*remainBitRate = 0
return bitRate
}
return ""
}
// returns AM Policy which AccessType and plmnId match
func (ue *UeContext) FindAMPolicy(anType models.AccessType, plmnId *models.NetworkId) *UeAMPolicyData {
if ue == nil || plmnId == nil {
return nil
}
for _, amPolicy := range ue.AMPolicyData {
if amPolicy.AccessType == anType && reflect.DeepEqual(*amPolicy.ServingPlmn, *plmnId) {
return amPolicy
}
}
return nil
}
// Return App Session Id with format "ue.Supi-%d" which be allocated
func (ue *UeContext) AllocUeAppSessionId(context *PCFContext) string {
var allocID int64
var err error
if allocID, err = ue.AppSessionIDGenerator.Allocate(); err != nil {
logger.CtxLog.Warnf("Allocate AppSessionId error: %+v", err)
return ""
}
appSessionID := fmt.Sprintf("%s-%d", ue.Supi, allocID)
return appSessionID
}
// returns SM Policy by IPv4
func (ue *UeContext) SMPolicyFindByIpv4(v4 string) *UeSmPolicyData {
for _, smPolicy := range ue.SmPolicyData {
if smPolicy.PolicyContext.Ipv4Address == v4 {
return smPolicy
}
}
return nil
}
// returns SM Policy by IPv6
func (ue *UeContext) SMPolicyFindByIpv6(v6 string) *UeSmPolicyData {
for _, smPolicy := range ue.SmPolicyData {
if smPolicy.PolicyContext.Ipv6AddressPrefix == v6 {
return smPolicy
}
}
return nil
}
// returns SM Policy by IPv4
func (ue *UeContext) SMPolicyFindByIdentifiersIpv4(
v4 string, sNssai *models.Snssai, dnn string, ipDomain string,
) *UeSmPolicyData {
for _, smPolicy := range ue.SmPolicyData {
policyContext := smPolicy.PolicyContext
if policyContext.Ipv4Address == v4 {
if dnn != "" && policyContext.Dnn != dnn {
continue
}
if ipDomain != "" && policyContext.IpDomain != "" && policyContext.IpDomain != ipDomain {
continue
}
if sNssai != nil && !reflect.DeepEqual(sNssai, policyContext.SliceInfo) {
continue
}
return smPolicy
}
}
return nil
}
// returns SM Policy by IPv6
func (ue *UeContext) SMPolicyFindByIdentifiersIpv6(v6 string, sNssai *models.Snssai, dnn string) *UeSmPolicyData {
for _, smPolicy := range ue.SmPolicyData {
policyContext := smPolicy.PolicyContext
if policyContext.Ipv6AddressPrefix == v6 {
if dnn != "" && policyContext.Dnn != dnn {
continue
}
if sNssai != nil && !reflect.DeepEqual(sNssai, policyContext.SliceInfo) {
continue
}
return smPolicy
}
}
return nil
}
// AppSessionIdStore -
type AppSessionIdStore struct {
AppSessionId string
AppSessionContext models.AppSessionContext
}
var AppSessionContextStore []AppSessionIdStore
// BdtPolicyData_store -
var (
BdtPolicyData_store []models.BdtPolicyData
CreateFailBdtDateStore []models.BdtData
)
// Convert bitRate string to float64 with uint Kbps
func ConvertBitRateToKbps(bitRate string) (kBitRate float64, err error) {
list := strings.Split(bitRate, " ")
if len(list) != 2 {
err = fmt.Errorf("bitRate format error")
return 0, err
}
// parse exponential value with 2 as base
exp := 0.0
switch list[1] {
case "Tbps":
exp = 30.0
case "Gbps":
exp = 20.0
case "Mbps":
exp = 10.0
case "Kbps":
exp = 0.0
case "bps":
exp = -10.0
default:
err = fmt.Errorf("bitRate format error")
return 0, err
}
// parse value from string to float64
kBitRate, err = strconv.ParseFloat(list[0], 64)
if err == nil {
kBitRate = kBitRate * math.Pow(2, exp)
} else {
kBitRate = 0.0
}
return kBitRate, err
}
// Convert bitRate from float64 to String
func ConvertBitRateToString(kBitRate float64) (bitRate string) {
return fmt.Sprintf("%f Kbps", kBitRate)
}
This file contains:
UeContext
struc, this struct represents UE in PCF point of view. According to PCF, the UE has:SUPI
, Hold the Subscription Permanent IdentifierGPSI
, hold the Generic Public SubscriptionPEI
, hold Permanen Equipment IdentifierGroupId
, group identifier for the userPolAssociationIdGenerator
, Policy Association IDAMPolicyData
, dictionary of AM Policy associated to the UEUdrUri
, URI to UDRSmPolicyData
, dictionary of SM Policy associated to the UEAppSessionIdGenerator
AfRoutReq
, AF routing requirementsAspId
, Application Service Provider IDAppSessionIdStore
, dicPolicyDataSubscriptionStore
, struct that store UE policy data subscriptiontype PolicyDataSubscription struct {
// string providing an URI formatted according to IETF RFC 3986.
NotificationUri string `json:"notificationUri" bson:"notificationUri"`
MonitoredResourceUris []string `json:"monitoredResourceUris" bson:"monitoredResourceUris"`
SupportedFeatures string `json:"supportedFeatures,omitempty" bson:"supportedFeatures"`
}
PolicyDataChangeStore
, struct that holds policy data change notificationstype PolicyDataChangeNotification struct {
AmPolicyData *AmPolicyData `json:"amPolicyData,omitempty" yaml:"amPolicyData" bson:"amPolicyData" mapstructure:"AmPolicyData"`
UePolicySet *UePolicySet `json:"uePolicySet,omitempty" yaml:"uePolicySet" bson:"uePolicySet" mapstructure:"UePolicySet"`
SmPolicyData *SmPolicyData `json:"smPolicyData,omitempty" yaml:"smPolicyData" bson:"smPolicyData" mapstructure:"SmPolicyData"`
UsageMonData *UsageMonData `json:"usageMonData,omitempty" yaml:"usageMonData" bson:"usageMonData" mapstructure:"UsageMonData"`
SponsorConnectivityData *SponsorConnectivityData `json:"SponsorConnectivityData,omitempty" yaml:"SponsorConnectivityData" bson:"SponsorConnectivityData" mapstructure:"SponsorConnectivityData"`
BdtData *BdtData `json:"bdtData,omitempty" yaml:"bdtData" bson:"bdtData" mapstructure:"BdtData"`
UeId string `json:"ueId,omitempty" yaml:"ueId" bson:"ueId" mapstructure:"UeId"`
SponsorId string `json:"sponsorId,omitempty" yaml:"sponsorId" bson:"sponsorId" mapstructure:"SponsorId"`
BdtRefId string `json:"bdtRefId,omitempty" yaml:"bdtRefId" bson:"bdtRefId" mapstructure:"BdtRefId"`
UsageMonId string `json:"usageMonId,omitempty" yaml:"usageMonId" bson:"usageMonId" mapstructure:"UsageMonId"`
}
RatingGroupData
, Data related to charging rating groups, keyed by policy IDUeAMPolicyData
, struct that hold Access and Mobility Policy representation. This struct has:PolAssoId
, policy association idAccessType
, 3GPP/non-3GPPNotificationUri
, URI for notificationServingPlmn
, Identifier to Public Land Mobile NetworkAltNotifIpv4Addrs
, alternative IPv4 address for notificationAltNotifIpv6Addrs
, alternative IPv6 address for notificationAmfStatusUri
, URI for AMF status changesGUAMI
, globally unique AMF IdentifierServiceName
, name of the serviceTriggers
, list of request triggersServAreaRes
, Service area restrictiontype ServiceAreaRestriction struct {
RestrictionType RestrictionType `json:"restrictionType,omitempty" yaml:"restrictionType" bson:"restrictionType" mapstructure:"RestrictionType"`
Areas []Area `json:"areas,omitempty" yaml:"areas" bson:"areas" mapstructure:"Areas"`
MaxNumOfTAs int32 `json:"maxNumOfTAs,omitempty" yaml:"maxNumOfTAs" bson:"maxNumOfTAs" mapstructure:"MaxNumOfTAs"`
MaxNumOfTAsForNotAllowedAreas int32 `json:"maxNumOfTAsForNotAllowedAreas,omitempty" yaml:"maxNumOfTAsForNotAllowedAreas" bson:"maxNumOfTAsForNotAllowedAreas" mapstructure:"MaxNumOfTAsForNotAllowedAreas"`
}
Rfsp
, Radio Frequency Sharing PolicyUserLoc
, User Locationtype UserLocation struct {
EutraLocation *EutraLocation `json:"eutraLocation,omitempty" yaml:"eutraLocation" bson:"eutraLocation" mapstructure:"EutraLocation"`
NrLocation *NrLocation `json:"nrLocation,omitempty" yaml:"nrLocation" bson:"nrLocation" mapstructure:"NrLocation"`
N3gaLocation *N3gaLocation `json:"n3gaLocation,omitempty" yaml:"n3gaLocation" bson:"n3gaLocation" mapstructure:"N3gaLocation"`
UtraLocation *UtraLocation `json:"utraLocation,omitempty" yaml:"utraLocation" bson:"utraLocation" mapstructure:"UtraLocation"`
GeraLocation *GeraLocation `json:"geraLocation,omitempty" yaml:"geraLocation" bson:"geraLocation" mapstructure:"GeraLocation"`
}
SuppFeat
, supported featureAmPolicyData
, AM policy data from UDRtype AmPolicyData struct {
SubscCats []string `json:"subscCats,omitempty" bson:"subscCats"`
}
UeSmPolicyData
, struct that represents Session Management Policy associated to the UE. This struct has:PackFiltIdGenerator
, packet filter IDPccRuleIdGenerator
, PCC rule IDChargingIdGenerator
, charging IDPackFiltMapToPccRuleId
, mapping between packet filter IDs to PCC rule IDsRemainGbrUL
, remaining guaranteed bit rate for uplinkRemainGbrDL
, remaining guaranteed bit rate for downlinkSmPolicyData
, SM Policy Data from UDRtype SmPolicyData struct {
SmPolicySnssaiData map[string]SmPolicySnssaiData `json:"smPolicySnssaiData" bson:"smPolicySnssaiData"`
UmDataLimits map[string]UsageMonDataLimit `json:"umDataLimits,omitempty" bson:"umDataLimits"`
UmData map[string]UsageMonData `json:"umData,omitempty" bson:"umData"`
}
PolicyContext
type SmPolicyContextData struct {
AccNetChId *AccNetChId `json:"accNetChId,omitempty" yaml:"accNetChId" bson:"accNetChId" mapstructure:"AccNetChId"`
ChargEntityAddr *AccNetChargingAddress `json:"chargEntityAddr,omitempty" yaml:"chargEntityAddr" bson:"chargEntityAddr" mapstructure:"ChargEntityAddr"`
Gpsi string `json:"gpsi,omitempty" yaml:"gpsi" bson:"gpsi" mapstructure:"Gpsi"`
Supi string `json:"supi" yaml:"supi" bson:"supi" mapstructure:"Supi"`
InterGrpIds []string `json:"interGrpIds,omitempty" yaml:"interGrpIds" bson:"interGrpIds" mapstructure:"InterGrpIds"`
PduSessionId int32 `json:"pduSessionId" yaml:"pduSessionId" bson:"pduSessionId" mapstructure:"PduSessionId"`
PduSessionType PduSessionType `json:"pduSessionType" yaml:"pduSessionType" bson:"pduSessionType" mapstructure:"PduSessionType"`
Chargingcharacteristics string `json:"chargingcharacteristics,omitempty" yaml:"chargingcharacteristics" bson:"chargingcharacteristics" mapstructure:"Chargingcharacteristics"`
Dnn string `json:"dnn" yaml:"dnn" bson:"dnn" mapstructure:"Dnn"`
NotificationUri string `json:"notificationUri" yaml:"notificationUri" bson:"notificationUri" mapstructure:"NotificationUri"`
AccessType AccessType `json:"accessType,omitempty" yaml:"accessType" bson:"accessType" mapstructure:"AccessType"`
RatType RatType `json:"ratType,omitempty" yaml:"ratType" bson:"ratType" mapstructure:"RatType"`
ServingNetwork *NetworkId `json:"servingNetwork,omitempty" yaml:"servingNetwork" bson:"servingNetwork" mapstructure:"ServingNetwork"`
UserLocationInfo *UserLocation `json:"userLocationInfo,omitempty" yaml:"userLocationInfo" bson:"userLocationInfo" mapstructure:"UserLocationInfo"`
UeTimeZone string `json:"ueTimeZone,omitempty" yaml:"ueTimeZone" bson:"ueTimeZone" mapstructure:"UeTimeZone"`
Pei string `json:"pei,omitempty" yaml:"pei" bson:"pei" mapstructure:"Pei"`
Ipv4Address string `json:"ipv4Address,omitempty" yaml:"ipv4Address" bson:"ipv4Address" mapstructure:"Ipv4Address"`
Ipv6AddressPrefix string `json:"ipv6AddressPrefix,omitempty" yaml:"ipv6AddressPrefix" bson:"ipv6AddressPrefix" mapstructure:"Ipv6AddressPrefix"`
// Indicates the IPv4 address domain
IpDomain string `json:"ipDomain,omitempty" yaml:"ipDomain" bson:"ipDomain" mapstructure:"IpDomain"`
SubsSessAmbr *Ambr `json:"subsSessAmbr,omitempty" yaml:"subsSessAmbr" bson:"subsSessAmbr" mapstructure:"SubsSessAmbr"`
SubsDefQos *SubscribedDefaultQos `json:"subsDefQos,omitempty" yaml:"subsDefQos" bson:"subsDefQos" mapstructure:"SubsDefQos"`
// Contains the number of supported packet filter for signalled QoS rules.
NumOfPackFilter int32 `json:"numOfPackFilter,omitempty" yaml:"numOfPackFilter" bson:"numOfPackFilter" mapstructure:"NumOfPackFilter"`
// If it is included and set to true, the online charging is applied to the PDU session.
Online bool `json:"online,omitempty" yaml:"online" bson:"online" mapstructure:"Online"`
// If it is included and set to true, the offline charging is applied to the PDU session.
Offline bool `json:"offline,omitempty" yaml:"offline" bson:"offline" mapstructure:"Offline"`
// If it is included and set to true, the 3GPP PS Data Off is activated by the UE.
Var3gppPsDataOffStatus bool `json:"3gppPsDataOffStatus,omitempty" yaml:"3gppPsDataOffStatus" bson:"3gppPsDataOffStatus" mapstructure:"Var3gppPsDataOffStatus"`
// If it is included and set to true, the reflective QoS is supported by the UE.
RefQosIndication bool `json:"refQosIndication,omitempty" yaml:"refQosIndication" bson:"refQosIndication" mapstructure:"RefQosIndication"`
TraceReq *TraceData `json:"traceReq,omitempty" yaml:"traceReq" bson:"traceReq" mapstructure:"TraceReq"`
SliceInfo *Snssai `json:"sliceInfo" yaml:"sliceInfo" bson:"sliceInfo" mapstructure:"SliceInfo"`
QosFlowUsage QosFlowUsage `json:"qosFlowUsage,omitempty" yaml:"qosFlowUsage" bson:"qosFlowUsage" mapstructure:"QosFlowUsage"`
ServNfId *ServingNfIdentity `json:"servNfId,omitempty" yaml:"servNfId" bson:"servNfId" mapstructure:"ServNfId"`
SuppFeat string `json:"suppFeat,omitempty" yaml:"suppFeat" bson:"suppFeat" mapstructure:"SuppFeat"`
SmfId string `json:"smfId,omitempty" yaml:"smfId" bson:"smfId" mapstructure:"SmfId"`
RecoveryTime *time.Time `json:"recoveryTime,omitempty" yaml:"recoveryTime" bson:"recoveryTime" mapstructure:"RecoveryTime"`
}
PolicyDecision
type SmPolicyDecision struct {
// A map of Sessionrules with the content being the SessionRule as described in subclause 5.6.2.7.
SessRules map[string]*SessionRule `json:"sessRules,omitempty" yaml:"sessRules" bson:"sessRules" mapstructure:"SessRules"`
// A map of PCC rules with the content being the PCCRule as described in subclause 5.6.2.6.
PccRules map[string]*PccRule `json:"pccRules,omitempty" yaml:"pccRules" bson:"pccRules" mapstructure:"PccRules"`
// If it is included and set to true, it indicates the P-CSCF Restoration is requested.
PcscfRestIndication bool `json:"pcscfRestIndication,omitempty" yaml:"pcscfRestIndication" bson:"pcscfRestIndication" mapstructure:"PcscfRestIndication"`
// Map of QoS data policy decisions.
QosDecs map[string]*QosData `json:"qosDecs,omitempty" yaml:"qosDecs" bson:"qosDecs" mapstructure:"QosDecs"`
// Map of Charging data policy decisions.
ChgDecs map[string]*ChargingData `json:"chgDecs,omitempty" yaml:"chgDecs" bson:"chgDecs" mapstructure:"ChgDecs"`
ChargingInfo *ChargingInformation `json:"chargingInfo,omitempty" yaml:"chargingInfo" bson:"chargingInfo" mapstructure:"ChargingInfo"`
// Map of Traffic Control data policy decisions.
TraffContDecs map[string]*TrafficControlData `json:"traffContDecs,omitempty" yaml:"traffContDecs" bson:"traffContDecs" mapstructure:"TraffContDecs"`
// Map of Usage Monitoring data policy decisions.
UmDecs map[string]*UsageMonitoringData `json:"umDecs,omitempty" yaml:"umDecs" bson:"umDecs" mapstructure:"UmDecs"`
// Map of QoS characteristics for non standard 5QIs. This map uses the 5QI values as keys.
QosChars map[string]*QosCharacteristics `json:"qosChars,omitempty" yaml:"qosChars" bson:"qosChars" mapstructure:"QosChars"`
ReflectiveQoSTimer int32 `json:"reflectiveQoSTimer,omitempty" yaml:"reflectiveQoSTimer" bson:"reflectiveQoSTimer" mapstructure:"ReflectiveQoSTimer"`
// A map of condition data with the content being as described in subclause 5.6.2.9.
Conds map[string]*ConditionData `json:"conds,omitempty" yaml:"conds" bson:"conds" mapstructure:"Conds"`
RevalidationTime *time.Time `json:"revalidationTime,omitempty" yaml:"revalidationTime" bson:"revalidationTime" mapstructure:"RevalidationTime"`
// Indicates the offline charging is applicable to the PDU session or PCC rule.
Offline bool `json:"offline,omitempty" yaml:"offline" bson:"offline" mapstructure:"Offline"`
// Indicates the online charging is applicable to the PDU session or PCC rule.
Online bool `json:"online,omitempty" yaml:"online" bson:"online" mapstructure:"Online"`
// Defines the policy control request triggers subscribed by the PCF.
PolicyCtrlReqTriggers []PolicyControlRequestTrigger `json:"policyCtrlReqTriggers,omitempty" yaml:"policyCtrlReqTriggers" bson:"policyCtrlReqTriggers" mapstructure:"PolicyCtrlReqTriggers"`
// Defines the last list of rule control data requested by the PCF.
LastReqRuleData []RequestedRuleData `json:"lastReqRuleData,omitempty" yaml:"lastReqRuleData" bson:"lastReqRuleData" mapstructure:"LastReqRuleData"`
LastReqUsageData *RequestedUsageData `json:"lastReqUsageData,omitempty" yaml:"lastReqUsageData" bson:"lastReqUsageData" mapstructure:"LastReqUsageData"`
// Map of PRA information.
PraInfos map[string]*PresenceInfoRm `json:"praInfos,omitempty" yaml:"praInfos" bson:"praInfos" mapstructure:"PraInfos"`
Ipv4Index int32 `json:"ipv4Index,omitempty" yaml:"ipv4Index" bson:"ipv4Index" mapstructure:"Ipv4Index"`
Ipv6Index int32 `json:"ipv6Index,omitempty" yaml:"ipv6Index" bson:"ipv6Index" mapstructure:"Ipv6Index"`
QosFlowUsage QosFlowUsage `json:"qosFlowUsage,omitempty" yaml:"qosFlowUsage" bson:"qosFlowUsage" mapstructure:"QosFlowUsage"`
SuppFeat string `json:"suppFeat,omitempty" yaml:"suppFeat" bson:"suppFeat" mapstructure:"SuppFeat"`
}
AppSessions
, Dictionary of application sessionsPcfUe
, Corresponding UE ContextSubscriptionID
, subscription IDNewUeAMPolicyData()
, this function create a New UE AM Policy by parsing data from request and put it into the AMPolicyData map
NewUeSmPolicyData()
, this function create a New UE SM policy data by parsing smPolicyContextData object and SmPolicyData object. This function will return the recently created SM Policy Data
RemovePccRule()
, this function will remove PCC rule based on SmPolicyDecision passed in argument, PCC rule Id. This function will return nil if the PCC rule deletion succeeded.
CheckRelatedAfEvents()
, this function check if related AF events exists in smPolicy. The function will return True
if it's find related event and return False
if it doesn't
ArrangeExistEventSubscription
, this function will iterate the policy request triggers and determine whether the triggers is happened or not (using CheckRelatedAfEvents()
) and set the changed flag to true, else, it will append a trigger to trigger list.
IncreaseRemainGBR
and decreaseRemainGBR
, these two function will adjust the GBR based on the 5QI (Qos ID) value and rewrite the value to UeSmPolicy struct
IncreaseRemainBitRate
and DecreaseRemainBitRate
, these two function provide the actual implementation and logic to adjust the bit rate.
FindAMPolicy
this function will return UeAmPolicy object if there is match in AccessType and PLMN id
AllocUeSessionId
this function will generate App Session ID and return the recently created session ID.
SMPolicyFindByIpv4
and SMPolicyFindByIpv6
these functions will return UeSmPolicy if there is IP address match.
AppSessionIdStore
this struct contain:
AppSessionId
, unique identifierAppSessionContext
type AppSessionContext struct {
AscReqData *AppSessionContextReqData `json:"ascReqData,omitempty" yaml:"ascReqData" bson:"ascReqData" mapstructure:"AscReqData"`
AscRespData *AppSessionContextRespData `json:"ascRespData,omitempty" yaml:"ascRespData" bson:"ascRespData" mapstructure:"AscRespData"`
EvsNotif *EventsNotification `json:"evsNotif,omitempty" yaml:"evsNotif" bson:"evsNotif" mapstructure:"EvsNotif"`
}
i. internal/sbi/server.go
j. internal/sbi/api_*.go
k. internal/sbi/consumer/consumer.go
l. internal/sbi/consumer/amf_service.go
m. internal/sbi/consumer/nrf_service.go
n. internal/sbi/consumer/udr_service.go
o. internal/sbi/processor/processor.go
p. internal/sbi/processor/ampolicy.go
q. internal/sbi/processor/bdtpolicy.go
r. internal/sbi/processor/notifier.go
s. internal/sbi/processor/oam.go
t. internal/sbi/processor/policyauthorization.go
u. internal/sbi/processor/smpolicy.go
v. internal/util
example
src : pcf/internal/context/ue.go
dst : smf/internal/context
https://www.etsi.org/deliver/etsi_ts/129500_129599/129513/16.06.00_60/ts_129513v160600p.pdf