diff --git a/client/client.go b/client/client.go index 11a77bd..1371306 100644 --- a/client/client.go +++ b/client/client.go @@ -1,6 +1,7 @@ package client import ( + "fmt" "log" "github.com/dot-5g/pfcp/ie" @@ -22,17 +23,17 @@ func New(ServerAddress string) *Pfcp { return &Pfcp{ServerAddress: ServerAddress, Udp: udpClient} } -func (pfcp *Pfcp) sendPfcpMessage(header messages.PFCPHeader, elements []ie.InformationElement, messageType string) error { +func (pfcp *Pfcp) sendPfcpMessage(header messages.PFCPHeader, elements []ie.InformationElement) error { var payload []byte for _, element := range elements { payload = append(payload, element.Serialize()...) } message := serializeMessage(header, payload) if err := pfcp.Udp.Send(message); err != nil { - log.Printf("Failed to send PFCP %s: %v\n", messageType, err) + log.Printf("Failed to send PFCP: %v\n", err) return err } - log.Printf("PFCP %s sent successfully to %s.\n", messageType, pfcp.ServerAddress) + log.Printf("PFCP sent successfully to %s.\n", pfcp.ServerAddress) return nil } @@ -44,12 +45,30 @@ func serializeMessage(header messages.PFCPHeader, payload []byte) []byte { func (pfcp *Pfcp) SendHeartbeatRequest(recoveryTimeStamp ie.RecoveryTimeStamp, sequenceNumber uint32) (ie.RecoveryTimeStamp, error) { header := messages.NewPFCPHeader(messages.PFCPHeartbeatRequest, sequenceNumber) - err := pfcp.sendPfcpMessage(header, []ie.InformationElement{recoveryTimeStamp}, "Heartbeat Request") - return recoveryTimeStamp, err + payload := []ie.InformationElement{recoveryTimeStamp} + err := pfcp.sendPfcpMessage(header, payload) + if err != nil { + return recoveryTimeStamp, fmt.Errorf("error sending PFCP Heartbeat Request: %w", err) + } + return recoveryTimeStamp, nil } func (pfcp *Pfcp) SendHeartbeatResponse(recoveryTimeStamp ie.RecoveryTimeStamp, sequenceNumber uint32) (ie.RecoveryTimeStamp, error) { header := messages.NewPFCPHeader(messages.PFCPHeartbeatResponse, sequenceNumber) - err := pfcp.sendPfcpMessage(header, []ie.InformationElement{recoveryTimeStamp}, "Heartbeat Response") - return recoveryTimeStamp, err + payload := []ie.InformationElement{recoveryTimeStamp} + err := pfcp.sendPfcpMessage(header, payload) + if err != nil { + return recoveryTimeStamp, fmt.Errorf("error sending PFCP Heartbeat Response: %w", err) + } + return recoveryTimeStamp, nil +} + +func (pfcp *Pfcp) SendPFCPAssociationSetupRequest(nodeID ie.NodeID, recoveryTimeStamp ie.RecoveryTimeStamp, sequenceNumber uint32) error { + header := messages.NewPFCPHeader(messages.PFCPAssociationSetupRequest, sequenceNumber) + payload := []ie.InformationElement{nodeID, recoveryTimeStamp} + err := pfcp.sendPfcpMessage(header, payload) + if err != nil { + return fmt.Errorf("error sending PFCP Association Setup Request: %w", err) + } + return nil } diff --git a/ie/ie.go b/ie/ie.go index bd5db1e..ac5915f 100644 --- a/ie/ie.go +++ b/ie/ie.go @@ -5,6 +5,8 @@ import ( "fmt" ) +const IEHeaderLength = 4 + type InformationElement interface { Serialize() []byte } @@ -15,13 +17,13 @@ func ParseInformationElements(b []byte) ([]InformationElement, error) { index := 0 for index < len(b) { - if len(b[index:]) < 4 { + if len(b[index:]) < IEHeaderLength { return nil, fmt.Errorf("not enough bytes for IE header") } ieType := int(binary.BigEndian.Uint16(b[index : index+2])) ieLength := int(binary.BigEndian.Uint16(b[index+2 : index+4])) - index += 4 // Move past the header + index += IEHeaderLength // Move past the header if len(b[index:]) < ieLength { return nil, fmt.Errorf("not enough bytes for IE data, expected %d, got %d", ieLength, len(b[index:])) diff --git a/ie/nodeId.go b/ie/nodeId.go index d03c05f..923ba37 100644 --- a/ie/nodeId.go +++ b/ie/nodeId.go @@ -3,21 +3,68 @@ package ie import ( "bytes" "encoding/binary" + "fmt" + "net" ) +const ( + IPv4 NodeIDType = iota + IPv6 + FQDN +) + +type NodeIDType int + type NodeID struct { Type uint16 Length uint16 - NodeIDType int + NodeIDType NodeIDType NodeIDValue []byte } -func NewNodeID(nodeIDType int, nodeIDValue []byte) NodeID { +func NewNodeID(nodeIDType NodeIDType, nodeIDValue string) NodeID { + var nodeIDValueBytes []byte + var length uint16 + + switch nodeIDType { + case IPv4: + ip := net.ParseIP(nodeIDValue) + if ip == nil { + panic("invalid IPv4 address") + } + ipv4 := ip.To4() + if ipv4 == nil { + panic("invalid IPv4 address") + } + nodeIDValueBytes = ipv4 + length = uint16(len(nodeIDValueBytes)) + case IPv6: + ip := net.ParseIP(nodeIDValue) + if ip == nil { + panic("invalid IPv6 address") + } + ipv6 := ip.To16() + if ipv6 == nil { + panic("invalid IPv6 address") + } + nodeIDValueBytes = ipv6 + length = uint16(len(nodeIDValueBytes)) + case FQDN: + fqdn := []byte(nodeIDValue) + if len(fqdn) > 255 { + panic("FQDN too long") + } + nodeIDValueBytes = fqdn + length = uint16(len(nodeIDValueBytes)) + + default: + panic(fmt.Sprintf("invalid NodeIDType %d", nodeIDType)) + } return NodeID{ Type: 60, - Length: uint16(len(nodeIDValue) + 1), + Length: length, NodeIDType: nodeIDType, - NodeIDValue: nodeIDValue, + NodeIDValue: nodeIDValueBytes, } } diff --git a/ie/nodeId_test.go b/ie/nodeId_test.go new file mode 100644 index 0000000..435552d --- /dev/null +++ b/ie/nodeId_test.go @@ -0,0 +1,91 @@ +package ie_test + +import ( + "testing" + + "github.com/dot-5g/pfcp/ie" +) + +func TestNewNodeIDIPv4(t *testing.T) { + nodeID := ie.NewNodeID(ie.IPv4, "1.2.3.4") + + if nodeID.Type != 60 { + t.Errorf("Expected NodeID, got %d", nodeID.Type) + } + + if nodeID.Length != 4 { + t.Errorf("Expected NodeID length 4, got %d", nodeID.Length) + } + + if nodeID.NodeIDType != 0 { + t.Errorf("Expected NodeID type IPv4, got %d", nodeID.NodeIDType) + } + + if len(nodeID.NodeIDValue) != 4 { + t.Errorf("Expected NodeID value length 4, got %d", len(nodeID.NodeIDValue)) + } + + expectedNodeIDValue := []byte{1, 2, 3, 4} + for i := range nodeID.NodeIDValue { + if nodeID.NodeIDValue[i] != expectedNodeIDValue[i] { + t.Errorf("Expected NodeID value %v, got %v", expectedNodeIDValue, nodeID.NodeIDValue) + } + } + +} + +func TestNewNodeIDIPv6(t *testing.T) { + nodeID := ie.NewNodeID(ie.IPv6, "2001:db8::68") + + if nodeID.Type != 60 { + t.Errorf("Expected NodeID, got %d", nodeID.Type) + } + + if nodeID.Length != 16 { + t.Errorf("Expected NodeID length 16, got %d", nodeID.Length) + } + + if nodeID.NodeIDType != 1 { + t.Errorf("Expected NodeID type IPv6, got %d", nodeID.NodeIDType) + } + + if len(nodeID.NodeIDValue) != 16 { + t.Errorf("Expected NodeID value length 16, got %d", len(nodeID.NodeIDValue)) + } + + expectedNodeIDValue := []byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104} + for i := range nodeID.NodeIDValue { + if nodeID.NodeIDValue[i] != expectedNodeIDValue[i] { + t.Errorf("Expected NodeID value %v, got %v", expectedNodeIDValue, nodeID.NodeIDValue) + } + } + +} + +func TestNewNodeIDFQDN(t *testing.T) { + nodeID := ie.NewNodeID(ie.FQDN, "www.example.com") + + if nodeID.Type != 60 { + t.Errorf("Expected NodeID, got %d", nodeID.Type) + } + + if nodeID.Length != 15 { + t.Errorf("Expected NodeID length 15, got %d", nodeID.Length) + } + + if nodeID.NodeIDType != 2 { + t.Errorf("Expected NodeID type FQDN, got %d", nodeID.NodeIDType) + } + + if len(nodeID.NodeIDValue) != 15 { + t.Errorf("Expected NodeID value length 15, got %d", len(nodeID.NodeIDValue)) + } + + expectedNodeIDValue := []byte{119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109} + for i := range nodeID.NodeIDValue { + if nodeID.NodeIDValue[i] != expectedNodeIDValue[i] { + t.Errorf("Expected NodeID value %v, got %v", expectedNodeIDValue, nodeID.NodeIDValue) + } + } + +} diff --git a/main_test.go b/main_test.go index 6334170..f686fa0 100644 --- a/main_test.go +++ b/main_test.go @@ -42,6 +42,7 @@ func HandleHeartbeatResponse(sequenceNumber uint32, recoveryTimeStamp ie.Recover func TestServer(t *testing.T) { t.Run("TestHeartbeatRequest", HeartbeatRequest) t.Run("TestHeartbeatResponse", HeartbeatResponse) + t.Run("TestPFCPAssociationSetupRequest", PFCPAssociationSetupRequest) } func HeartbeatRequest(t *testing.T) { @@ -109,3 +110,13 @@ func HeartbeatResponse(t *testing.T) { pfcpServer.Close() } + +func PFCPAssociationSetupRequest(t *testing.T) { + + pfcpClient := client.New("127.0.0.1:8805") + nodeID := ie.NewNodeID(ie.IPv4, "1.2.3.4") + recoveryTimeStamp := ie.NewRecoveryTimeStamp(time.Now()) + sequenceNumber := uint32(21) + pfcpClient.SendPFCPAssociationSetupRequest(nodeID, recoveryTimeStamp, sequenceNumber) + +}