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

feat: Follows spec defined message content #4

Merged
merged 3 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

name: Main workflow

on:
Expand All @@ -17,19 +16,19 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
go-version-file: "go.mod"

- name: Build
run: go build

go-vet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
go-version-file: "go.mod"

- name: Go vet
run: go vet ./...
Expand All @@ -41,7 +40,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
go-version-file: "go.mod"

- name: Unit tests
run: go test ./...
run: go test ./... -cover
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# PFCP

[![GoDoc](https://godoc.org/github.com/dot-5g/pfcp?status.svg)](https://godoc.org/github.com/dot-5g/pfcp)


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
Expand All @@ -17,7 +20,7 @@ import (

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)
}
Expand Down
10 changes: 4 additions & 6 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package client

import (
"encoding/binary"
"log"
"time"

Expand Down Expand Up @@ -41,12 +40,11 @@ func serializeMessage(header messages.PFCPHeader, payload []byte) []byte {
return append(headerBytes, payload...)
}

func (pfcp *Pfcp) SendHeartbeatRequest() (messages.RecoveryTimeStamp, error) {
timestamp := time.Now().Unix()
timeBytes := make([]byte, 8)
binary.BigEndian.PutUint64(timeBytes, uint64(timestamp))
func (pfcp *Pfcp) SendHeartbeatRequest(time time.Time) (messages.RecoveryTimeStamp, error) {
// Create a RecoveryTimeStamp with the current time
recoveryTimeStamp := messages.NewRecoveryTimeStamp(time)
timeBytes := recoveryTimeStamp.ToBytes()
header := messages.NewPFCPHeader(1, 1)
err := pfcp.sendPfcpMessage(header, timeBytes, "Heartbeat Request")
recoveryTimeStamp := messages.RecoveryTimeStamp(time.Unix(timestamp, 0))
return recoveryTimeStamp, err
}
3 changes: 2 additions & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package client_test

import (
"testing"
"time"

"github.com/dot-5g/pfcp/client"
)
Expand All @@ -24,7 +25,7 @@ func TestGivenPfcpWhenSendHeartbeatRequestThenNoError(t *testing.T) {
pfcpClient := client.New("127.0.0.1:8805")
pfcpClient.Udp = mockSender

_, err := pfcpClient.SendHeartbeatRequest()
_, err := pfcpClient.SendHeartbeatRequest(time.Now())
if err != nil {
t.Errorf("SendHeartbeatRequest failed: %v", err)
}
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"log"
"time"

"github.com/dot-5g/pfcp/client"
"github.com/dot-5g/pfcp/messages"
Expand All @@ -10,7 +11,7 @@ import (

func main() {
pfcpClient := client.New("1.2.3.4:8805")
_, err := pfcpClient.SendHeartbeatRequest()
_, err := pfcpClient.SendHeartbeatRequest(time.Now())
if err != nil {
log.Fatalf("SendHeartbeatRequest failed: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestGivenHandleHeartbeatRequestWhenRunThenHeartbeatRequestHandled(t *testin
time.Sleep(time.Second)

pfcpClient := client.New("127.0.0.1:8805")
sentRecoveryTimeStamp, err := pfcpClient.SendHeartbeatRequest()
sentRecoveryTimeStamp, err := pfcpClient.SendHeartbeatRequest(time.Now())
if err != nil {
t.Fatalf("Failed to send Heartbeat request: %v", err)
}
Expand All @@ -44,7 +44,7 @@ func TestGivenHandleHeartbeatRequestWhenRunThenHeartbeatRequestHandled(t *testin
t.Errorf("Heartbeat request handler was not called")
}
if receivedRecoveryTimestamp != sentRecoveryTimeStamp {
t.Errorf("Heartbeat request handler was called with wrong timestamp: %v", receivedRecoveryTimestamp)
t.Errorf("Heartbeat request handler was called with wrong timestamp.\n- Sent timestamp: %v\n- Received timestamp %v\n", sentRecoveryTimeStamp, receivedRecoveryTimestamp)
}
mu.Unlock()

Expand Down
18 changes: 10 additions & 8 deletions messages/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package messages
import (
"bytes"
"encoding/binary"
"fmt"
)

type PFCPHeader struct {
Expand All @@ -15,7 +16,7 @@ type PFCPHeader struct {
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)
// Octet 1: Version (3 bits), Spare (2 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)

Expand All @@ -28,34 +29,35 @@ func SerializePFCPHeader(header PFCPHeader) []byte {
// 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
buf.Write(seqNumBytes[1:])

// 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
Version: 1,
MessageType: messageType,
MessageLength: 0, // To be set later
SequenceNumber: sequenceNumber,
}
}

func ParsePFCPHeader(data []byte) PFCPHeader {
func ParsePFCPHeader(data []byte) (PFCPHeader, error) {
if len(data) != 8 {
return PFCPHeader{}, fmt.Errorf("expected 8 bytes, got %d", len(data))
}

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])
seqNumBytes := []byte{0, data[4], data[5], data[6]}
header.SequenceNumber = binary.BigEndian.Uint32(seqNumBytes)

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

import (
"bytes"
"encoding/binary"
"testing"

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

func TestGivenPfcpHeaderWhenSerializePFCPHeaderThenSerializedCorrectly(t *testing.T) {
pfcpHeader := messages.PFCPHeader{
Version: 1,
MessageType: 2,
MessageLength: 3,
SequenceNumber: 4,
}

headerBytes := messages.SerializePFCPHeader(pfcpHeader)

if len(headerBytes) != 8 {
t.Errorf("Expected 8 bytes, got %d", len(headerBytes))
}

serializedVersion := headerBytes[0] >> 5
if serializedVersion != 1 {
t.Errorf("Expected version 1, got %d", serializedVersion)
}

serializedMessageType := headerBytes[1]
if serializedMessageType != 2 {
t.Errorf("Expected message type 2, got %d", serializedMessageType)
}

serializedMessageLength := binary.BigEndian.Uint16(headerBytes[2:4])
if serializedMessageLength != 3 {
t.Errorf("Expected message length 3, got %d", serializedMessageLength)
}

expectedSeqNum := []byte{0, 0, 4}
serializedSequenceNumber := headerBytes[4:7]
if !bytes.Equal(serializedSequenceNumber, expectedSeqNum) {
t.Errorf("Expected sequence number %v, got %v", expectedSeqNum, serializedSequenceNumber)
}
}
33 changes: 32 additions & 1 deletion messages/heartbeat.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package messages

import (
"encoding/binary"
"time"
)

type RecoveryTimeStamp time.Time
type RecoveryTimeStamp struct {
Type int
Length int
Value int64 // Seconds since 1900
}

type HeartbeatRequest struct {
RecoveryTimeStamp RecoveryTimeStamp
Expand All @@ -13,3 +18,29 @@ type HeartbeatRequest struct {
type HeartbeatResponse struct {
RecoveryTimeStamp RecoveryTimeStamp
}

func NewRecoveryTimeStamp(value time.Time) RecoveryTimeStamp {
return RecoveryTimeStamp{
Type: 96,
Length: 8,
Value: value.Unix() + ntpEpochOffset,
}
}

const ntpEpochOffset = 2208988800 // Offset between Unix and NTP epoch (seconds)

func (rt RecoveryTimeStamp) ToBytes() []byte {
bytes := make([]byte, 8)
binary.BigEndian.PutUint16(bytes[0:2], uint16(rt.Type))
binary.BigEndian.PutUint16(bytes[2:4], uint16(rt.Length))
binary.BigEndian.PutUint32(bytes[4:8], uint32(rt.Value))
return bytes
}

func FromBytes(b []byte) RecoveryTimeStamp {
return RecoveryTimeStamp{
Type: int(binary.BigEndian.Uint16(b[0:2])),
Length: int(binary.BigEndian.Uint16(b[2:4])),
Value: int64(binary.BigEndian.Uint32(b[4:8])),
}
}
34 changes: 11 additions & 23 deletions server/server.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package server

import (
"encoding/binary"
"log"
"net"
"time"

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

const HeaderSize = 8

type HandleHeartbeatRequest func(*messages.HeartbeatRequest)

type HandleHeartbeatResponse func(*messages.HeartbeatResponse)
Expand Down Expand Up @@ -40,25 +40,18 @@ func (server *Server) Run() {

func (server *Server) handleUDPMessage(data []byte, addr net.Addr) {

pfcpMessage := ParseUDPMessage(data)
header, err := messages.ParsePFCPHeader(data[:HeaderSize])
if err != nil {
log.Printf("Error parsing PFCP header: %v", err)
}
pfcpMessage := PfcpMessage{Header: header, Message: data[HeaderSize:]}

if pfcpMessage.Header.MessageType == 1 {
timestampBytes := pfcpMessage.Message

if len(timestampBytes) >= 4 {
timestamp := binary.BigEndian.Uint32(timestampBytes)
recoveryTime := time.Unix(int64(timestamp), 0)

heartbeatRequest := messages.HeartbeatRequest{
RecoveryTimeStamp: messages.RecoveryTimeStamp(recoveryTime),
}

if server.heartbeatRequestHandler != nil {
server.heartbeatRequestHandler(&heartbeatRequest)
}
} else {
log.Printf("Error: timestampBytes slice is too short to contain a valid timestamp.")
recoveryTimeStamp := messages.FromBytes(pfcpMessage.Message)
heartbeatRequest := messages.HeartbeatRequest{
RecoveryTimeStamp: recoveryTimeStamp,
}
server.heartbeatRequestHandler(&heartbeatRequest)
}
}

Expand All @@ -69,8 +62,3 @@ func (server *Server) HeartbeatRequest(handler HandleHeartbeatRequest) {
func (server *Server) HeartbeatResponse(handler HandleHeartbeatResponse) {
server.heartbeatResponseHandler = handler
}

func ParseUDPMessage(data []byte) PfcpMessage {
header := messages.ParsePFCPHeader(data)
return PfcpMessage{Header: header, Message: data[12:]}
}
Loading