PFCP
====

The system provides a python object :data:`alc.pfcp` to inspect and modify the PFCP packets.

.. _alc_dot_pfcp:

The :data:`alc.pfcp` object
---------------------------

Attributes
^^^^^^^^^^

The object exposes the header fields as attributes on the object.

.. list-table:: Attributes of alc.pfcp
   :header-rows: 1
   
   * - Attribute
     - Description
     - Access
     - Type
   * - flags
     - Complete flags field as an 8-bit integer
     - ro
     - int
   * - version
     - PFCP version
     - ro
     - int
   * - fo_flag
     - FO flag (follow on) marking another PFCP message follows in the same UDP frame
     - ro
     - bool
   * - mp_flag
     - Flag marking if there is a msg_priority field
     - ro
     - bool
   * - s_flag
     - Flag marking if there is a SEID field
     - ro
     - bool
   * - msg_type
     - Message type
     - ro
     - int
   * - msg_len
     - Message length
     - ro
     - int
   * - seid
     - Session Endpoint Identifier; `None` if the header field is not present
     - ro
     - int|None
   * - seq_number
     - Sequence number
     - ro
     - int
   * - msg_priority
     - Message priority; `None` if the header field is not present
     - ro
     - int|None


Methods
^^^^^^^

.. method:: alc.Pfcp.drop()

   Drops the packet.

.. method:: alc.Pfcp.get_ie_list()

   Returns a list of IE-types that are present in the packet.

   - The order of the elements is the same as they appear in the packet.
   - If there are multiple instances of the same IE, the IE-type appears multiple times in the tuple.

   :rtype: list

.. method:: alc.Pfcp.has(type, /)

   Returns if an IE of the specified type is present at the top-level.

   :param int type: IE-type to find
   :rtype: bool

.. method:: alc.Pfcp.get(type, /)

   Returns all values of the IEs with the specified type.  The values are returned as the exact bytestrings as they appear in the packet.

   - If the specified type does not exist, the result is an empty tuple: ()
   - If a specific instance has zero length or no values, the corresponding bytestring is an empty bytestring: b""

   :param int type: IE-type to fetch
   :rtype: tuple of bytestrings

.. method:: alc.Pfcp.set(type, value, /)

   Replaces the currently present IEs of the specific type with new ones. Multiple values can be specified in a tuple in which case one IE with the specified type is added for each item in the tuple.

   :param int type: IE-type to set
   :param bytes|tuple(bytes) value: value of the IE; length is computed from this value

.. method:: alc.Pfcp.clear(type, /)

   Removes all IEs with the specified type.

   :param int type: IE-type to clear

Constants
^^^^^^^^^

.. list-table:: Message type constants defined on alc.Pfcp
   :header-rows: 1
   
   * - Name
     - Value
   * - MSG_TYPE_HEARTBEAT_REQ
     - 1
   * - MSG_TYPE_HEARTBEAT_RESP
     - 2
   * - MSG_TYPE_PFD_MGMT_REQ
     - 3
   * - MSG_TYPE_PFD_MGMT_RESP
     - 4
   * - MSG_TYPE_ASSOC_SETUP_REQ
     - 5
   * - MSG_TYPE_ASSOC_SETUP_RESP
     - 6
   * - MSG_TYPE_ASSOC_UPDATE_REQ
     - 7
   * - MSG_TYPE_ASSOC_UPDATE_RESP
     - 8
   * - MSG_TYPE_ASSOC_RELEASE_REQ
     - 9
   * - MSG_TYPE_ASSOC_RELEASE_RESP
     - 10
   * - MSG_TYPE_VERSION_NOT_SUP_RESP
     - 11
   * - MSG_TYPE_NODE_REPORT_REQ
     - 12 
   * - MSG_TYPE_NODE_REPORT_RESP
     - 13 
   * - MSG_TYPE_SESSION_SET_DEL_REQ
     - 14 
   * - MSG_TYPE_SESSION_SET_DEL_RESP
     - 15 
   * - MSG_TYPE_SESSION_EST_REQ
     - 50 
   * - MSG_TYPE_SESSION_EST_RESP
     - 51 
   * - MSG_TYPE_SESSION_MOD_REQ
     - 52 
   * - MSG_TYPE_SESSION_MOD_RESP
     - 53 
   * - MSG_TYPE_SESSION_DEL_REQ
     - 54 
   * - MSG_TYPE_SESSION_DEL_RESP
     - 55 
   * - MSG_TYPE_SESSION_REPORT_REQ
     - 56 
   * - MSG_TYPE_SESSION_REPORT_RESP
     - 57 


.. _alc_dot_pfcp_utils:

The :mod:`alc.pfcp_utils` module
---------------------------------

.. module:: alc.pfcp_utils

Decoded IE classes
^^^^^^^^^^^^^^^^^^

The module comes with a list of pre-defined classes which represent a decoded IE.
These classes known how to convert between a bytes-representation of a specific IE and a Python-object of that class that allows direct interaction with its value.

* :class:`AbstractIE`
* :class:`BasicIE`
* :class:`IntegerIE`
* :class:`StringIE`
* :class:`GroupedIE`

Other decoders can be created by subclassing the :class:`AbstractIE` class, providing a sensible `__init__` function and overriding the following two methods:

* :meth:`AbstractIE.encode`
* :meth:`AbstractIE.from_bytes`


.. autoclass:: AbstractIE
   :members:
   :special-members: __eq__

.. autoclass:: BasicIE
   :members:

.. autoclass:: IntegerIE
   :members:

.. autoclass:: StringIE
   :members:

.. autoclass:: GroupedIE
   :members:


Decoding
^^^^^^^^

The module provides a function to decode a bytes representation of the value of an IE.
This function can be passed in the result of a call to :meth:`alc.pfcp.get` as the value for the `iter_of_bytes` argument.

.. autofunction:: decode_ies

Examples
--------
Example 1: Adding and removing IEs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

::

  from alc import pfcp
  from pfcp_utils import IntegerIE, GroupedIE, BasicIE, StringIE

  # remove some IEs from the packet
  pfcp.clear(17)
  pfcp.clear(2)

  # create new IEs and insert them
  new_ies = [
      IntegerIE(109, 4, 1),
      BasicIE(25, b'\x00'),
      GroupedIE(7, [qerid_ie, corrid_ie, gatestatus_ie])
  ]

  for ie in new_ies:
      pfcp.set(ie.type, ie.encode())

  # add multiple IEs of the same type
  pfcp.set(22, (StringIE(22, "ISP1"), StringIE(22, "ISP2")))



Example 2: Fetching and decoding IEs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

::

  from alc import pfcp
  from pfcp_utils import IntegerIE, decode_ies

  # fetch some IEs
  # result will be a tuple of bytestrings
  raw_qer_ies = pfcp.get(109)

  # decode them using an IntegerIE decoder
  qer_ies = decode_ies(109, raw_qer_ies, IntegerIE)



Example 3: Working with grouped IEs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

::

  from alc import pfcp
  from pfcp_utils import IntegerIE, GroupedIE, BasicIE, StringIE, decode_ies

  # Fetch a grouped IE and iterate its sub-IEs
  create_pdrs = decode_ies(1, pfcp.get(1), GroupedIE)

  for pdr in create_pdrs:
    for ie in pdr.sub_ies:
      print(ie)

  # You can pass in extra info about sub-IEs that you want to be decoded automatically
  # Notice that you can specify sub-IEs to be GroupedIE as well, which will cause the
  # decoding to recurse even further.
  sub_decoders = {2: GroupedIE, 20: IntegerIE}
  create_pdrs = decode_ies(1, pfcp.get(1), GroupedIE, sub_decoders = sub_decoders)

  # Find a specific sub-ie
  for pdr in create_pdrs:
      for pdi_ie in pdr.sub_ies_of_type(2):
          print(ie)

  # Find and modify specific sub-sub-ie:
  for pdr in create_pdrs:
      for pdi_ie in pdr.sub_ies_of_type(2):
          for src_iface_ie in pdi_ie.sub_ies_of_type(20):
              src_iface = src_iface_ie.value & 0x0f
              if src_iface == 1:
                  src_iface = 2

  # create a groupedIE
  qerid_ie      = IntegerIE(109, 4, 1)
  corrid_ie     = IntegerIE(28, 4, corrid)
  gatestatus_ie = BasicIE(25, b'\x00')
  qer_ie        = GroupedIE(7, [qerid_ie, corrid_ie, gatestatus_ie])

  # Add a sub-IE to an existing grouped IE:
  for pdr in create_pdrs:
      pdr.sub_ies.append(qerid_ie)

  # In order to propagate these changes into the pfcp-packet, the outer
  # GroupedIE needs to be encoded again and the resulting bytes has to be
  # set on the packet.
  alc.pfcp.set(1, tuple(ie.encode() for ie in create_pdrs))