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

Commit

Permalink
feat: Adds server side of pfcp interface (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
gruyaume authored Dec 20, 2023
1 parent c5f6f5e commit 5abed22
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 71 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
Copyright 2023 Guillaume Belanger

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,53 @@
# PFCP

A Go library for using the PFCP protocol in 5G networks.
A Go library for using the PFCP protocol in 5G networks as defined in the [ETSI TS 29.244 specification](https://www.etsi.org/deliver/etsi_ts/129200_129299/129244/16.04.00_60/ts_129244v160400p.pdf).

## Usage

### Client

```go
pfcpClient := pfcp.New("1.2.3.4:8805")
err := pfcpClient.SendHeartbeatRequest()
if err != nil {
t.Errorf("SendHeartbeatRequest failed: %v", err)
package main

import (
"log"

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

func main() {
pfcpClient := client.New("1.2.3.4:8805")
err := pfcpClient.SendHeartbeatRequest()
if err != nil {
log.Fatalf("SendHeartbeatRequest failed: %v", err)
}
}
```

### Server


```go
package main

import (
"github.com/dot-5g/pfcp/messages"
"github.com/dot-5g/pfcp/server"
)

func main() {
pfcpServer := server.New("localhost:8805")
pfcpServer.HeartbeatRequest(HandleHeartbeatRequest)
pfcpServer.HeartbeatResponse(HandleHeartbeatResponse)
pfcpServer.Run()
}

func HandleHeartbeatRequest(h *messages.HeartbeatRequest) {
// Do something
}

func HandleHeartbeatResponse(h *messages.HeartbeatResponse) {
// Do something
}

```
42 changes: 11 additions & 31 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"time"

"github.com/dot-5g/pfcp/messages"
"github.com/dot-5g/pfcp/network"
)

Expand All @@ -13,16 +14,6 @@ type Pfcp struct {
Udp network.UdpSender
}

type RecoveryTimeStamp time.Time

type HeartbeatRequest struct {
RecoveryTimeStamp RecoveryTimeStamp
}

type HeartbeatResponse struct {
RecoveryTimeStamp RecoveryTimeStamp
}

func New(ServerAddress string) *Pfcp {

udpClient, err := network.NewUdp(ServerAddress)
Expand All @@ -34,7 +25,7 @@ func New(ServerAddress string) *Pfcp {
return &Pfcp{ServerAddress: ServerAddress, Udp: udpClient}
}

func (pfcp *Pfcp) sendPfcpMessage(header PFCPHeader, payload []byte, messageType string) error {
func (pfcp *Pfcp) sendPfcpMessage(header messages.PFCPHeader, payload []byte, messageType string) error {
message := serializeMessage(header, payload)
if err := pfcp.Udp.Send(message); err != nil {
log.Printf("Failed to send PFCP %s: %v\n", messageType, err)
Expand All @@ -44,29 +35,18 @@ func (pfcp *Pfcp) sendPfcpMessage(header PFCPHeader, payload []byte, messageType
return nil
}

func serializeMessage(header PFCPHeader, payload []byte) []byte {
headerBytes := SerializePFCPHeader(header)
header.MessageLength = uint16(len(headerBytes) + len(payload))
func serializeMessage(header messages.PFCPHeader, payload []byte) []byte {
header.MessageLength = uint16(4 + len(payload))
headerBytes := messages.SerializePFCPHeader(header)
return append(headerBytes, payload...)
}

func (pfcp *Pfcp) SendHeartbeatRequest() error {
request := HeartbeatRequest{RecoveryTimeStamp: RecoveryTimeStamp(time.Now())}
requestBytes := request.ToBytes()
header := NewPFCPHeader(1, 1)
return pfcp.sendPfcpMessage(header, requestBytes, "Heartbeat Request")
}

func (p *Pfcp) SendHeartbeatResponse() error {
response := HeartbeatRequest{RecoveryTimeStamp: RecoveryTimeStamp(time.Now())}
responseBytes := response.ToBytes()
header := NewPFCPHeader(2, 1)
return p.sendPfcpMessage(header, responseBytes, "Heartbeat Response")
}

func (h HeartbeatRequest) ToBytes() []byte {
timestamp := time.Time(h.RecoveryTimeStamp).Unix()
func (pfcp *Pfcp) SendHeartbeatRequest() (messages.RecoveryTimeStamp, error) {
timestamp := time.Now().Unix()
timeBytes := make([]byte, 8)
binary.BigEndian.PutUint64(timeBytes, uint64(timestamp))
return timeBytes
header := messages.NewPFCPHeader(1, 1)
err := pfcp.sendPfcpMessage(header, timeBytes, "Heartbeat Request")
recoveryTimeStamp := messages.RecoveryTimeStamp(time.Unix(timestamp, 0))
return recoveryTimeStamp, err
}
2 changes: 1 addition & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestGivenPfcpWhenSendHeartbeatRequestThenNoError(t *testing.T) {
pfcpClient := client.New("127.0.0.1:8805")
pfcpClient.Udp = mockSender

err := pfcpClient.SendHeartbeatRequest()
_, err := pfcpClient.SendHeartbeatRequest()
if err != nil {
t.Errorf("SendHeartbeatRequest failed: %v", err)
}
Expand Down
32 changes: 0 additions & 32 deletions client/header.go

This file was deleted.

19 changes: 18 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,29 @@ import (
"log"

"github.com/dot-5g/pfcp/client"
"github.com/dot-5g/pfcp/messages"
"github.com/dot-5g/pfcp/server"
)

func main() {
pfcpClient := client.New("1.2.3.4:8805")
err := pfcpClient.SendHeartbeatRequest()
_, err := pfcpClient.SendHeartbeatRequest()
if err != nil {
log.Fatalf("SendHeartbeatRequest failed: %v", err)
}
}

func RunServer() {
pfcpServer := server.New("localhost:8805")
pfcpServer.HeartbeatRequest(HandleHeartbeatRequest)
pfcpServer.HeartbeatResponse(HandleHeartbeatResponse)
pfcpServer.Run()
}

func HandleHeartbeatRequest(h *messages.HeartbeatRequest) {

}

func HandleHeartbeatResponse(h *messages.HeartbeatResponse) {

}
51 changes: 51 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main_test

import (
"sync"
"testing"
"time"

"github.com/dot-5g/pfcp/client"
"github.com/dot-5g/pfcp/messages"
"github.com/dot-5g/pfcp/server"
)

var (
mu sync.Mutex
handlerCalled bool
receivedRecoveryTimestamp messages.RecoveryTimeStamp
)

func HandleHeartbeatRequest(h *messages.HeartbeatRequest) {
mu.Lock()
defer mu.Unlock()
handlerCalled = true
receivedRecoveryTimestamp = h.RecoveryTimeStamp
}

func TestGivenHandleHeartbeatRequestWhenRunThenHeartbeatRequestHandled(t *testing.T) {
pfcpServer := server.New("127.0.0.1:8805")
pfcpServer.HeartbeatRequest(HandleHeartbeatRequest)

go pfcpServer.Run()

time.Sleep(time.Second)

pfcpClient := client.New("127.0.0.1:8805")
sentRecoveryTimeStamp, err := pfcpClient.SendHeartbeatRequest()
if err != nil {
t.Fatalf("Failed to send Heartbeat request: %v", err)
}

time.Sleep(time.Second)

mu.Lock()
if !handlerCalled {
t.Errorf("Heartbeat request handler was not called")
}
if receivedRecoveryTimestamp != sentRecoveryTimeStamp {
t.Errorf("Heartbeat request handler was called with wrong timestamp: %v", receivedRecoveryTimestamp)
}
mu.Unlock()

}
61 changes: 61 additions & 0 deletions messages/header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package messages

import (
"bytes"
"encoding/binary"
)

type PFCPHeader struct {
Version byte
MessageType byte
MessageLength uint16
SequenceNumber uint32
}

func SerializePFCPHeader(header PFCPHeader) []byte {
buf := new(bytes.Buffer)

// Octet 1: Version (3 bits), Spare (3 bits), FO (1 bit set to 0), MP (1 bit set to 0), S (1 bit set to 0)
firstOctet := (header.Version << 5)
buf.WriteByte(firstOctet)

// Octet 2: Message Type (1 byte)
buf.WriteByte(header.MessageType)

// Octets 3 and 4: Message Length (2 bytes)
binary.Write(buf, binary.BigEndian, header.MessageLength)

// Octets 5, 6, and 7: Sequence Number (3 bytes)
seqNumBytes := make([]byte, 4)
binary.BigEndian.PutUint32(seqNumBytes, header.SequenceNumber)
buf.Write(seqNumBytes[0:3]) // Only write the first 3 bytes

// Octet 8: Spare (1 byte set to 0)
buf.WriteByte(0)

return buf.Bytes()
}

// NewPFCPHeader creates a new PFCPHeader with the given message type and sequence number.
func NewPFCPHeader(messageType byte, sequenceNumber uint32) PFCPHeader {
return PFCPHeader{
Version: 1, // Assuming the version is 1
MessageType: messageType,
MessageLength: 0, // To be set later
SequenceNumber: sequenceNumber,
}
}

func ParsePFCPHeader(data []byte) PFCPHeader {

header := PFCPHeader{}
header.Version = data[0] >> 5
header.MessageType = data[1]
header.MessageLength = binary.BigEndian.Uint16(data[2:4])

seqNumBytes := make([]byte, 4)
copy(seqNumBytes, data[4:7])
header.SequenceNumber = binary.BigEndian.Uint32(seqNumBytes)

return header
}
15 changes: 15 additions & 0 deletions messages/heartbeat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package messages

import (
"time"
)

type RecoveryTimeStamp time.Time

type HeartbeatRequest struct {
RecoveryTimeStamp RecoveryTimeStamp
}

type HeartbeatResponse struct {
RecoveryTimeStamp RecoveryTimeStamp
}
46 changes: 46 additions & 0 deletions network/udpserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package network

import (
"log"
"net"
)

type UdpServer struct {
Handler func([]byte, net.Addr)
}

func (udpServer *UdpServer) SetHandler(handler func([]byte, net.Addr)) {
udpServer.Handler = handler
}

func NewUdpServer() *UdpServer {
return &UdpServer{}
}

func (udpServer *UdpServer) Run(address string) error {
addr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return err
}

conn, err := net.ListenUDP("udp", addr)
if err != nil {
return err
}
log.Printf("Listening on %s\n", addr)

defer conn.Close()

for {
buffer := make([]byte, 1024)
length, remoteAddr, err := conn.ReadFrom(buffer)
if err != nil {
log.Printf("Error reading from UDP: %v", err)
continue
}

if udpServer.Handler != nil {
go udpServer.Handler(buffer[:length], remoteAddr)
}
}
}
Loading

0 comments on commit 5abed22

Please sign in to comment.