Commit d832dafb authored by ppavlidis's avatar ppavlidis
Browse files

Implement NEF endpoints, data model and abstract methods for location retrieval TF

parent d17cf924
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -56,6 +56,14 @@ class NetworkManager(NetworkManagementInterface):
        flow_id = flow_id_mapping[session_info.qosProfile.root]
        subscription.flowInfo = build_flows(flow_id, session_info)

    def core_specific_monitoring_event_validation(self, retrieve_location_request : schemas.RetrievalLocationRequest) -> None:
        if retrieve_location_request.device is None:
            raise ValidationError(
                "Open5GS requires a device to be specified for location retrieval."
            )
    def add_core_specific_location_parameters(self, retrieve_location_request: schemas.RetrievalLocationRequest, subscription: schemas.MonitoringEventSubscriptionRequest) -> None:
        subscription.msisdn = retrieve_location_request.device.phoneNumber


# Note:
# As this class is inheriting from NetworkManagementInterface, it is
+16 −0
Original line number Diff line number Diff line
@@ -31,6 +31,22 @@ def _make_request(method: str, url: str, data=None):
        raise CoreHttpError("connection error") from e
    


# Monitoring Event Methods
def monitoring_event_post(base_url: str, scs_as_id: str, model_payload: BaseModel) -> dict:
    data = model_payload.model_dump_json(exclude_none=True, by_alias=True)
    url = monitoring_event_build_url(base_url, scs_as_id)
    return _make_request("POST", url, data=data)


def monitoring_event_build_url(base_url: str, scs_as_id: str, session_id: str = None):
    url = f"{base_url}/3gpp-monitoring-event/v1/{scs_as_id}/subscriptions"
    if session_id is not None and len(session_id) > 0:
        return f"{url}/{session_id}"
    else:
        return url


# QoD methods
def as_session_with_qos_post(
    base_url: str, scs_as_id: str, model_payload: BaseModel
+41 −0
Original line number Diff line number Diff line
@@ -111,6 +111,17 @@ class NetworkManagementInterface(ABC):
        """
        pass

    def add_core_specific_location_parameters(
        self,
        retrieve_location_request: schemas.RetrievalLocationRequest,
        subscription: schemas.MonitoringEventSubscriptionRequest,
    ):
        """
        Placeholder for adding core-specific parameters to the location subscription.
        This method should be overridden by subclasses to implement specific logic.
        """
        pass

    def core_specific_qod_validation(self, session_info: schemas.CreateSession) -> None:
        """
        Validates core-specific parameters for the session creation.
@@ -141,6 +152,20 @@ class NetworkManagementInterface(ABC):
        # This method should be overridden by subclasses if needed
        pass

    def core_specific_monitoring_event_validation(self, retrieve_location_request: schemas.RetrievalLocationRequest) -> None:
        """
        Validates core-specific parameters for the monitoring event subscription.

        args:
            retrieve_location_request: The request information to validate.

        raises:
            ValidationError: If the request information does not meet core-specific requirements.
        """
        # Placeholder for core-specific validation logic
        # This method should be overridden by subclasses if needed
        pass

    def _build_qod_subscription(
        self, session_info: Dict
    ) -> schemas.AsSessionWithQoSSubscription:
@@ -187,6 +212,22 @@ class NetworkManagementInterface(ABC):
        self.add_core_specific_ti_parameters(traffic_influence_data, subscription)
        return subscription
    
    def _build_monitoring_event_subscription(self, retrieve_location_request: schemas.RetrievalLocationRequest) ->schemas.MonitoringEventSubscriptionRequest:
        pass

    def create_monitoring_event_subscription(self, retrieve_location_request: Dict) -> Dict:
        """
        Creates a Monitoring Event subscription based on CAMARA Location API input.

        args:
            retrieve_location_request: Dictionary containing location retrieval details conforming to
                                       the CAMARA Location API parameters.

        returns:
            dictionary containing the created subscription details, including its ID.
        """
        pass

    def create_qod_session(self, session_info: Dict) -> Dict:
        """
        Creates a QoS session based on CAMARA QoD API input.
+122 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ from ipaddress import IPv4Address, IPv6Address
from typing import Annotated
from uuid import UUID

from pydantic import AnyUrl, BaseModel, ConfigDict, Field, NonNegativeInt, RootModel
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, NonNegativeInt, RootModel, AnyHttpUrl
from pydantic_extra_types.mac_address import MacAddress


@@ -212,6 +212,79 @@ class TrafficInfluSub(BaseModel): # Replace with a meaningful name
        self.snssai = Snssai(sst=sst, sd=sd)


##Monitoring Event API

class DurationMin(BaseModel):
    duration: int = Field(0,description="Unsigned integer identifying a period of time in units of minutes",ge=0)

class PlmnId(BaseModel):
    mcc: str = Field(...,description="String encoding a Mobile Country Code, comprising of 3 digits.")
    mnc: str = Field(...,description="String encoding a Mobile Network Code, comprising of 2 or 3 digits.")

#The enumeration Accuracy represents a desired granularity of accuracy of the requested location information.
class Accuracy(str,Enum):
    cgi_ecgi = "CGI_ECGI" # The AF requests to be notified using cell level location accuracy.
    ta_ra = "TA_RA" # The AF requests to be notified using TA/RA level location accuracy.
    geo_area = "GEO_AREA" # The AF requests to be notified using the geographical area accuracy.
    civic_addr = "CIVIC_ADDR" # The AF requests to be notified using the civic address accuracy. #EDGEAPP

#If locationType set to "LAST_KNOWN_LOCATION", the monitoring event request from AF shall be only for one-time monitoring request
class LocationType(str,Enum):
    CURRENT_LOCATION =  "CURRENT_LOCATION" # The AF requests to be notified for current location.
    LAST_KNOWN = "LAST_KNOWN_LOCATION" # The AF requests to be notified for last known location.

#This data type represents a monitoring event type.
class MonitoringType(str, Enum):
    LOCATION_REPORTING = "LOCATION_REPORTING"

class LocationFailureCause(str,Enum):
    position_denied = "POSITIONING_DENIED" # Positioning is denied.
    unsupported_by_ue = "UNSUPPORTED_BY_UE" # Positioning is not supported by UE.
    not_registered_ue = "NOT_REGISTERED_UE" # UE is not registered.
    unspecified = "UNSPECIFIED" # Unspecified cause.

#This data type represents the user location information which is sent from the NEF to the AF.
class LocationInfo(BaseModel):
    ageOfLocationInfo: DurationMin | None = Field(None,description="Indicates the elapsed time since the last network contact of the UE.")
    cellId: str | None = Field(None, description="Cell ID where the UE is located.")
    trackingAreaId: str | None = Field(None, description="TrackingArea ID where the UE is located.")
    enodeBId: str | None = Field(None, description="eNodeB ID where the UE is located.")
    routingAreaId: str | None = Field(None, description="Routing Area ID where the UE is located")
    plmnId: PlmnId | None = Field(None, description="PLMN ID where the UE is located.")
    twanId: str | None = Field(None, description="TWAN ID where the UE is located.")
    #geographicArea: GeographicArea | None = Field(None,description="Identifies a geographic area of the user where the UE is located.")

class MonitoringEventSubscriptionRequest(BaseModel):
    accuracy: Accuracy | None = Field(None,description="Accuracy represents a desired granularity of accuracy of the requested location information.")
    externalId: str | None = Field(None, description="Identifies a user clause 4.6.2 TS 23.682 (optional)")
    msisdn: str | None = Field(None,description="Identifies the MS internal PSTN/ISDN number allocated for a UE.")
    ipv4Addr: IPv4Address | None = Field(None,description="Identifies the Ipv4 address.")
    ipv6Addr: IPv6Address | None = Field(None,description="Identifies the Ipv6 address.")
    notificationDestination: AnyHttpUrl = Field(..., description="URI of a notification destination that the T8 message shall be delivered to.")
    monitoringType: MonitoringType = Field(..., description="Enumeration of monitoring type. Refer to clause 5.3.2.4.3.")
    maximumNumberOfReports: int | None = Field(None, description="Identifies the maximum number of event reports to be generated by the AMF to the NEF and then the AF.")
    monitorExpireTime: datetime | None = Field(None, description="Identifies the absolute time at which the related monitoring event request is considered to expire.")
    locationType: LocationType | None = Field(None, description="Indicates whether the request is for Current Location, Initial Location, or Last Known Location.")
    repPeriod: DurationSec | None = Field(None,description="Identifies the periodic time for the event reports.")
    minimumReportInterval: DurationSec | None = Field(None,description="identifies a minimum time interval between Location Reporting notifications")


# This data type represents a monitoring event notification which is sent from the NEF to the AF.
class MonitoringEventReport(BaseModel):
    externalId: str | None = Field(None,description="Identifies a user, clause 4.6.2 TS 23.682") 
    msisdn: str | None = Field(None,description="Identifies the MS internal PSTN/ISDN number allocated for a UE.")
    locationInfo: LocationInfo | None = Field(None, description="Indicates the user location related information.")
    locFailureCause: LocationFailureCause | None = Field(None, description="Indicates the location positioning failure cause.")
    monitoringType: MonitoringType = Field(..., description="Identifies the type of monitoring as defined in clause 5.3.2.4.3.")
    eventTime: datetime | None = Field(None, description="Identifies when the event is detected or received. Shall be included for each group of UEs.")

# This data type represents a monitoring notification which is sent from the NEF to the AF.
class MonitoringNotification(BaseModel):
    subscription: AnyHttpUrl = Field(..., description="Link to the subscription resource to which this notification is related.")
    monitoringEventReports: list[MonitoringEventReport] | None = Field(None, description="Each element identifies a monitoring event report (optional).")
    cancelInd: bool | None = Field(False,description="Indicates whether to request to cancel the corresponding monitoring subscription. Set to false or omitted otherwise.")


###############################################################
###############################################################
# CAMARA Models
@@ -291,6 +364,54 @@ class Device(BaseModel):
    ipv6Address: DeviceIpv6Address | None = None


class RetrievalLocationRequest(BaseModel):
    """
    Request to retrieve the location of a device. Device is not required when using a 3-legged access token.
    """
    device: Annotated[Device | None, Field(None,description="End-user device able to connect to a mobile network.")]
    maxAge: Annotated[int | None, Field(None, description="Maximum age of the location information which is accepted for the location retrieval (in seconds).")]
    maxSurface: Annotated[int | None, Field(None,description="Maximum surface in square meters which is accepted by the client for the location retrieval.",ge=1,examples=[1000000])]


class AreaType(str,Enum):
    circle = "CIRCLE" # The area is defined as a circle.
    polygon = "POLYGON" # The area is defined as a polygon.


class Area(RootModel[Annotated[
        AreaType,
        Field(description="""
            Type of this area.
            CIRCLE - The area is defined as a circle.
            POLYGON - The area is defined as a polygon.
            """)]]):
    pass

class Point(BaseModel):
    latitude: Annotated[float,Field(description="Latitude component of a location.",examples=["50.735851"],ge=-90,le=90)]
    longitude: Annotated[float,Field(..., description="Longitude component of location.",examples=["7.10066"],ge=-180,le=180)]

class PointList(RootModel[Annotated[
        list[Point],
        Field(min_length=3,max_length=15, description="List of points defining the area.")]]):
    pass

class Circle(Area):
    center: Annotated[Point, Field(description="Center point of the circle.")]
    radius: Annotated[float,Field(description="Radius of the circle.",ge=1)]

class Polygon(Area):
    boundary: Annotated[PointList, Field(description="List of points defining the polygon.")]

class LastLocationTime(RootModel[Annotated[
        datetime, 
        Field( description="Last date and time when the device was localized.",examples="2023-09-07T10:40:52Z")]]):
    pass

class Location(BaseModel):
    lastLocationTime: Annotated[LastLocationTime, Field(description="Last known location time.")]
    area: Annotated[Area,Field(description="Geographical area of the location.")]

class ApplicationServerIpv4Address(RootModel[str]):
    root: Annotated[
        str,