diff --git a/ie/ie.go b/ie/ie.go index ac5915f..15516d5 100644 --- a/ie/ie.go +++ b/ie/ie.go @@ -13,6 +13,7 @@ type InformationElement interface { func ParseInformationElements(b []byte) ([]InformationElement, error) { var ies []InformationElement + var err error index := 0 @@ -21,29 +22,30 @@ func ParseInformationElements(b []byte) ([]InformationElement, error) { 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 += IEHeaderLength // Move past the header - - if len(b[index:]) < ieLength { + ieType := binary.BigEndian.Uint16(b[index : index+2]) + ieLength := binary.BigEndian.Uint16(b[index+2 : index+4]) + index += IEHeaderLength + if len(b[index:]) < int(ieLength) { return nil, fmt.Errorf("not enough bytes for IE data, expected %d, got %d", ieLength, len(b[index:])) } - ieValue := b[index : index+ieLength] + ieValue := b[index : index+int(ieLength)] var ie InformationElement switch ieType { + case 60: + ie = DeserializeNodeID(ieType, ieLength, ieValue) case 96: ie = DeserializeRecoveryTimeStamp(ieType, ieLength, ieValue) default: - return nil, fmt.Errorf("unknown IE type %d", ieType) + err = fmt.Errorf("unknown IE type %d", ieType) } if ie != nil { ies = append(ies, ie) } - index += ieLength + index += int(ieLength) } - return ies, nil + return ies, err } diff --git a/ie/nodeId.go b/ie/nodeId.go index 923ba37..630112a 100644 --- a/ie/nodeId.go +++ b/ie/nodeId.go @@ -37,7 +37,7 @@ func NewNodeID(nodeIDType NodeIDType, nodeIDValue string) NodeID { panic("invalid IPv4 address") } nodeIDValueBytes = ipv4 - length = uint16(len(nodeIDValueBytes)) + length = uint16(len(nodeIDValueBytes)) + 1 case IPv6: ip := net.ParseIP(nodeIDValue) if ip == nil { @@ -48,14 +48,14 @@ func NewNodeID(nodeIDType NodeIDType, nodeIDValue string) NodeID { panic("invalid IPv6 address") } nodeIDValueBytes = ipv6 - length = uint16(len(nodeIDValueBytes)) + length = uint16(len(nodeIDValueBytes)) + 1 case FQDN: fqdn := []byte(nodeIDValue) if len(fqdn) > 255 { panic("FQDN too long") } nodeIDValueBytes = fqdn - length = uint16(len(nodeIDValueBytes)) + length = uint16(len(nodeIDValueBytes)) + 1 default: panic(fmt.Sprintf("invalid NodeIDType %d", nodeIDType)) @@ -84,5 +84,19 @@ func (n NodeID) Serialize() []byte { // Octets 6 to n+5: Node ID Value buf.Write(n.NodeIDValue) + fmt.Printf("Node ID - serialized: %v\n", buf.Bytes()) + return buf.Bytes() } + +func DeserializeNodeID(ieType uint16, ieLength uint16, ieValue []byte) NodeID { + nodeIDType := NodeIDType(ieValue[0] & 0x0F) // Ensure NodeIDType is only 4 bits + nodeIDValue := ieValue[1:] + + return NodeID{ + Type: ieType, + Length: ieLength, + NodeIDType: nodeIDType, + NodeIDValue: nodeIDValue, + } +} diff --git a/ie/nodeId_test.go b/ie/nodeId_test.go index 435552d..550e107 100644 --- a/ie/nodeId_test.go +++ b/ie/nodeId_test.go @@ -13,7 +13,7 @@ func TestNewNodeIDIPv4(t *testing.T) { t.Errorf("Expected NodeID, got %d", nodeID.Type) } - if nodeID.Length != 4 { + if nodeID.Length != 4+1 { t.Errorf("Expected NodeID length 4, got %d", nodeID.Length) } @@ -41,7 +41,7 @@ func TestNewNodeIDIPv6(t *testing.T) { t.Errorf("Expected NodeID, got %d", nodeID.Type) } - if nodeID.Length != 16 { + if nodeID.Length != 16+1 { t.Errorf("Expected NodeID length 16, got %d", nodeID.Length) } @@ -69,7 +69,7 @@ func TestNewNodeIDFQDN(t *testing.T) { t.Errorf("Expected NodeID, got %d", nodeID.Type) } - if nodeID.Length != 15 { + if nodeID.Length != 15+1 { t.Errorf("Expected NodeID length 15, got %d", nodeID.Length) } diff --git a/ie/recoveryTimeStamp.go b/ie/recoveryTimeStamp.go index b240971..d5bf65d 100644 --- a/ie/recoveryTimeStamp.go +++ b/ie/recoveryTimeStamp.go @@ -8,8 +8,8 @@ import ( const ntpEpochOffset = 2208988800 // Offset between Unix and NTP epoch (seconds) type RecoveryTimeStamp struct { - Type int - Length int + Type uint16 + Length uint16 Value int64 // Seconds since 1900 } @@ -29,7 +29,7 @@ func (rt RecoveryTimeStamp) Serialize() []byte { return bytes } -func DeserializeRecoveryTimeStamp(ieType int, ieLength int, ieValue []byte) RecoveryTimeStamp { +func DeserializeRecoveryTimeStamp(ieType uint16, ieLength uint16, ieValue []byte) RecoveryTimeStamp { return RecoveryTimeStamp{ Type: ieType, Length: ieLength, diff --git a/main_test.go b/main_test.go index f686fa0..62e73ef 100644 --- a/main_test.go +++ b/main_test.go @@ -24,6 +24,14 @@ var ( heartbeatResponseReceivedSequenceNumber uint32 ) +var ( + pfcpAssociationSetupRequestMu sync.Mutex + pfcpAssociationSetupRequesthandlerCalled bool + pfcpAssociationSetupRequestReceivedSequenceNumber uint32 + pfcpAssociationSetupRequestReceivedRecoveryTimeStamp ie.RecoveryTimeStamp + pfcpAssociationSetupRequestReceivedNodeID ie.NodeID +) + func HandleHeartbeatRequest(sequenceNumber uint32, recoveryTimeStamp ie.RecoveryTimeStamp) { heartbeatRequestMu.Lock() defer heartbeatRequestMu.Unlock() @@ -38,10 +46,18 @@ func HandleHeartbeatResponse(sequenceNumber uint32, recoveryTimeStamp ie.Recover heartbeatResponsereceivedRecoveryTimestamp = recoveryTimeStamp heartbeatResponseReceivedSequenceNumber = sequenceNumber } +func HandlePFCPAssociationSetupRequest(sequenceNumber uint32, nodeID ie.NodeID, recoveryTimeStamp ie.RecoveryTimeStamp) { + pfcpAssociationSetupRequestMu.Lock() + defer pfcpAssociationSetupRequestMu.Unlock() + pfcpAssociationSetupRequesthandlerCalled = true + pfcpAssociationSetupRequestReceivedSequenceNumber = sequenceNumber + pfcpAssociationSetupRequestReceivedRecoveryTimeStamp = recoveryTimeStamp + pfcpAssociationSetupRequestReceivedNodeID = nodeID +} func TestServer(t *testing.T) { - t.Run("TestHeartbeatRequest", HeartbeatRequest) - t.Run("TestHeartbeatResponse", HeartbeatResponse) + // t.Run("TestHeartbeatRequest", HeartbeatRequest) + // t.Run("TestHeartbeatResponse", HeartbeatResponse) t.Run("TestPFCPAssociationSetupRequest", PFCPAssociationSetupRequest) } @@ -112,11 +128,50 @@ func HeartbeatResponse(t *testing.T) { } func PFCPAssociationSetupRequest(t *testing.T) { + pfcpServer := server.New("127.0.0.1:8805") + pfcpServer.PFCPAssociationSetupRequest(HandlePFCPAssociationSetupRequest) + + go pfcpServer.Run() + time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "1.2.3.4") + nodeID := ie.NewNodeID(ie.IPv4, "12.23.34.45") recoveryTimeStamp := ie.NewRecoveryTimeStamp(time.Now()) - sequenceNumber := uint32(21) + sequenceNumber := uint32(32) pfcpClient.SendPFCPAssociationSetupRequest(nodeID, recoveryTimeStamp, sequenceNumber) + time.Sleep(time.Second) + + pfcpAssociationSetupRequestMu.Lock() + if !pfcpAssociationSetupRequesthandlerCalled { + t.Errorf("PFCP Association Setup Request handler was not called") + } + + if pfcpAssociationSetupRequestReceivedSequenceNumber != sequenceNumber { + t.Errorf("PFCP Association Setup Request handler was called with wrong sequence number.\n- Sent sequence number: %v\n- Received sequence number %v\n", sequenceNumber, pfcpAssociationSetupRequestReceivedSequenceNumber) + } + + if pfcpAssociationSetupRequestReceivedRecoveryTimeStamp != recoveryTimeStamp { + t.Errorf("PFCP Association Setup Request handler was called with wrong recovery timestamp.\n- Sent recovery timestamp: %v\n- Received recovery timestamp %v\n", recoveryTimeStamp, pfcpAssociationSetupRequestReceivedRecoveryTimeStamp) + } + + if pfcpAssociationSetupRequestReceivedNodeID.Length != nodeID.Length { + t.Errorf("PFCP Association Setup Request handler was called with wrong node ID length.\n- Sent node ID length: %v\n- Received node ID length %v\n", nodeID.Length, pfcpAssociationSetupRequestReceivedNodeID.Length) + } + + if pfcpAssociationSetupRequestReceivedNodeID.NodeIDType != nodeID.NodeIDType { + t.Errorf("PFCP Association Setup Request handler was called with wrong node ID type.\n- Sent node ID type: %v\n- Received node ID type %v\n", nodeID.NodeIDType, pfcpAssociationSetupRequestReceivedNodeID.NodeIDType) + } + + if len(pfcpAssociationSetupRequestReceivedNodeID.NodeIDValue) != len(nodeID.NodeIDValue) { + t.Errorf("PFCP Association Setup Request handler was called with wrong node ID value length.\n- Sent node ID value length: %v\n- Received node ID value length %v\n", len(nodeID.NodeIDValue), len(pfcpAssociationSetupRequestReceivedNodeID.NodeIDValue)) + } + + for i := range nodeID.NodeIDValue { + if pfcpAssociationSetupRequestReceivedNodeID.NodeIDValue[i] != nodeID.NodeIDValue[i] { + t.Errorf("PFCP Association Setup Request handler was called with wrong node ID value.\n- Sent node ID value: %v\n- Received node ID value %v\n", nodeID.NodeIDValue, pfcpAssociationSetupRequestReceivedNodeID.NodeIDValue) + } + } + + pfcpAssociationSetupRequestMu.Unlock() } diff --git a/network/udpserver.go b/network/udpserver.go index ae57b6f..adb23b5 100644 --- a/network/udpserver.go +++ b/network/udpserver.go @@ -10,10 +10,10 @@ type UdpServer struct { conn *net.UDPConn closeCh chan struct{} wg sync.WaitGroup - Handler func([]byte, net.Addr) + Handler func([]byte) } -func (udpServer *UdpServer) SetHandler(handler func([]byte, net.Addr)) { +func (udpServer *UdpServer) SetHandler(handler func([]byte)) { udpServer.Handler = handler } @@ -40,14 +40,14 @@ func (udpServer *UdpServer) Run(address string) error { return default: buffer := make([]byte, 1024) - length, remoteAddr, err := udpServer.conn.ReadFrom(buffer) + length, _, err := udpServer.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) + go udpServer.Handler(buffer[:length]) } } } diff --git a/server/server.go b/server/server.go index a42e647..bc9e8c1 100644 --- a/server/server.go +++ b/server/server.go @@ -2,7 +2,6 @@ package server import ( "log" - "net" "github.com/dot-5g/pfcp/ie" "github.com/dot-5g/pfcp/messages" @@ -12,13 +11,16 @@ import ( const HeaderSize = 8 const ( - HeartbeatRequestType byte = 1 - HeartbeatResponseType byte = 2 + HeartbeatRequestType byte = 1 + HeartbeatResponseType byte = 2 + PFCPAssociationSetupRequestType byte = 5 + PFCPAssociationSetupResponseType byte = 6 ) -// Define a new handler type that specifically accepts RecoveryTimeStampIE type HandleHeartbeatRequest func(sequenceNumber uint32, recoveryTimeStampIE ie.RecoveryTimeStamp) type HandleHeartbeatResponse func(sequenceNumber uint32, recoveryTimeStampIE ie.RecoveryTimeStamp) +type HandlePFCPAssociationSetupRequest func(sequenceNumber uint32, nodeID ie.NodeID, recoveryTimeStampIE ie.RecoveryTimeStamp) + type MessageHandler func(header messages.PFCPHeader, ies []ie.InformationElement) type Server struct { @@ -73,7 +75,24 @@ func (server *Server) HeartbeatResponse(handler HandleHeartbeatResponse) { }) } -func (server *Server) handleUDPMessage(data []byte, addr net.Addr) { +func (server *Server) PFCPAssociationSetupRequest(handler HandlePFCPAssociationSetupRequest) { + server.registerHandler(PFCPAssociationSetupRequestType, func(header messages.PFCPHeader, ies []ie.InformationElement) { + var recoveryTimeStamp ie.RecoveryTimeStamp + var nodeID ie.NodeID + for _, elem := range ies { + if tsIE, ok := elem.(ie.RecoveryTimeStamp); ok { + recoveryTimeStamp = tsIE + } + if nodeIDIE, ok := elem.(ie.NodeID); ok { + nodeID = nodeIDIE + } + } + + handler(header.SequenceNumber, nodeID, recoveryTimeStamp) + }) +} + +func (server *Server) handleUDPMessage(data []byte) { header, err := messages.ParsePFCPHeader(data[:HeaderSize]) if err != nil { log.Printf("Error parsing PFCP header: %v", err)