Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Commit

Permalink
feat: Adds pfcp session report
Browse files Browse the repository at this point in the history
  • Loading branch information
gruyaume committed Jan 7, 2024
1 parent b3db12e commit f833f6a
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ func HandleHeartbeatRequest(sequenceNumber uint32, msg messages.HeartbeatRequest
- [x] PFCP Session Establishment
- [ ] PFCP Session Modification
- [x] PFCP Session Deletion
- [ ] PFCP Session Report
- [x] PFCP Session Report
20 changes: 20 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,23 @@ func (pfcp *Pfcp) SendPFCPSessionDeletionResponse(msg messages.PFCPSessionDeleti
}
return nil
}

func (pfcp *Pfcp) SendPFCPSessionReportRequest(msg messages.PFCPSessionReportRequest, seid uint64, sequenceNumber uint32) error {
header := messages.NewSessionPFCPHeader(messages.PFCPSessionReportRequestMessageType, seid, sequenceNumber)
ies := []ie.InformationElement{msg.ReportType}
err := pfcp.sendPfcpMessage(header, ies)
if err != nil {
return fmt.Errorf("error sending PFCP Session Report Request: %w", err)
}
return nil
}

func (pfcp *Pfcp) SendPFCPSessionReportResponse(msg messages.PFCPSessionReportResponse, seid uint64, sequenceNumber uint32) error {
header := messages.NewSessionPFCPHeader(messages.PFCPSessionReportResponseMessageType, seid, sequenceNumber)
ies := []ie.InformationElement{msg.Cause}
err := pfcp.sendPfcpMessage(header, ies)
if err != nil {
return fmt.Errorf("error sending PFCP Session Report Response: %w", err)
}
return nil
}
37 changes: 32 additions & 5 deletions ie/cause.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,41 @@ import (
type Cause struct {
IEType uint16
Length uint16
Value uint8
Value CauseValue
}

func NewCause(value int) (Cause, error) {
type CauseValue uint8

const (
RequestAccepted CauseValue = 1
MoreUsageReportToSend CauseValue = 2
RequestRejected CauseValue = 64
SessionContextNotFound CauseValue = 65
MandatoryIEMissing CauseValue = 66
ConditionalIEMissing CauseValue = 67
InvalidLength CauseValue = 68
MandatoryIEIncorrect CauseValue = 69
InvalidForwardingPolicy CauseValue = 70
InvalidFTeidAllocation CauseValue = 71
NoEstablishedPFCPAssociation CauseValue = 72
RuleCreationFailure CauseValue = 73
PFCPEntityInCongestion CauseValue = 74
NoResourcesAvailable CauseValue = 75
ServiceNotSupported CauseValue = 76
SystemFailure CauseValue = 77
RedirectionRequested CauseValue = 78
)

func NewCause(value CauseValue) (Cause, error) {
// Validate that value is in the range of supported values
if value < 1 || value > 78 {
return Cause{}, fmt.Errorf("invalid value for Cause: %d", value)
}

return Cause{
IEType: uint16(CauseIEType),
Length: 1,
Value: uint8(value),
Value: value,
}, nil
}

Expand All @@ -30,7 +57,7 @@ func (cause Cause) Serialize() []byte {
binary.Write(buf, binary.BigEndian, uint16(cause.Length))

// Octet 5: Value (1 byte)
buf.WriteByte(cause.Value)
buf.WriteByte(uint8(cause.Value))

return buf.Bytes()
}
Expand All @@ -57,6 +84,6 @@ func DeserializeCause(ieType uint16, ieLength uint16, ieValue []byte) (Cause, er
return Cause{
IEType: ieType,
Length: ieLength,
Value: ieValue[0],
Value: CauseValue(ieValue[0]),
}, nil
}
3 changes: 3 additions & 0 deletions ie/ie.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
CauseIEType IEType = 19
SourceInterfaceIEType IEType = 20
PrecedenceIEType IEType = 29
ReportTypeIEType IEType = 39
UPFunctionFeaturesIEType IEType = 43
ApplyActionIEType IEType = 44
PDRIDIEType IEType = 56
Expand Down Expand Up @@ -83,6 +84,8 @@ func ParseInformationElements(b []byte) ([]InformationElement, error) {
ie, err = DeserializeApplyAction(uint16(ieType), ieLength, ieValue)
case CreateFARIEType:
ie, err = DeserializeCreateFAR(uint16(ieType), ieLength, ieValue)
case ReportTypeIEType:
ie, err = DeserializeReportType(uint16(ieType), ieLength, ieValue)
default:
err = fmt.Errorf("unknown IE type %d", ieType)
}
Expand Down
78 changes: 78 additions & 0 deletions ie/reportType.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package ie

import (
"bytes"
"encoding/binary"
"errors"
)

type Report int

const (
UISR Report = iota
SESR
TMIR
UPIR
ERIR
USAR
DLDR
)

type ReportType struct {
IEType uint16
Length uint16
Reports []Report
}

func NewReportType(reports []Report) (ReportType, error) {
return ReportType{
IEType: uint16(ReportTypeIEType),
Length: 1,
Reports: reports,
}, nil
}

func (reportType ReportType) Serialize() []byte {
buf := new(bytes.Buffer)

// Octets 1 to 2: Type
binary.Write(buf, binary.BigEndian, uint16(ReportTypeIEType))

// Octets 3 to 4: Length
binary.Write(buf, binary.BigEndian, uint16(reportType.Length))

// Octet 5: Reports
// Bit 1: DLDR, Bit 2: USAR, Bit 3: ERIR, Bit 4: UPIR, Bit 5: TMIR, Bit 6: SESR, Bit 7: UISR, Bit 8: Spare
var reportsByte byte = 0
for _, report := range reportType.Reports {
reportsByte |= 1 << report
}
buf.WriteByte(reportsByte)

return buf.Bytes()
}

func (reportType ReportType) IsZeroValue() bool {
return reportType.Length == 0
}

func DeserializeReportType(ieType uint16, ieLength uint16, ieValue []byte) (ReportType, error) {
if len(ieValue) != int(ieLength) {
return ReportType{}, errors.New("invalid length for ReportType")
}

var reports []Report
reportsByte := ieValue[0]

for i := 0; i < 8; i++ {
if reportsByte&(1<<i) != 0 {
reports = append(reports, Report(i))
}
}

return ReportType{
IEType: ieType,
Length: ieLength,
Reports: reports,
}, nil
}
75 changes: 75 additions & 0 deletions ie/reportType_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ie_test

import (
"testing"

"github.com/dot-5g/pfcp/ie"
)

func TestGivenCorrectValueWhenNewReportTypeThenFieldsSetCorrectly(t *testing.T) {
reports := []ie.Report{ie.UISR, ie.SESR}

reportType, err := ie.NewReportType(reports)

if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

if reportType.IEType != uint16(ie.ReportTypeIEType) {
t.Errorf("Expected IE type %d, got %d", ie.ReportTypeIEType, reportType.IEType)
}

if reportType.Length != 1 {
t.Errorf("Expected length 1, got %d", reportType.Length)
}

if len(reportType.Reports) != 2 {
t.Errorf("Expected 2 reports, got %d", len(reportType.Reports))
}

if reportType.Reports[0] != ie.UISR {
t.Errorf("Expected report %d, got %d", ie.UISR, reportType.Reports[0])
}

if reportType.Reports[1] != ie.SESR {
t.Errorf("Expected report %d, got %d", ie.SESR, reportType.Reports[1])
}
}

func TestGivenSerializedWhenDeserializeReportTypeThenFieldsSetCorrectly(t *testing.T) {
reports := []ie.Report{ie.UISR, ie.SESR}

reportType, err := ie.NewReportType(reports)

if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

serializedReportType := reportType.Serialize()

deserializedReportType, err := ie.DeserializeReportType(39, 1, serializedReportType[4:])
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

if deserializedReportType.IEType != uint16(ie.ReportTypeIEType) {
t.Errorf("Expected IE type %d, got %d", ie.ReportTypeIEType, deserializedReportType.IEType)
}

if deserializedReportType.Length != 1 {
t.Errorf("Expected length 1, got %d", deserializedReportType.Length)
}

if len(deserializedReportType.Reports) != 2 {
t.Errorf("Expected 2 reports, got %d", len(deserializedReportType.Reports))
}

if deserializedReportType.Reports[0] != ie.UISR {
t.Errorf("Expected report %d, got %d", ie.UISR, deserializedReportType.Reports[0])
}

if deserializedReportType.Reports[1] != ie.SESR {
t.Errorf("Expected report %d, got %d", ie.SESR, deserializedReportType.Reports[1])
}

}
4 changes: 4 additions & 0 deletions messages/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const (
PFCPSessionEstablishmentResponseMessageType MessageType = 51
PFCPSessionDeletionRequestMessageType MessageType = 54
PFCPSessionDeletionResponseMessageType MessageType = 55
PFCPSessionReportRequestMessageType MessageType = 56
PFCPSessionReportResponseMessageType MessageType = 57
)

type PFCPMessage interface {
Expand All @@ -45,6 +47,8 @@ var messageTypeDeserializers = map[MessageType]DeserializerFunc{
PFCPSessionEstablishmentResponseMessageType: DeserializePFCPSessionEstablishmentResponse,
PFCPSessionDeletionRequestMessageType: DeserializePFCPSessionDeletionRequest,
PFCPSessionDeletionResponseMessageType: DeserializePFCPSessionDeletionResponse,
PFCPSessionReportRequestMessageType: DeserializePFCPSessionReportRequest,
PFCPSessionReportResponseMessageType: DeserializePFCPSessionReportResponse,
}

type PFCPHeader struct {
Expand Down
45 changes: 45 additions & 0 deletions messages/pfcp_session_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package messages

import "github.com/dot-5g/pfcp/ie"

type PFCPSessionReportRequest struct {
ReportType ie.ReportType // Mandatory
}

type PFCPSessionReportResponse struct {
Cause ie.Cause // Mandatory
}

func DeserializePFCPSessionReportRequest(data []byte) (PFCPMessage, error) {
ies, err := ie.ParseInformationElements(data)
var reportType ie.ReportType

for _, elem := range ies {
if reportTypeIE, ok := elem.(ie.ReportType); ok {
reportType = reportTypeIE
continue
}

}

return PFCPSessionReportRequest{
ReportType: reportType,
}, err
}

func DeserializePFCPSessionReportResponse(data []byte) (PFCPMessage, error) {
ies, err := ie.ParseInformationElements(data)
var cause ie.Cause

for _, elem := range ies {
if causeIE, ok := elem.(ie.Cause); ok {
cause = causeIE
continue
}

}

return PFCPSessionReportResponse{
Cause: cause,
}, err
}
34 changes: 34 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type HandlePFCPSessionEstablishmentRequest func(sequenceNumber uint32, seid uint
type HandlePFCPSessionEstablishmentResponse func(sequenceNumber uint32, seid uint64, msg messages.PFCPSessionEstablishmentResponse)
type HandlePFCPSessionDeletionRequest func(sequenceNumber uint32, seid uint64, msg messages.PFCPSessionDeletionRequest)
type HandlePFCPSessionDeletionResponse func(sequenceNumber uint32, seid uint64, msg messages.PFCPSessionDeletionResponse)
type HandlePFCPSessionReportRequest func(sequenceNumber uint32, seid uint64, msg messages.PFCPSessionReportRequest)
type HandlePFCPSessionReportResponse func(sequenceNumber uint32, seid uint64, msg messages.PFCPSessionReportResponse)

type Server struct {
address string
Expand All @@ -40,6 +42,8 @@ type Server struct {
pfcpSessionEstablishmentResponseHandler HandlePFCPSessionEstablishmentResponse
pfcpSessionDeletionRequestHandler HandlePFCPSessionDeletionRequest
pfcpSessionDeletionResponseHandler HandlePFCPSessionDeletionResponse
pfcpSessionReportRequestHandler HandlePFCPSessionReportRequest
pfcpSessionReportResponseHandler HandlePFCPSessionReportResponse
}

func New(address string) *Server {
Expand Down Expand Up @@ -116,6 +120,14 @@ func (server *Server) PFCPSessionDeletionResponse(handler HandlePFCPSessionDelet
server.pfcpSessionDeletionResponseHandler = handler
}

func (server *Server) PFCPSessionReportRequest(handler HandlePFCPSessionReportRequest) {
server.pfcpSessionReportRequestHandler = handler
}

func (server *Server) PFCPSessionReportResponse(handler HandlePFCPSessionReportResponse) {
server.pfcpSessionReportResponseHandler = handler
}

func (server *Server) handleUDPMessage(payload []byte) {
header, genericMessage, err := messages.DeserializePFCPMessage(payload)

Expand Down Expand Up @@ -279,6 +291,28 @@ func (server *Server) handleUDPMessage(payload []byte) {
return
}
server.pfcpSessionDeletionResponseHandler(header.SequenceNumber, header.SEID, msg)
case messages.PFCPSessionReportRequestMessageType:
if server.pfcpSessionReportRequestHandler == nil {
log.Printf("No handler for PFCP Session Report Request")
return
}
msg, ok := genericMessage.(messages.PFCPSessionReportRequest)
if !ok {
log.Printf("Error asserting PFCP Session Report Request type")
return
}
server.pfcpSessionReportRequestHandler(header.SequenceNumber, header.SEID, msg)
case messages.PFCPSessionReportResponseMessageType:
if server.pfcpSessionReportResponseHandler == nil {
log.Printf("No handler for PFCP Session Report Response")
return
}
msg, ok := genericMessage.(messages.PFCPSessionReportResponse)
if !ok {
log.Printf("Error asserting PFCP Session Report Response type")
return
}
server.pfcpSessionReportResponseHandler(header.SequenceNumber, header.SEID, msg)
default:
log.Printf("Unknown PFCP message type: %v", header.MessageType)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pfcp_association_release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func PFCPAssociationReleaseResponse(t *testing.T) {
}

sequenceNumber := uint32(32)
cause, err := ie.NewCause(2)
cause, err := ie.NewCause(ie.RequestAccepted)

if err != nil {
t.Fatalf("Error creating cause IE: %v", err)
Expand Down
Loading

0 comments on commit f833f6a

Please sign in to comment.