This is an automated email from the ASF dual-hosted git repository. sruehl pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 232b6645e2c7dc3d5210ab29bb4c277a355196a4 Author: Sebastian Rühl <[email protected]> AuthorDate: Wed Aug 21 22:35:21 2024 +0200 feat(plc4go/bacnet): InitializeRoutingTable --- plc4go/internal/bacnetip/comp.go | 1 + plc4go/internal/bacnetip/npdu.go | 151 ++++++++++++++++++++- plc4go/internal/bacnetip/tests/state_machine.go | 13 ++ .../bacnetip/tests/test_npdu/test_codec_test.go | 97 +++++++++++++ 4 files changed, 258 insertions(+), 4 deletions(-) diff --git a/plc4go/internal/bacnetip/comp.go b/plc4go/internal/bacnetip/comp.go index 98a9174baa..7df264e855 100644 --- a/plc4go/internal/bacnetip/comp.go +++ b/plc4go/internal/bacnetip/comp.go @@ -130,6 +130,7 @@ const ( KWRmtnDNET = KnownKey("rmtnDNET") KWRbtnNetworkList = KnownKey("rbtnNetworkList") KWRatnNetworkList = KnownKey("ratnNetworkList") + KWIrtTable = KnownKey("irtTable") ) type MessageBridge struct { diff --git a/plc4go/internal/bacnetip/npdu.go b/plc4go/internal/bacnetip/npdu.go index 330bd196cf..b6e8d59477 100644 --- a/plc4go/internal/bacnetip/npdu.go +++ b/plc4go/internal/bacnetip/npdu.go @@ -727,16 +727,159 @@ func (r *RouterAvailableToNetwork) String() string { return fmt.Sprintf("RouterAvailableToNetwork{%s, ratnNetworkList: %v}", r._NPDU, r.ratnNetworkList) } -func NewRoutingTableEntry() (*RoutingTableEntry, error) { - panic("implement me") +type RoutingTableEntry struct { + *DebugContents + rtDNET uint16 + rtPortId uint8 + rtPortInfo []byte +} + +func NewRoutingTableEntry(opts ...func(*RoutingTableEntry)) *RoutingTableEntry { + r := &RoutingTableEntry{} + for _, opt := range opts { + opt(r) + } + return r +} + +func WithRoutingTableEntryDestinationNetworkAddress(dnet uint16) func(*RoutingTableEntry) { + return func(r *RoutingTableEntry) { + r.rtDNET = dnet + } +} + +func WithRoutingTableEntryPortId(id uint8) func(*RoutingTableEntry) { + return func(r *RoutingTableEntry) { + r.rtPortId = id + } +} + +func WithRoutingTableEntryPortInfo(portInfo []byte) func(*RoutingTableEntry) { + return func(r *RoutingTableEntry) { + r.rtPortInfo = portInfo + } +} + +func (r *RoutingTableEntry) tuple() (destinationNetworkAddress uint16, portId uint8, portInfoLength uint8, portInfo []byte) { + return r.rtDNET, r.rtPortId, uint8(len(r.rtPortInfo)), r.rtPortInfo +} + +func (r *RoutingTableEntry) Equals(other any) bool { + if r == nil && other == nil { + return true + } + if r == nil { + return false + } + otherEntry, ok := other.(*RoutingTableEntry) + if !ok { + return false + } + return r.rtDNET == otherEntry.rtDNET && + r.rtPortId == otherEntry.rtPortId && + bytes.Equal(r.rtPortInfo, otherEntry.rtPortInfo) +} + +func (r *RoutingTableEntry) String() string { + return fmt.Sprintf("RoutingTableEntry{rtDNET: %d, rtPortId: %d, rtPortInfo: %d}", r.rtDNET, r.rtPortId, r.rtPortInfo) } type InitializeRoutingTable struct { *_NPDU + irtTable []*RoutingTableEntry + + readWriteModel.NLMInitializeRoutingTable } -func NewInitializeRoutingTable() (*InitializeRoutingTable, error) { - panic("implement me") +func NewInitializeRoutingTable(opts ...func(*InitializeRoutingTable)) (*InitializeRoutingTable, error) { + i := &InitializeRoutingTable{} + for _, opt := range opts { + opt(i) + } + i.NLMInitializeRoutingTable = readWriteModel.NewNLMInitializeRoutingTable(i.produceNLMInitializeRoutingTablePortMapping()) + npdu, err := NewNPDU(i.NLMInitializeRoutingTable, nil) + if err != nil { + return nil, errors.Wrap(err, "error creating NPDU") + } + i._NPDU = npdu.(*_NPDU) + return i, nil +} + +func WithInitializeRoutingTableIrtTable(irtTable ...*RoutingTableEntry) func(*InitializeRoutingTable) { + return func(r *InitializeRoutingTable) { + r.irtTable = irtTable + } +} + +func (r *InitializeRoutingTable) GetIrtTable() []*RoutingTableEntry { + return r.irtTable +} + +func (r *InitializeRoutingTable) produceNLMInitializeRoutingTablePortMapping() (numberOfPorts uint8, mappings []readWriteModel.NLMInitializeRoutingTablePortMapping, _ uint16) { + numberOfPorts = uint8(len(r.irtTable)) + mappings = make([]readWriteModel.NLMInitializeRoutingTablePortMapping, numberOfPorts) + for i, entry := range r.irtTable { + mappings[i] = readWriteModel.NewNLMInitializeRoutingTablePortMapping(entry.tuple()) + } + return +} + +func (r *InitializeRoutingTable) produceIRTTable(mappings []readWriteModel.NLMInitializeRoutingTablePortMapping) (irtTable []*RoutingTableEntry) { + irtTable = make([]*RoutingTableEntry, len(mappings)) + for i, entry := range mappings { + irtTable[i] = NewRoutingTableEntry( + WithRoutingTableEntryDestinationNetworkAddress(entry.GetDestinationNetworkAddress()), + WithRoutingTableEntryPortId(entry.GetPortId()), + WithRoutingTableEntryPortInfo(entry.GetPortInfo()), + ) + } + return +} + +func (r *InitializeRoutingTable) Encode(npdu Arg) error { + switch npdu := npdu.(type) { + case NPDU: + if err := npdu.Update(r); err != nil { + return errors.Wrap(err, "error updating _NPCI") + } + for _, rte := range r.irtTable { + npdu.PutShort(int16(rte.rtDNET)) + npdu.Put(rte.rtPortId) + npdu.Put(byte(len(rte.rtPortInfo))) + npdu.PutData(rte.rtPortInfo...) + } + npdu.setNPDU(r.npdu) + npdu.setNLM(r.nlm) + npdu.setAPDU(r.apdu) + return nil + default: + return errors.Errorf("invalid NPDU type %T", npdu) + } +} + +func (r *InitializeRoutingTable) Decode(npdu Arg) error { + switch npdu := npdu.(type) { + case NPDU: + if err := r.Update(npdu); err != nil { + return errors.Wrap(err, "error updating _NPCI") + } + switch pduUserData := npdu.GetPDUUserData().(type) { + case readWriteModel.NPDUExactly: + switch nlm := pduUserData.GetNlm().(type) { + case readWriteModel.NLMInitializeRoutingTable: + r.setNLM(nlm) + r.NLMInitializeRoutingTable = nlm + r.irtTable = r.produceIRTTable(nlm.GetPortMappings()) + } + } + return nil + default: + return errors.Errorf("invalid NPDU type %T", npdu) + } +} + +func (r *InitializeRoutingTable) String() string { + return fmt.Sprintf("InitializeRoutingTable{%s, irtTable: %v}", r._NPDU, r.irtTable) } type InitializeRoutingTableAck struct { diff --git a/plc4go/internal/bacnetip/tests/state_machine.go b/plc4go/internal/bacnetip/tests/state_machine.go index b51f893a69..d1382e5aeb 100644 --- a/plc4go/internal/bacnetip/tests/state_machine.go +++ b/plc4go/internal/bacnetip/tests/state_machine.go @@ -219,6 +219,19 @@ func MatchPdu(localLog zerolog.Logger, pdu bacnetip.PDU, pduType any, pduAttrs m return false } return slices.Equal(net, uint16s) + case bacnetip.KWIrtTable: + irt, ok := pdu.(*bacnetip.InitializeRoutingTable) + if !ok { + return false + } + irts := irt.GetIrtTable() + oirts, ok := attrValue.([]*bacnetip.RoutingTableEntry) + if !ok { + return false + } + return slices.EqualFunc(irts, oirts, func(entry *bacnetip.RoutingTableEntry, entry2 *bacnetip.RoutingTableEntry) bool { + return entry.Equals(entry2) + }) default: panic("implement " + attrName) } diff --git a/plc4go/internal/bacnetip/tests/test_npdu/test_codec_test.go b/plc4go/internal/bacnetip/tests/test_npdu/test_codec_test.go index a13b8794d6..07ed9e924a 100644 --- a/plc4go/internal/bacnetip/tests/test_npdu/test_codec_test.go +++ b/plc4go/internal/bacnetip/tests/test_npdu/test_codec_test.go @@ -79,6 +79,14 @@ func RouterAvailableToNetwork(netList ...uint16) *bacnetip.RouterAvailableToNetw return network } +func InitializeRoutingTable(irtTable ...*bacnetip.RoutingTableEntry) *bacnetip.InitializeRoutingTable { + network, err := bacnetip.NewInitializeRoutingTable(bacnetip.WithInitializeRoutingTableIrtTable(irtTable...)) + if err != nil { + panic(err) + } + return network +} + type TestNPDUCodecSuite struct { suite.Suite @@ -374,6 +382,95 @@ func (suite *TestNPDUCodecSuite) TestRouterAvailableToNetworkNetworks() { // Tes err = suite.Confirmation(bacnetip.NewArgs(&bacnetip.RouterAvailableToNetwork{}), bacnetip.NewKWArgs(bacnetip.KWRatnNetworkList, networkList)) } +func (suite *TestNPDUCodecSuite) TestInitializeRoutingTableEmpty() { // Test the Result encoding and decoding. + // Request successful + pduBytes, err := bacnetip.Xtob( + "01.80" + // version, network layer message + "06 00", // message type and list length + ) + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.NPDUParse(testutils.TestContext(suite.T()), pduBytes, uint16(len(pduBytes))) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(InitializeRoutingTable()), bacnetip.NoKWArgs) + suite.Assert().NoError(err) + err = suite.Indication(bacnetip.NoArgs, bacnetip.NewKWArgs(bacnetip.KWPDUData, pduBytes)) + suite.Assert().NoError(err) + + err = suite.Response(bacnetip.NewArgs(bacnetip.NewPDU(&bacnetip.MessageBridge{Bytes: pduBytes})), bacnetip.NoKWArgs) + suite.Assert().NoError(err) + err = suite.Confirmation(bacnetip.NewArgs(&bacnetip.InitializeRoutingTable{}), bacnetip.NewKWArgs(bacnetip.KWIrtTable, []*bacnetip.RoutingTableEntry{})) +} + +func (suite *TestNPDUCodecSuite) TestInitializeRoutingTable01() { // Test the Result encoding and decoding. + // Request successful + xtob, err := bacnetip.Xtob("") + suite.Require().NoError(err) + rte := RoutingTableEntry(1, 2, xtob) + rtEntries := []*bacnetip.RoutingTableEntry{rte} + + // Request successful + pduBytes, err := bacnetip.Xtob( + "01.80" + // version, network layer message + "06 01" + // message type and list length + "0001 02 00", // network, port number, port info + ) + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.NPDUParse(testutils.TestContext(suite.T()), pduBytes, uint16(len(pduBytes))) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(InitializeRoutingTable(rtEntries...)), bacnetip.NoKWArgs) + suite.Assert().NoError(err) + err = suite.Indication(bacnetip.NoArgs, bacnetip.NewKWArgs(bacnetip.KWPDUData, pduBytes)) + suite.Assert().NoError(err) + + err = suite.Response(bacnetip.NewArgs(bacnetip.NewPDU(&bacnetip.MessageBridge{Bytes: pduBytes})), bacnetip.NoKWArgs) + suite.Assert().NoError(err) + err = suite.Confirmation(bacnetip.NewArgs(&bacnetip.InitializeRoutingTable{}), bacnetip.NewKWArgs(bacnetip.KWIrtTable, rtEntries)) +} + +func (suite *TestNPDUCodecSuite) TestInitializeRoutingTable02() { // Test the Result encoding and decoding. + // Request successful + xtob, err := bacnetip.Xtob("deadbeef") + suite.Require().NoError(err) + rte := RoutingTableEntry(3, 4, xtob) + rtEntries := []*bacnetip.RoutingTableEntry{rte} + + // Request successful + pduBytes, err := bacnetip.Xtob( + "01.80" + // version, network layer message + "06 01" + // message type and list length + "0003 04 04 DEADBEEF", // network, port number, port info + ) + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.NPDUParse(testutils.TestContext(suite.T()), pduBytes, uint16(len(pduBytes))) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(InitializeRoutingTable(rtEntries...)), bacnetip.NoKWArgs) + suite.Assert().NoError(err) + err = suite.Indication(bacnetip.NoArgs, bacnetip.NewKWArgs(bacnetip.KWPDUData, pduBytes)) + suite.Assert().NoError(err) + + err = suite.Response(bacnetip.NewArgs(bacnetip.NewPDU(&bacnetip.MessageBridge{Bytes: pduBytes})), bacnetip.NoKWArgs) + suite.Assert().NoError(err) + err = suite.Confirmation(bacnetip.NewArgs(&bacnetip.InitializeRoutingTable{}), bacnetip.NewKWArgs(bacnetip.KWIrtTable, rtEntries)) +} + func TestNPDUCodec(t *testing.T) { suite.Run(t, new(TestNPDUCodecSuite)) }
