You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2024/08/23 09:50:12 UTC

(plc4x) branch develop updated: fix(plc4go/bacnetip): added more BVLCs

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


The following commit(s) were added to refs/heads/develop by this push:
     new cd13df2cbd fix(plc4go/bacnetip): added more BVLCs
cd13df2cbd is described below

commit cd13df2cbdc7a29c49b7914c796f336ff198f9b6
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Fri Aug 23 11:50:03 2024 +0200

    fix(plc4go/bacnetip): added more BVLCs
---
 plc4go/internal/bacnetip/bvll.go                   | 390 ++++++++++++++++++---
 plc4go/internal/bacnetip/tests/state_machine.go    |  13 +-
 .../bacnetip/tests/test_bvll/test_codec_test.go    | 204 +++++++++--
 3 files changed, 524 insertions(+), 83 deletions(-)

diff --git a/plc4go/internal/bacnetip/bvll.go b/plc4go/internal/bacnetip/bvll.go
index fe713eeb82..bfcc69361e 100644
--- a/plc4go/internal/bacnetip/bvll.go
+++ b/plc4go/internal/bacnetip/bvll.go
@@ -617,119 +617,409 @@ func (w *ForwardedNPDU) String() string {
 	return fmt.Sprintf("ForwardedNPDU{%v, bvlciAddress: %v}", w._BVLPDU, w.bvlciAddress)
 }
 
-// TODO: finish
+type FDTEntry struct {
+	FDAddress *Address
+	FDTTL     uint16
+	FDRemain  uint16
+}
+
+func (f FDTEntry) Equals(other any) bool {
+	if f == other {
+		return true
+	}
+	otherEntry, ok := other.(FDTEntry)
+	if !ok {
+		return false
+	}
+	return f.FDAddress.Equals(otherEntry.FDAddress) && f.FDTTL == otherEntry.FDTTL && f.FDRemain == otherEntry.FDRemain
+}
+
 type RegisterForeignDevice struct {
 	*_BVLPDU
+
+	bvlciTimeToLive uint16
 }
 
 var _ BVLPDU = (*RegisterForeignDevice)(nil)
 
-func NewRegisterForeignDevice() (BVLPDU, error) {
+func NewRegisterForeignDevice(opts ...func(RegisterForeignDevice *RegisterForeignDevice)) (*RegisterForeignDevice, error) {
 	b := &RegisterForeignDevice{}
-	b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
+	for _, opt := range opts {
+		opt(b)
+	}
+	b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCRegisterForeignDevice(b.bvlciTimeToLive)).(*_BVLPDU)
 	return b, nil
 }
 
-func (b *RegisterForeignDevice) Encode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func WithRegisterForeignDeviceBvlciTimeToLive(ttl uint16) func(*RegisterForeignDevice) {
+	return func(b *RegisterForeignDevice) {
+		b.bvlciTimeToLive = ttl
+	}
 }
 
-func (b *RegisterForeignDevice) Decode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func (n *RegisterForeignDevice) GetBvlciTimeToLive() uint16 {
+	return n.bvlciTimeToLive
+}
+
+func (n *RegisterForeignDevice) Encode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := bvlpdu.Update(n); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+		bvlpdu.PutShort(n.bvlciTimeToLive)
+		bvlpdu.setBVLC(n.bvlc)
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (n *RegisterForeignDevice) Decode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := n.Update(bvlpdu); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+		switch pduUserData := bvlpdu.GetPDUUserData().(type) {
+		case readWriteModel.BVLCRegisterForeignDeviceExactly:
+			switch bvlc := pduUserData.(type) {
+			case readWriteModel.BVLCRegisterForeignDevice:
+				n.setBVLC(bvlc)
+				n.bvlciTimeToLive = bvlc.GetTtl()
+			}
+		}
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (n *RegisterForeignDevice) String() string {
+	return fmt.Sprintf("RegisterForeignDevice{%v, bvlciTimeToLive: %v}", n._BVLPDU, n.bvlciTimeToLive)
 }
 
-// TODO: finish
 type ReadForeignDeviceTable struct {
 	*_BVLPDU
 }
 
 var _ BVLPDU = (*ReadForeignDeviceTable)(nil)
 
-func NewReadForeignDeviceTable() (BVLPDU, error) {
+func NewReadForeignDeviceTable(opts ...func(*ReadForeignDeviceTable)) (*ReadForeignDeviceTable, error) {
 	b := &ReadForeignDeviceTable{}
-	b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
+	for _, opt := range opts {
+		opt(b)
+	}
+	b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCReadForeignDeviceTable()).(*_BVLPDU)
 	return b, nil
 }
 
-func (b *ReadForeignDeviceTable) Encode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func (w *ReadForeignDeviceTable) Encode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := bvlpdu.Update(w); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+		bvlpdu.setBVLC(w.bvlc)
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
 }
 
-func (b *ReadForeignDeviceTable) Decode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func (w *ReadForeignDeviceTable) 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.BVLCReadForeignDeviceTableExactly:
+			switch bvlc := pduUserData.(type) {
+			case readWriteModel.BVLCReadForeignDeviceTable:
+				w.setBVLC(bvlc)
+			}
+		}
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (w *ReadForeignDeviceTable) String() string {
+	return fmt.Sprintf("ReadForeignDeviceTable{%v}", w._BVLPDU)
 }
 
-// TODO: finish
 type ReadForeignDeviceTableAck struct {
 	*_BVLPDU
+
+	bvlciFDT []FDTEntry
 }
 
 var _ BVLPDU = (*ReadForeignDeviceTableAck)(nil)
 
-func NewReadForeignDeviceTableAck() (BVLPDU, error) {
+func NewReadForeignDeviceTableAck(opts ...func(*ReadForeignDeviceTableAck)) (*ReadForeignDeviceTableAck, error) {
 	b := &ReadForeignDeviceTableAck{}
-	b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
+	for _, opt := range opts {
+		opt(b)
+	}
+	b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCReadForeignDeviceTableAck(b.produceForeignDeviceTable(), 0)).(*_BVLPDU)
 	return b, nil
 }
 
-func (b *ReadForeignDeviceTableAck) Encode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func WithReadForeignDeviceTableAckFDT(fdts ...FDTEntry) func(*ReadForeignDeviceTableAck) {
+	return func(b *ReadForeignDeviceTableAck) {
+		b.bvlciFDT = fdts
+	}
 }
 
-func (b *ReadForeignDeviceTableAck) Decode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func (w *ReadForeignDeviceTableAck) GetBvlciFDT() []FDTEntry {
+	return w.bvlciFDT
+}
+
+func (w *ReadForeignDeviceTableAck) produceForeignDeviceTable() (entries []readWriteModel.BVLCForeignDeviceTableEntry) {
+	for _, entry := range w.bvlciFDT {
+		address := entry.FDAddress
+		addr := address.AddrAddress[:4]
+		port := uint16(47808)
+		if address.AddrPort != nil {
+			port = *address.AddrPort
+		}
+		entries = append(entries, readWriteModel.NewBVLCForeignDeviceTableEntry(addr, port, entry.FDTTL, entry.FDRemain))
+	}
+	return
+}
+
+func (w *ReadForeignDeviceTableAck) produceBvlciFDT(entries []readWriteModel.BVLCForeignDeviceTableEntry) (bvlciFDT []FDTEntry) {
+	for _, entry := range entries {
+		addr := entry.GetIp()
+		port := entry.GetPort()
+		var portArray = make([]byte, 2)
+		binary.BigEndian.PutUint16(portArray, port)
+		address, _ := NewAddress(zerolog.Nop(), append(addr, portArray...))
+		bvlciFDT = append(bvlciFDT, FDTEntry{
+			FDAddress: address,
+			FDTTL:     entry.GetTtl(),
+			FDRemain:  entry.GetSecondRemainingBeforePurge(),
+		})
+	}
+	return
+}
+
+func (w *ReadForeignDeviceTableAck) 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 _, fdte := range w.bvlciFDT {
+			bvlpdu.PutData(fdte.FDAddress.AddrAddress...)
+			bvlpdu.PutShort(fdte.FDTTL)
+			bvlpdu.PutShort(fdte.FDRemain)
+		}
+		bvlpdu.setBVLC(w.bvlc)
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (w *ReadForeignDeviceTableAck) 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.BVLCReadForeignDeviceTableAckExactly:
+			switch bvlc := pduUserData.(type) {
+			case readWriteModel.BVLCReadForeignDeviceTableAck:
+				w.setBVLC(bvlc)
+				w.bvlciFDT = w.produceBvlciFDT(bvlc.GetTable())
+			}
+		}
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (w *ReadForeignDeviceTableAck) String() string {
+	return fmt.Sprintf("ReadForeignDeviceTableAck{%v, bvlciFDT: %v}", w._BVLPDU, w.bvlciFDT)
 }
 
-// TODO: finish
 type DeleteForeignDeviceTableEntry struct {
 	*_BVLPDU
+
+	bvlciAddress *Address
 }
 
 var _ BVLPDU = (*DeleteForeignDeviceTableEntry)(nil)
 
-func NewDeleteForeignDeviceTableEntry() (BVLPDU, error) {
-	b := &DeleteForeignDeviceTableEntry{}
-	b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
-	return b, nil
+func NewDeleteForeignDeviceTableEntry(opts ...func(*DeleteForeignDeviceTableEntry)) (*DeleteForeignDeviceTableEntry, error) {
+	d := &DeleteForeignDeviceTableEntry{}
+	for _, opt := range opts {
+		opt(d)
+	}
+	d._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCDeleteForeignDeviceTableEntry(d.buildIPArgs())).(*_BVLPDU)
+	return d, nil
 }
 
-func (b *DeleteForeignDeviceTableEntry) Encode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func WithDeleteForeignDeviceTableEntryAddress(address *Address) func(*DeleteForeignDeviceTableEntry) {
+	return func(d *DeleteForeignDeviceTableEntry) {
+		d.bvlciAddress = address
+	}
 }
 
-func (b *DeleteForeignDeviceTableEntry) Decode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func (d *DeleteForeignDeviceTableEntry) buildIPArgs() (ip []uint8, port uint16) {
+	if d.bvlciAddress == nil {
+		return
+	}
+	ip = d.bvlciAddress.AddrAddress[:4]
+	port = *d.bvlciAddress.AddrPort
+	return
+}
+
+func (d *DeleteForeignDeviceTableEntry) buildAddress(ip []uint8, port uint16) *Address {
+	var portArray = make([]byte, 2)
+	binary.BigEndian.PutUint16(portArray, port)
+	address, _ := NewAddress(zerolog.Nop(), append(ip, portArray...))
+	return address
+}
+
+func (d *DeleteForeignDeviceTableEntry) GetBvlciAddress() *Address {
+	return d.bvlciAddress
+}
+
+func (d *DeleteForeignDeviceTableEntry) Encode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := bvlpdu.Update(d); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+		bvlpdu.PutData(d.bvlciAddress.AddrAddress...)
+		bvlpdu.setBVLC(d.bvlc)
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (d *DeleteForeignDeviceTableEntry) Decode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := d.Update(bvlpdu); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+		switch pduUserData := bvlpdu.GetPDUUserData().(type) {
+		case readWriteModel.BVLCDeleteForeignDeviceTableEntryExactly:
+			switch bvlc := pduUserData.(type) {
+			case readWriteModel.BVLCDeleteForeignDeviceTableEntry:
+				d.bvlciAddress = d.buildAddress(bvlc.GetIp(), bvlc.GetPort())
+				d.setBVLC(bvlc)
+			}
+		}
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (d *DeleteForeignDeviceTableEntry) String() string {
+	return fmt.Sprintf("DeleteForeignDeviceTableEntry{%v}", d._BVLPDU)
 }
 
-// TODO: finish
 type DistributeBroadcastToNetwork struct {
 	*_BVLPDU
+
+	// post construct function
+	_postConstruct []func()
 }
 
 var _ BVLPDU = (*DistributeBroadcastToNetwork)(nil)
 
-func NewDistributeBroadcastToNetwork() (BVLPDU, error) {
-	b := &DistributeBroadcastToNetwork{}
-	b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
-	return b, nil
+func NewDistributeBroadcastToNetwork(pdu PDU, opts ...func(*DistributeBroadcastToNetwork)) (*DistributeBroadcastToNetwork, error) {
+	o := &DistributeBroadcastToNetwork{}
+	for _, opt := range opts {
+		opt(o)
+	}
+	switch npdu := pdu.(type) {
+	case readWriteModel.NPDUExactly:
+		o._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCDistributeBroadcastToNetwork(o.produceInnerNPDU(npdu))).(*_BVLPDU)
+	case nil:
+		o._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
+	default:
+		// TODO: re-encode seems expensive... check if there is a better option (e.g. only do it on the message bridge)
+		data := pdu.GetPduData()
+		parse, err := readWriteModel.NPDUParse(context.Background(), data, uint16(len(data)))
+		if err != nil {
+			return nil, errors.Wrap(err, "error re-encoding")
+		}
+		o._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCDistributeBroadcastToNetwork(o.produceInnerNPDU(parse))).(*_BVLPDU)
+	}
+	// Do a post construct for a bit more easy initialization
+	for _, f := range o._postConstruct {
+		f()
+	}
+	o._postConstruct = nil
+	return o, nil
 }
 
-func (b *DistributeBroadcastToNetwork) Encode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func WithDistributeBroadcastToNetworkDestination(destination *Address) func(*DistributeBroadcastToNetwork) {
+	return func(o *DistributeBroadcastToNetwork) {
+		o._postConstruct = append(o._postConstruct, func() {
+			o.SetPDUDestination(destination)
+		})
+	}
 }
 
-func (b *DistributeBroadcastToNetwork) Decode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func WithDistributeBroadcastToNetworkUserData(userData spi.Message) func(*DistributeBroadcastToNetwork) {
+	return func(o *DistributeBroadcastToNetwork) {
+		o._postConstruct = append(o._postConstruct, func() {
+			o.SetPDUUserData(userData)
+		})
+	}
+}
+
+func (o *DistributeBroadcastToNetwork) produceInnerNPDU(inNpdu readWriteModel.NPDU) (npdu readWriteModel.NPDU, bvlcPayloadLength uint16) {
+	npdu = inNpdu
+	return
+}
+
+func (o *DistributeBroadcastToNetwork) Encode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := bvlpdu.Update(o); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+
+		bvlpdu.PutData(o.getPDUData()...)
+
+		bvlpdu.setBVLC(o.bvlc)
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (o *DistributeBroadcastToNetwork) Decode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := o.Update(bvlpdu); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+		switch pduUserData := bvlpdu.GetPDUUserData().(type) {
+		case readWriteModel.BVLCDistributeBroadcastToNetworkExactly:
+			o.setBVLC(pduUserData)
+		}
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (o *DistributeBroadcastToNetwork) String() string {
+	return fmt.Sprintf("DistributeBroadcastToNetwork{%s}", o._BVLPDU)
 }
 
 type OriginalUnicastNPDU struct {
@@ -950,7 +1240,7 @@ func init() {
 			return v
 		},
 		0x09: func() interface{ Decode(Arg) error } {
-			v, _ := NewDistributeBroadcastToNetwork()
+			v, _ := NewDistributeBroadcastToNetwork(nil)
 			return v
 		},
 		0x0A: func() interface{ Decode(Arg) error } {
diff --git a/plc4go/internal/bacnetip/tests/state_machine.go b/plc4go/internal/bacnetip/tests/state_machine.go
index 152f5ec8eb..a73f75f6da 100644
--- a/plc4go/internal/bacnetip/tests/state_machine.go
+++ b/plc4go/internal/bacnetip/tests/state_machine.go
@@ -403,12 +403,17 @@ func MatchPdu(localLog zerolog.Logger, pdu bacnetip.PDU, pduType any, pduAttrs m
 				return false
 			}
 		case bacnetip.KWBvlciAddress:
-			nni, ok := pdu.(*bacnetip.ForwardedNPDU)
-			if !ok {
-				attrLog.Trace().Msg("doesn't match")
+			var address *bacnetip.Address
+			switch pdu := pdu.(type) {
+			case *bacnetip.ForwardedNPDU:
+				address = pdu.GetBvlciAddress()
+			case *bacnetip.DeleteForeignDeviceTableEntry:
+				address = pdu.GetBvlciAddress()
+			default:
+				attrLog.Trace().Type("type", pdu).Msg("doesn't match")
 				return false
 			}
-			equals := nni.GetBvlciAddress().Equals(attrValue)
+			equals := address.Equals(attrValue)
 			if !equals {
 				attrLog.Trace().Msg("doesn't match")
 				return false
diff --git a/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go b/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go
index d2b1f8efe8..f4c3123595 100644
--- a/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go
+++ b/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go
@@ -71,24 +71,48 @@ func ForwardedNPDU(addr *bacnetip.Address, pduBytes []byte) *bacnetip.ForwardedN
 	return npdu
 }
 
-func RegisterForeignDevice() any {
-	panic("implement me")
+func RegisterForeignDevice(ttl uint16) *bacnetip.RegisterForeignDevice {
+	registerForeignDevice, err := bacnetip.NewRegisterForeignDevice(bacnetip.WithRegisterForeignDeviceBvlciTimeToLive(ttl))
+	if err != nil {
+		panic(err)
+	}
+	return registerForeignDevice
 }
 
-func ReadForeignDeviceTable() any {
-	panic("implement me")
+func ReadForeignDeviceTable() *bacnetip.ReadForeignDeviceTable {
+	readForeignDeviceTable, err := bacnetip.NewReadForeignDeviceTable()
+	if err != nil {
+		panic(err)
+	}
+	return readForeignDeviceTable
 }
 
-func ReadForeignDeviceTableAck() any {
-	panic("implement me")
+func FDTEntry() (entry bacnetip.FDTEntry) {
+	return
 }
 
-func DeleteForeignDeviceTableEntry() any {
-	panic("implement me")
+func ReadForeignDeviceTableAck(fdts ...bacnetip.FDTEntry) *bacnetip.ReadForeignDeviceTableAck {
+	readForeignDeviceTableAck, err := bacnetip.NewReadForeignDeviceTableAck(bacnetip.WithReadForeignDeviceTableAckFDT(fdts...))
+	if err != nil {
+		panic(err)
+	}
+	return readForeignDeviceTableAck
 }
 
-func DistributeBroadcastToNetwork() any {
-	panic("implement me")
+func DeleteForeignDeviceTableEntry(address *bacnetip.Address) *bacnetip.DeleteForeignDeviceTableEntry {
+	deleteForeignDeviceTableEntry, err := bacnetip.NewDeleteForeignDeviceTableEntry(bacnetip.WithDeleteForeignDeviceTableEntryAddress(address))
+	if err != nil {
+		panic(err)
+	}
+	return deleteForeignDeviceTableEntry
+}
+
+func DistributeBroadcastToNetwork(pduBytes []byte) any {
+	distributeBroadcastToNetwork, err := bacnetip.NewDistributeBroadcastToNetwork(bacnetip.NewPDU(bacnetip.NewMessageBridge(pduBytes...)))
+	if err != nil {
+		panic(err)
+	}
+	return distributeBroadcastToNetwork
 }
 
 func OriginalUnicastNPDU(pduBytes []byte) *bacnetip.OriginalUnicastNPDU {
@@ -168,14 +192,7 @@ func (suite *TestAnnexJCodecSuite) Confirmation(args bacnetip.Args, kwargs bacne
 
 	pduType := args[0].(any)
 	pduAttrs := kwargs
-	{ // TODO temporary log to hunt down array mutation
-		suite.log.Error().Msg("going in wtf.....")
-	}
-	pduMatch := tests.MatchPdu(suite.log, suite.client.GetConfirmationReceived(), pduType, pduAttrs)
-	{ // TODO temporary log to hunt down array mutation
-		suite.log.Error().Bool("pduMatch", pduMatch).Msg("wtf.....")
-	}
-	suite.Assert().True(pduMatch)
+	suite.Assert().True(tests.MatchPdu(suite.log, suite.client.GetConfirmationReceived(), pduType, pduAttrs))
 	return nil
 }
 
@@ -368,33 +385,162 @@ func (suite *TestAnnexJCodecSuite) TestForwardNPDU() {
 }
 
 func (suite *TestAnnexJCodecSuite) TestRegisterForeignDevice() {
-	// TODO: implement me
-	suite.T().Fail()
+	// Request successful
+	pduBytes, err := bacnetip.Xtob(
+		"81.05.0006" + // bvlci
+			"001e", //time-to-live
+	)
+	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(RegisterForeignDevice(30)), 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.NewMessageBridge(pduBytes...))), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.RegisterForeignDevice)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciTimeToLive, uint16(30)))
 }
 
 func (suite *TestAnnexJCodecSuite) TestReadForeignDeviceTable() {
-	// TODO: implement me
-	suite.T().Fail()
+	// Read an empty table
+	pduBytes, err := bacnetip.Xtob("81.06.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(ReadForeignDeviceTable()), 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.NewMessageBridge(pduBytes...))), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ReadForeignDeviceTable)(nil)), bacnetip.NoKWArgs)
 }
 
 func (suite *TestAnnexJCodecSuite) TestReadForeignDeviceTableAck() {
-	// TODO: implement me
-	suite.T().Fail()
+	// Read an empty TableAck
+	pduBytes, err := bacnetip.Xtob("81.07.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(ReadForeignDeviceTableAck()), 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.NewMessageBridge(pduBytes...))), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ReadForeignDeviceTableAck)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciFDT, []bacnetip.FDTEntry{}))
+
+	// Read TableAck with one entry
+	fdte := FDTEntry()
+	fdte.FDAddress, err = bacnetip.NewAddress(suite.log, "192.168.0.10")
+	suite.Require().NoError(err)
+	fdte.FDTTL = 30
+	fdte.FDRemain = 15
+	pduBytes, err = bacnetip.Xtob(
+		"81.07.000e" + //bvlci
+			"c0.a8.00.0a.ba.c0" + // address
+			"001e.000f", // ttl and remaining
+	)
+	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(ReadForeignDeviceTableAck(fdte)), 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.NewMessageBridge(pduBytes...))), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ReadForeignDeviceTableAck)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciFDT, []bacnetip.FDTEntry{fdte}))
 }
 
 func (suite *TestAnnexJCodecSuite) TestDeleteForeignDeviceTableEntry() {
-	// TODO: implement me
-	suite.T().Fail()
+	addr, _ := bacnetip.NewAddress(zerolog.Nop(), "192.168.0.11/24")
+	pduBytes, err := bacnetip.Xtob("81.08.000a" + // bvlci
+		"c0.a8.00.0b.ba.c0") // address of entry to be deleted
+	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(DeleteForeignDeviceTableEntry(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.NewMessageBridge(pduBytes...))), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.DeleteForeignDeviceTableEntry)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciAddress, addr))
 }
 
 func (suite *TestAnnexJCodecSuite) TestDeleteForeignDeviceTableAck() {
 	// TODO: implement me
-	suite.T().Fail()
+	suite.T().Skip()
 }
 
 func (suite *TestAnnexJCodecSuite) TestDistributeBroadcastToNetwork() {
-	// TODO: implement me
-	suite.T().Fail()
+	xpdu, err := bacnetip.Xtob(
+		// "deadbeef", // forwarded PDU // TODO: this is not a ndpu so we just exploded with that. We use the iartn for that for now
+		// TODO: this below is from us as upstream message is not parsable
+		"01.80" + // version, network layer message
+			"01 0001 0002 0003", // message type and network list
+	)
+	suite.Require().NoError(err)
+	pduBytes, err := bacnetip.Xtob("81.0a.000d" + //   bvlci // TODO: length was 08 before
+		// "deadbeef", // forwarded PDU // TODO: this is not a ndpu so we just exploded with that. We use the iartn for that for now
+		// TODO: this below is from us as upstream message is not parsable
+		"01.80" + // version, network layer message
+		"01 0001 0002 0003", // message type and network list
+	)
+	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(DistributeBroadcastToNetwork(xpdu)), 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.NewMessageBridge(pduBytes...))), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.DistributeBroadcastToNetwork)(nil)), bacnetip.NewKWArgs(bacnetip.KWPDUData, xpdu))
 }
 
 func (suite *TestAnnexJCodecSuite) TestOriginalUnicastNPDU() {