From 00b2c219ea3627b7b237d1037e8251ce53170905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20R=C3=BChl?= Date: Thu, 22 Aug 2024 14:40:25 +0200 Subject: [PATCH] feat(plc4go/bacnetip): add BVLCResult --- .../bacnetip/BACnetVirtualLinkLayerService.go | 58 +++- plc4go/internal/bacnetip/PDU.go | 48 +-- plc4go/internal/bacnetip/bvll.go | 276 +++++++++++++----- plc4go/internal/bacnetip/comp.go | 12 + plc4go/internal/bacnetip/npdu.go | 23 +- .../internal/bacnetip/tests/state_machine.go | 31 ++ .../tests/test_bvll/test_codec_test.go | 204 +++++++++++++ 7 files changed, 531 insertions(+), 121 deletions(-) create mode 100644 plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go diff --git a/plc4go/internal/bacnetip/BACnetVirtualLinkLayerService.go b/plc4go/internal/bacnetip/BACnetVirtualLinkLayerService.go index 39ef11d7175..a195173140a 100644 --- a/plc4go/internal/bacnetip/BACnetVirtualLinkLayerService.go +++ b/plc4go/internal/bacnetip/BACnetVirtualLinkLayerService.go @@ -20,6 +20,7 @@ package bacnetip import ( + "context" "fmt" "time" @@ -322,13 +323,46 @@ func (b *AnnexJCodec) String() string { } func (b *AnnexJCodec) Indication(args Args, kwargs KWArgs) error { - // Note: our BVLC are all annexJ at the moment - return b.Request(args, kwargs) + b.log.Debug().Stringer("args", args).Stringer("kwargs", kwargs).Msg("Indication") + + rpdu := args.Get0PDU() + + // encode it as a generic BVLL PDU + bvlpdu := NewBVLPDU(nil) + // TODO: runtime cast might be dangerous + if err := rpdu.(interface{ Encode(Arg) error }).Encode(bvlpdu); err != nil { + return errors.Wrap(err, "error encoding PDU") + } + + // encode it as a PDU + pdu := NewPDU(nil) + if err := bvlpdu.Encode(pdu); err != nil { + return errors.Wrap(err, "error encoding PDU") + } + + // send it downstream + return b.Request(NewArgs(pdu), NoKWArgs) } func (b *AnnexJCodec) Confirmation(args Args, kwargs KWArgs) error { - // Note: our BVLC are all annexJ at the moment - return b.Response(args, kwargs) + b.log.Debug().Stringer("args", args).Stringer("kwargs", kwargs).Msg("Confirmation") + + pdu := args.Get0PDU() + + // interpret as a BVLL PDU + bvlpdu := NewBVLPDU(nil) + if err := bvlpdu.Decode(pdu); err != nil { + return errors.Wrap(err, "error decoding pdu") + } + + // get the class related to the function + rpdu := BVLPDUTypes[bvlpdu.GetBvlcFunction()]() + if err := rpdu.Decode(bvlpdu); err != nil { + return errors.Wrap(err, "error decoding PDU") + } + + // send it upstream + return b.Response(NewArgs(rpdu), NoKWArgs) } type _BIPSAP interface { @@ -445,7 +479,7 @@ func (b *BIPSimple) String() string { func (b *BIPSimple) Indication(args Args, kwargs KWArgs) error { b.log.Debug().Stringer("Args", args).Stringer("KWArgs", kwargs).Msg("Indication") - pdu := args.Get0NPDU() + pdu := args.Get0PDU() if pdu == nil { return errors.New("no pdu") } @@ -486,7 +520,19 @@ func (b *BIPSimple) Confirmation(args Args, kwargs KWArgs) error { b.log.Debug().Stringer("Args", args).Stringer("KWArgs", kwargs).Msg("Confirmation") pdu := args.Get0PDU() - switch msg := pdu.GetMessage().(type) { + // TODO: come up with a better way to check that... this is hugely inefficient + _data := pdu.GetPDUUserData() + _ = _data + data := pdu.GetPduData() + bvlcParse, err := readWriteModel.NPDUParse(context.Background(), data, uint16(len(data))) + if err != nil { + panic(err) + } + + // TODO: we need to work with the inner types here.... + panic("todo") + + switch msg := bvlcParse.(type) { // some kind of response to a request case readWriteModel.BVLCResultExactly: // send this to the service access point diff --git a/plc4go/internal/bacnetip/PDU.go b/plc4go/internal/bacnetip/PDU.go index 3551ba8ee89..77d3ec52ca8 100644 --- a/plc4go/internal/bacnetip/PDU.go +++ b/plc4go/internal/bacnetip/PDU.go @@ -1057,44 +1057,8 @@ func (d *_PDUData) deepCopy() *_PDUData { return ©PDUData } -type APCI interface { - PCI -} - -type _APCI struct { - *_PCI -} - -func newAPCI(pduUserData spi.Message, pduSource *Address, pduDestination *Address, expectingReply bool, networkPriority readWriteModel.NPDUNetworkPriority) *_APCI { - return &_APCI{ - _PCI: newPCI(pduUserData, pduSource, pduDestination, expectingReply, networkPriority), - } -} - -func (a *_APCI) Update(apci Arg) error { - if err := a._PCI.Update(apci); err != nil { - return errors.Wrap(err, "error updating _PCI") - } - switch pci := apci.(type) { - case APCI: - // TODO: update coordinates.... - return nil - default: - return errors.Errorf("invalid APCI type %T", pci) - } -} - -func (a *_APCI) deepCopy() *_APCI { - _pci := a._PCI.deepCopy() - return &_APCI{_pci} -} - -func (a *_APCI) String() string { - return fmt.Sprintf("APCI{%s}", a._PCI) -} - type PDU interface { - APCI + PCI PDUData GetMessage() spi.Message // TODO: check if we still need that... () DeepCopy() PDU @@ -1106,14 +1070,14 @@ type PDUContract interface { } type _PDU struct { - *_APCI + *_PCI *_PDUData PDUContract } func NewPDU(pduUserData spi.Message, pduOptions ...PDUOption) PDU { p := &_PDU{ - _APCI: newAPCI(pduUserData, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE), + _PCI: newPCI(pduUserData, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE), } p.PDUContract = p for _, option := range pduOptions { @@ -1125,7 +1089,7 @@ func NewPDU(pduUserData spi.Message, pduOptions ...PDUOption) PDU { func NewPDUFromPDUWithNewMessage(pdu PDU, pduUserData spi.Message, pduOptions ...PDUOption) PDU { p := &_PDU{ - _APCI: newAPCI(pduUserData, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()), + _PCI: newPCI(pduUserData, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()), } p.PDUContract = p for _, option := range pduOptions { @@ -1177,7 +1141,9 @@ func (p *_PDU) GetMessage() spi.Message { } func (p *_PDU) deepCopy() *_PDU { - return &_PDU{_APCI: p._APCI.deepCopy(), _PDUData: p._PDUData.deepCopy()} + pduCopy := &_PDU{_PCI: p._PCI.deepCopy(), _PDUData: p._PDUData.deepCopy()} + pduCopy.PDUContract = pduCopy + return pduCopy } func (p *_PDU) DeepCopy() PDU { diff --git a/plc4go/internal/bacnetip/bvll.go b/plc4go/internal/bacnetip/bvll.go index 2308a5d62a9..fd6f23b5a71 100644 --- a/plc4go/internal/bacnetip/bvll.go +++ b/plc4go/internal/bacnetip/bvll.go @@ -28,59 +28,43 @@ import ( "github.com/apache/plc4x/plc4go/spi/utils" "github.com/pkg/errors" + "github.com/rs/zerolog" ) -// BCLPDUTypes is a dictionary of message type values and structs -var BCLPDUTypes map[uint8]func() interface{ Decode(Arg) error } +// BVLPDUTypes is a dictionary of message type values and structs +var BVLPDUTypes map[uint8]func() interface{ Decode(Arg) error } type BVLCI interface { PCI - Update(bvlci Arg) error + Encode(pdu Arg) error Decode(pdu Arg) error - - setBVLC(apdu readWriteModel.BVLC) -} - -// BVLCIContract provides a set of functions which can be overwritten by a sub struct -type BVLCIContract interface { -} - -// BVLCIRequirements provides a set of functions which need to be overwritten by a sub struct -type BVLCIRequirements interface { - BVLCIContract } type _BVLCI struct { *_PCI *DebugContents - BVLCIContract - requirements BVLCIRequirements - - bvlc readWriteModel.BVLC } var _ BVLCI = (*_BVLCI)(nil) -func NewBVLCI(pduUserData spi.Message, requirements BVLCIRequirements) BVLCI { - b := &_BVLCI{ - requirements: requirements, - BVLCIContract: requirements, - } +func NewBVLCI(pduUserData spi.Message) BVLCI { + b := &_BVLCI{} b._PCI = newPCI(pduUserData, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE) return b } -func (b *_BVLCI) setBVLC(bvlc readWriteModel.BVLC) { - b.bvlc = bvlc -} - func (b *_BVLCI) Update(bvlci Arg) error { if err := b._PCI.Update(bvlci); err != nil { return errors.Wrap(err, "Update BVLCI") } - // TODO - return nil + switch bvlci := bvlci.(type) { + case BVLCI: + // TODO: update coordinates.... + return nil + default: + return errors.Errorf("invalid BVLCI type %T", bvlci) + } } func (b *_BVLCI) Encode(pdu Arg) error { @@ -99,43 +83,61 @@ func (b *_BVLCI) Decode(pdu Arg) error { return nil } -func (b *_BVLCI) GetMessage() spi.Message { - return b.bvlc -} - -func (b *_BVLCI) getPDUData() []byte { - if b.GetMessage() == nil { - return nil - } - writeBufferByteBased := utils.NewWriteBufferByteBased() - if err := b.GetMessage().SerializeWithWriteBuffer(context.Background(), writeBufferByteBased); err != nil { - panic(err) // TODO: graceful handle - } - return writeBufferByteBased.GetBytes() -} - func (b *_BVLCI) deepCopy() *_BVLCI { return &_BVLCI{_PCI: b._PCI.deepCopy()} } type BVLPDU interface { + readWriteModel.BVLC BVLCI PDUData + + setBVLC(readWriteModel.BVLC) + getBVLC() readWriteModel.BVLC } type _BVLPDU struct { *_BVLCI *_PDUData + + bvlc readWriteModel.BVLC } var _ BVLPDU = (*_BVLPDU)(nil) func NewBVLPDU(bvlc readWriteModel.BVLC) BVLPDU { - b := &_BVLPDU{} - b._BVLCI = NewBVLCI(bvlc, b).(*_BVLCI) + b := &_BVLPDU{ + bvlc: bvlc, + } + //b.bvlc = readWriteModel.NewBVLC() // TODO: using this function leads to a npe + b._BVLCI = NewBVLCI(bvlc).(*_BVLCI) + b._PDUData = newPDUData(b) return b } +// Deprecated: check if needed as we do it in update +func (b *_BVLPDU) setBVLC(bvlc readWriteModel.BVLC) { + b.bvlc = bvlc +} + +func (b *_BVLPDU) getBVLC() readWriteModel.BVLC { + return b.bvlc +} + +func (b *_BVLPDU) Update(bvlci Arg) error { + if err := b._BVLCI.Update(bvlci); err != nil { + return errors.Wrap(err, "Update BVLCI") + } + switch bvlci := bvlci.(type) { + case BVLCI: + b.bvlc = b.getBVLC() + // TODO: update coordinates.... + return nil + default: + return errors.Errorf("invalid BVLCI type %T", bvlci) + } +} + func (b *_BVLPDU) Encode(pdu Arg) error { if err := b._BVLCI.Encode(pdu); err != nil { return errors.Wrap(err, "error encoding _BVLCI") @@ -161,14 +163,47 @@ func (b *_BVLPDU) Decode(pdu Arg) error { return nil } +func (b *_BVLPDU) GetMessage() spi.Message { + return b.bvlc +} + +func (b *_BVLPDU) getPDUData() []byte { + if b.GetMessage() == nil { + return nil + } + writeBufferByteBased := utils.NewWriteBufferByteBased() + if err := b.GetMessage().SerializeWithWriteBuffer(context.Background(), writeBufferByteBased); err != nil { + panic(err) // TODO: graceful handle + } + return writeBufferByteBased.GetBytes() +} + +func (b *_BVLPDU) GetBvlcFunction() uint8 { + if b.bvlc == nil { + return 0 + } + return b.bvlc.GetBvlcFunction() +} + +func (b *_BVLPDU) GetBvlcPayloadLength() uint16 { + if b.bvlc == nil { + return 0 + } + return b.bvlc.GetBvlcPayloadLength() +} + func (b *_BVLPDU) deepCopy() *_BVLPDU { - return &_BVLPDU{_BVLCI: b._BVLCI.deepCopy(), _PDUData: b._PDUData.deepCopy()} + return &_BVLPDU{_BVLCI: b._BVLCI.deepCopy(), _PDUData: b._PDUData.deepCopy(), bvlc: b.bvlc} } func (b *_BVLPDU) DeepCopy() PDU { return b.deepCopy() } +func (b *_BVLPDU) String() string { + return fmt.Sprintf("_BVLPDU{%s}", b._BVLCI) +} + type Result struct { *_BVLPDU @@ -177,9 +212,12 @@ type Result struct { var _ BVLPDU = (*Result)(nil) -func NewResult() (BVLPDU, error) { +func NewResult(opts ...func(result *Result)) (*Result, error) { b := &Result{} - b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCResult(0)).(*_BVLPDU) + for _, opt := range opts { + opt(b) + } + b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCResult(b.bvlciResultCode)).(*_BVLPDU) return b, nil } @@ -189,6 +227,10 @@ func WithResultBvlciResultCode(code readWriteModel.BVLCResultCode) func(*Result) } } +func (n *Result) GetBvlciResultCode() readWriteModel.BVLCResultCode { + return n.bvlciResultCode +} + func (n *Result) Encode(bvlpdu Arg) error { switch bvlpdu := bvlpdu.(type) { case BVLPDU: @@ -210,7 +252,7 @@ func (n *Result) Decode(bvlpdu Arg) error { return errors.Wrap(err, "error updating BVLPDU") } switch pduUserData := bvlpdu.GetPDUUserData().(type) { - case readWriteModel.BVLCExactly: + case readWriteModel.BVLCResultExactly: switch bvlc := pduUserData.(type) { case readWriteModel.BVLCResult: n.setBVLC(bvlc) @@ -224,30 +266,79 @@ func (n *Result) Decode(bvlpdu Arg) error { } func (n *Result) String() string { - return fmt.Sprintf("Result{%s, bvlciResultCode: %v}", n._BVLPDU, n.bvlciResultCode) + return fmt.Sprintf("Result{%v, bvlciResultCode: %v}", n._BVLPDU, n.bvlciResultCode) } -// TODO: finish type WriteBroadcastDistributionTable struct { *_BVLPDU + + bvlciBDT []*Address } var _ BVLPDU = (*WriteBroadcastDistributionTable)(nil) -func NewWriteBroadcastDistributionTable() (BVLPDU, error) { +func NewWriteBroadcastDistributionTable(opts ...func(*WriteBroadcastDistributionTable)) (*WriteBroadcastDistributionTable, error) { b := &WriteBroadcastDistributionTable{} + for _, opt := range opts { + opt(b) + } b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU) return b, nil } -func (b *WriteBroadcastDistributionTable) Encode(pdu Arg) error { - // TODO: finish - return nil +func WithWriteBroadcastDistributionTable(bdt ...*Address) func(*WriteBroadcastDistributionTable) { + return func(b *WriteBroadcastDistributionTable) { + b.bvlciBDT = bdt + } } -func (b *WriteBroadcastDistributionTable) Decode(pdu Arg) error { - // TODO: finish - return nil +func (w *WriteBroadcastDistributionTable) GetBvlciBDT() []*Address { + return w.bvlciBDT +} + +func (w *WriteBroadcastDistributionTable) Encode(bvlpdu Arg) error { + switch bvlpdu := bvlpdu.(type) { + case BVLPDU: + if err := bvlpdu.Update(w); err != nil { + return errors.Wrap(err, "error updating BVLPDU") + } + for _, bdte := range w.bvlciBDT { + bvlpdu.PutData(bdte.AddrAddress...) + bvlpdu.PutLong(int64(*bdte.AddrMask)) + } + bvlpdu.setBVLC(w.bvlc) + return nil + default: + return errors.Errorf("invalid BVLPDU type %T", bvlpdu) + } +} + +func (w *WriteBroadcastDistributionTable) Decode(bvlpdu Arg) error { + switch bvlpdu := bvlpdu.(type) { + case BVLPDU: + if err := w.Update(bvlpdu); err != nil { + return errors.Wrap(err, "error updating BVLPDU") + } + switch pduUserData := bvlpdu.GetPDUUserData().(type) { + case readWriteModel.BVLCWriteBroadcastDistributionTableExactly: + switch bvlc := pduUserData.(type) { + case readWriteModel.BVLCWriteBroadcastDistributionTable: + w.setBVLC(bvlc) + for _, entry := range bvlc.GetTable() { + // TODO: what is with port and the map?? + address, _ := NewAddress(zerolog.Nop(), entry.GetIp()) + w.bvlciBDT = append(w.bvlciBDT, address) + } + } + } + return nil + default: + return errors.Errorf("invalid BVLPDU type %T", bvlpdu) + } +} + +func (w *WriteBroadcastDistributionTable) String() string { + return fmt.Sprintf("WriteBroadcastDistributionTable{%v, bvlciBDT: %v}", w._BVLPDU, w.bvlciBDT) } // TODO: finish @@ -436,28 +527,50 @@ func (b *DistributeBroadcastToNetwork) Decode(pdu Arg) error { type OriginalUnicastNPDU struct { *_BVLPDU + + // post construct function + _postConstruct []func() } var _ BVLPDU = (*OriginalUnicastNPDU)(nil) -func NewOriginalUnicastNPDU(npdu NPDU, opts ...func(*OriginalUnicastNPDU)) (BVLPDU, error) { +func NewOriginalUnicastNPDU(pdu PDU, opts ...func(*OriginalUnicastNPDU)) (BVLPDU, error) { b := &OriginalUnicastNPDU{} for _, opt := range opts { opt(b) } - b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCOriginalUnicastNPDU(npdu, npdu.GetLengthInBytes(context.Background()))).(*_BVLPDU) + switch npdu := pdu.(type) { + case readWriteModel.NPDUExactly: + b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCOriginalUnicastNPDU(npdu, npdu.GetLengthInBytes(context.Background()))).(*_BVLPDU) + default: + // TODO: re-encode seems expensive... check if there is a better option (e.g. only do it on the message bridge) + parse, err := readWriteModel.BVLCParse(context.Background(), pdu.GetPduData()) + if err != nil { + return nil, errors.Wrap(err, "error re-encoding") + } + b._BVLPDU = NewBVLPDU(parse).(*_BVLPDU) + } + // Do a post construct for a bit more easy initialization + for _, f := range b._postConstruct { + f() + } + b._postConstruct = nil return b, nil } func WithOriginalUnicastNPDUDestination(destination *Address) func(*OriginalUnicastNPDU) { return func(o *OriginalUnicastNPDU) { - o.pduDestination = destination + o._postConstruct = append(o._postConstruct, func() { + o.SetPDUDestination(destination) + }) } } func WithOriginalUnicastNPDUUserData(userData spi.Message) func(*OriginalUnicastNPDU) { return func(o *OriginalUnicastNPDU) { - o.pduUserData = userData + o._postConstruct = append(o._postConstruct, func() { + o.SetPDUUserData(userData) + }) } } @@ -468,6 +581,7 @@ func (n *OriginalUnicastNPDU) Encode(bvlpdu Arg) error { return errors.Wrap(err, "error updating BVLPDU") } bvlpdu.setBVLC(n.bvlc) + bvlpdu.PutData(n.getPDUData()...) return nil default: return errors.Errorf("invalid BVLPDU type %T", bvlpdu) @@ -485,6 +599,7 @@ func (n *OriginalUnicastNPDU) Decode(bvlpdu Arg) error { switch bvlc := pduUserData.(type) { case readWriteModel.BVLCOriginalUnicastNPDU: n.setBVLC(bvlc) + n.PutData(bvlpdu.GetPduData()...) } } return nil @@ -499,26 +614,49 @@ func (n *OriginalUnicastNPDU) String() string { type OriginalBroadcastNPDU struct { *_BVLPDU + + // post construct function + _postConstruct []func() } -func NewOriginalBroadcastNPDU(npdu NPDU, opts ...func(*OriginalBroadcastNPDU)) (BVLPDU, error) { +func NewOriginalBroadcastNPDU(pdu PDU, opts ...func(*OriginalBroadcastNPDU)) (BVLPDU, error) { b := &OriginalBroadcastNPDU{} for _, opt := range opts { opt(b) } - b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCOriginalBroadcastNPDU(npdu, npdu.GetLengthInBytes(context.Background()))).(*_BVLPDU) + switch npdu := pdu.(type) { + case readWriteModel.NPDUExactly: + b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCOriginalBroadcastNPDU(npdu, npdu.GetLengthInBytes(context.Background()))).(*_BVLPDU) + default: + // TODO: re-encode seems expensive... check if there is a better option (e.g. only do it on the message bridge) + parse, err := readWriteModel.BVLCParse(context.Background(), pdu.GetPduData()) + if err != nil { + return nil, errors.Wrap(err, "error re-encoding") + } + b._BVLPDU = NewBVLPDU(parse).(*_BVLPDU) + } + + // Do a post construct for a bit more easy initialization + for _, f := range b._postConstruct { + f() + } + b._postConstruct = nil return b, nil } func WithOriginalBroadcastNPDUDestination(destination *Address) func(*OriginalBroadcastNPDU) { return func(o *OriginalBroadcastNPDU) { - o.pduDestination = destination + o._postConstruct = append(o._postConstruct, func() { + o.SetPDUDestination(destination) + }) } } func WithOriginalBroadcastNPDUUserData(userData spi.Message) func(*OriginalBroadcastNPDU) { return func(o *OriginalBroadcastNPDU) { - o.pduUserData = userData + o._postConstruct = append(o._postConstruct, func() { + o.SetPDUUserData(userData) + }) } } @@ -529,6 +667,7 @@ func (n *OriginalBroadcastNPDU) Encode(bvlpdu Arg) error { return errors.Wrap(err, "error updating BVLPDU") } bvlpdu.setBVLC(n.bvlc) + bvlpdu.PutData(n.getPDUData()...) return nil default: return errors.Errorf("invalid BVLPDU type %T", bvlpdu) @@ -546,6 +685,7 @@ func (n *OriginalBroadcastNPDU) Decode(bvlpdu Arg) error { switch bvlc := pduUserData.(type) { case readWriteModel.BVLCOriginalBroadcastNPDU: n.setBVLC(bvlc) + n.PutData(bvlpdu.GetPduData()...) } } return nil @@ -559,7 +699,7 @@ func (n *OriginalBroadcastNPDU) String() string { } func init() { - BCLPDUTypes = map[uint8]func() interface{ Decode(Arg) error }{ + BVLPDUTypes = map[uint8]func() interface{ Decode(Arg) error }{ 0x00: func() interface{ Decode(Arg) error } { v, _ := NewResult() return v diff --git a/plc4go/internal/bacnetip/comp.go b/plc4go/internal/bacnetip/comp.go index 23e4cd5022d..fd37ac093fe 100644 --- a/plc4go/internal/bacnetip/comp.go +++ b/plc4go/internal/bacnetip/comp.go @@ -137,6 +137,18 @@ const ( KWDctnDNET = KnownKey("dctnDNET") KWNniNet = KnownKey("nniNet") KWNniFlag = KnownKey("nniFlag") + + //// + // BVLL related keys + + KWBvlciResultCode = KnownKey("bvlciResultCode") + KWBvlciBDT = KnownKey("bvlciBDT") + KWBvlciAddress = KnownKey("bvlciAddress") + KWFdAddress = KnownKey("fdAddress") + KWFdTTL = KnownKey("fdTTL") + KWFdRemain = KnownKey("fdRemain") + KWBvlciTimeToLive = KnownKey("bvlciTimeToLive") + KWBvlciFDT = KnownKey("bvlciFDT") ) type MessageBridge struct { diff --git a/plc4go/internal/bacnetip/npdu.go b/plc4go/internal/bacnetip/npdu.go index b945051af18..b9b82d89164 100644 --- a/plc4go/internal/bacnetip/npdu.go +++ b/plc4go/internal/bacnetip/npdu.go @@ -43,7 +43,8 @@ type NPCI interface { Encode(pdu Arg) error Decode(pdu Arg) error - setNLM(nlm readWriteModel.NLM) + setNLM(readWriteModel.NLM) + getNLM() readWriteModel.NLM } type _NPCI struct { @@ -60,6 +61,10 @@ func NewNPCI(pduUserData spi.Message, nlm readWriteModel.NLM) NPCI { nlm: nlm, } n._PCI = newPCI(pduUserData, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE) + switch nlm := pduUserData.(type) { + case readWriteModel.NLMExactly: + n.nlm = nlm + } return n } @@ -71,20 +76,26 @@ func (n *_NPCI) GetNPDUNetMessage() *uint8 { return &messageType } +// Deprecated: check if needed as we do it in update func (n *_NPCI) setNLM(nlm readWriteModel.NLM) { n.nlm = nlm } +func (n *_NPCI) getNLM() readWriteModel.NLM { + return n.nlm +} + func (n *_NPCI) Update(npci Arg) error { if err := n._PCI.Update(npci); err != nil { return errors.Wrap(err, "error updating _PCI") } - switch pci := npci.(type) { + switch npci := npci.(type) { case NPCI: - // TODO: update coordinates.... + n.nlm = npci.getNLM() + // TODO: update coordinates... return nil default: - return errors.Errorf("invalid APCI type %T", pci) + return errors.Errorf("invalid APCI type %T", npci) } } @@ -105,7 +116,7 @@ func (n *_NPCI) Decode(pdu Arg) error { } func (n *_NPCI) deepCopy() *_NPCI { - return &_NPCI{_PCI: n._PCI.deepCopy()} + return &_NPCI{_PCI: n._PCI.deepCopy(), nlm: n.nlm} } type NPDU interface { @@ -347,7 +358,7 @@ func (n *_NPDU) GetPayloadSubtraction() uint16 { } func (n *_NPDU) deepCopy() *_NPDU { - return &_NPDU{_NPCI: n._NPCI.deepCopy(), _PDUData: n._PDUData.deepCopy()} + return &_NPDU{_NPCI: n._NPCI.deepCopy(), _PDUData: n._PDUData.deepCopy(), npdu: n.npdu, apdu: n.apdu} } func (n *_NPDU) DeepCopy() PDU { diff --git a/plc4go/internal/bacnetip/tests/state_machine.go b/plc4go/internal/bacnetip/tests/state_machine.go index 284a8fa6869..c3f702de819 100644 --- a/plc4go/internal/bacnetip/tests/state_machine.go +++ b/plc4go/internal/bacnetip/tests/state_machine.go @@ -275,6 +275,37 @@ func MatchPdu(localLog zerolog.Logger, pdu bacnetip.PDU, pduType any, pduAttrs m return false } return nni.GetNniFlag() == attrValue + case bacnetip.KWBvlciResultCode: + r, ok := pdu.(*bacnetip.Result) + if !ok { + return false + } + return r.GetBvlciResultCode() == attrValue + case bacnetip.KWBvlciBDT: + wbdt, ok := pdu.(*bacnetip.WriteBroadcastDistributionTable) + if !ok { + return false + } + iwbdt := wbdt.GetBvlciBDT() + owbdt, ok := attrValue.([]*bacnetip.Address) + if !ok { + return false + } + return slices.EqualFunc(iwbdt, owbdt, func(a *bacnetip.Address, b *bacnetip.Address) bool { + return a.Equals(b) + }) + case bacnetip.KWBvlciAddress: + panic("implement me") + case bacnetip.KWFdAddress: + panic("implement me") + case bacnetip.KWFdTTL: + panic("implement me") + case bacnetip.KWFdRemain: + panic("implement me") + case bacnetip.KWBvlciTimeToLive: + panic("implement me") + case bacnetip.KWBvlciFDT: + panic("implement me") default: panic("implement " + attrName) } diff --git a/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go b/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go new file mode 100644 index 00000000000..e38f2cb46fc --- /dev/null +++ b/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package test_bvll + +import ( + "testing" + + "github.com/apache/plc4x/plc4go/internal/bacnetip" + "github.com/apache/plc4x/plc4go/internal/bacnetip/tests" + readWriteModel "github.com/apache/plc4x/plc4go/protocols/bacnetip/readwrite/model" + "github.com/apache/plc4x/plc4go/spi/testutils" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/suite" +) + +func Result(i uint16) *bacnetip.Result { + result, err := bacnetip.NewResult(bacnetip.WithResultBvlciResultCode(readWriteModel.BVLCResultCode(i))) + if err != nil { + panic(err) + } + return result +} + +func WriteBroadcastDistributionTable(bdt ...*bacnetip.Address) *bacnetip.WriteBroadcastDistributionTable { + writeBroadcastDistributionTable, err := bacnetip.NewWriteBroadcastDistributionTable(bacnetip.WithWriteBroadcastDistributionTable(bdt...)) + if err != nil { + panic(err) + } + return writeBroadcastDistributionTable +} + +type TestAnnexJCodecSuite struct { + suite.Suite + + client *tests.TrappedClient + codec *bacnetip.AnnexJCodec + server *tests.TrappedServer + + log zerolog.Logger +} + +func (suite *TestAnnexJCodecSuite) SetupSuite() { + suite.log = testutils.ProduceTestingLogger(suite.T()) +} + +func (suite *TestAnnexJCodecSuite) SetupTest() { + // minature trapped stack + var err error + suite.codec, err = bacnetip.NewAnnexJCodec(suite.log) + suite.Require().NoError(err) + suite.client, err = tests.NewTrappedClient(suite.log) + suite.Require().NoError(err) + suite.server, err = tests.NewTrappedServer(suite.log) + suite.Require().NoError(err) + err = bacnetip.Bind(suite.log, suite.client, suite.codec, suite.server) + suite.Require().NoError(err) +} + +// Pass the PDU to the client to send down the stack. +func (suite *TestAnnexJCodecSuite) Request(args bacnetip.Args, kwargs bacnetip.KWArgs) error { + suite.log.Debug().Stringer("Args", args).Stringer("KWArgs", kwargs).Msg("Request") + + return suite.client.Request(args, kwargs) +} + +// Check what the server received. +func (suite *TestAnnexJCodecSuite) Indication(args bacnetip.Args, kwargs bacnetip.KWArgs) error { + suite.log.Debug().Stringer("Args", args).Stringer("KWArgs", kwargs).Msg("Indication") + + var pduType any + if len(args) > 0 { + pduType = args[0].(any) + } + pduAttrs := kwargs + suite.Assert().True(tests.MatchPdu(suite.log, suite.server.GetIndicationReceived(), pduType, pduAttrs)) + return nil +} + +// Check what the server received. +func (suite *TestAnnexJCodecSuite) Response(args bacnetip.Args, kwargs bacnetip.KWArgs) error { + suite.log.Debug().Stringer("Args", args).Stringer("KWArgs", kwargs).Msg("Response") + + return suite.server.Response(args, kwargs) +} + +// Check what the server received. +func (suite *TestAnnexJCodecSuite) Confirmation(args bacnetip.Args, kwargs bacnetip.KWArgs) error { + suite.log.Debug().Stringer("Args", args).Stringer("KWArgs", kwargs).Msg("Confirmation") + + pduType := args[0].(any) + pduAttrs := kwargs + suite.Assert().True(tests.MatchPdu(suite.log, suite.client.GetConfirmationReceived(), pduType, pduAttrs)) + return nil +} + +func (suite *TestAnnexJCodecSuite) TestResult() { + // Request successful + pduBytes, err := bacnetip.Xtob("81.00.0006.0000") + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.BVLCParse(testutils.TestContext(suite.T()), pduBytes) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(Result(0)), 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.Result)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciResultCode, readWriteModel.BVLCResultCode(0))) + + // Request error condition + pduBytes, err = bacnetip.Xtob("81.00.0006.0010") // TODO: check if this is right or if it should be 01 as there is no code for 1 + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.BVLCParse(testutils.TestContext(suite.T()), pduBytes) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(Result(0x0010)), 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.Result)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciResultCode, readWriteModel.BVLCResultCode(0x0010))) +} + +func (suite *TestAnnexJCodecSuite) TestWriteBroadcastDistributionTable() { + suite.T().Skip("something is odd here") // TODO: check what is going on with the output... + // write an empty table + pduBytes, err := bacnetip.Xtob("81.01.0004") + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.BVLCParse(testutils.TestContext(suite.T()), pduBytes) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(WriteBroadcastDistributionTable()), 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.WriteBroadcastDistributionTable)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciBDT, nil)) + + // write table with an element + addr, _ := bacnetip.NewAddress(zerolog.Nop(), "192.168.0.254/24") + pduBytes, err = bacnetip.Xtob("81.01.000e" + + "c0.a8.00.fe.ba.c0 ff.ff.ff.00") // address and mask + suite.Require().NoError(err) + { // Parse with plc4x parser to validate + parse, err := readWriteModel.BVLCParse(testutils.TestContext(suite.T()), pduBytes) + suite.Assert().NoError(err) + if parse != nil { + suite.T().Log("\n" + parse.String()) + } + } + + err = suite.Request(bacnetip.NewArgs(WriteBroadcastDistributionTable(addr)), 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.WriteBroadcastDistributionTable)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciBDT, []*bacnetip.Address{addr})) + +} + +func TestAnnexJCodec(t *testing.T) { + suite.Run(t, new(TestAnnexJCodecSuite)) +}