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/22 18:34:29 UTC

(plc4x) branch develop updated: feat(plc4go/bacnetip): ForwardedNPDU

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 a86fe67ed6 feat(plc4go/bacnetip): ForwardedNPDU
a86fe67ed6 is described below

commit a86fe67ed69b69c2a06819966d825a0bde7b4852
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Aug 22 20:34:21 2024 +0200

    feat(plc4go/bacnetip): ForwardedNPDU
---
 plc4go/internal/bacnetip/CommunicationsModule.go   |   9 +-
 plc4go/internal/bacnetip/PDU.go                    |  16 ++++
 plc4go/internal/bacnetip/bvll.go                   | 103 +++++++++++++++++++--
 plc4go/internal/bacnetip/comp.go                   |  10 +-
 plc4go/internal/bacnetip/tests/state_machine.go    |   6 +-
 .../bacnetip/tests/test_bvll/test_codec_test.go    |  44 +++++++++
 6 files changed, 175 insertions(+), 13 deletions(-)

diff --git a/plc4go/internal/bacnetip/CommunicationsModule.go b/plc4go/internal/bacnetip/CommunicationsModule.go
index 2b13a79352..b3e0c6ba0e 100644
--- a/plc4go/internal/bacnetip/CommunicationsModule.go
+++ b/plc4go/internal/bacnetip/CommunicationsModule.go
@@ -25,6 +25,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/apache/plc4x/plc4go/internal/bacnetip/globals"
 	"github.com/apache/plc4x/plc4go/spi"
 	"github.com/apache/plc4x/plc4go/spi/utils"
 
@@ -151,12 +152,18 @@ func (p *__PCI) GetLengthInBits(ctx context.Context) uint16 {
 
 func (p *__PCI) String() string {
 	pduUserDataString := ""
-	if p.pduUserData != nil {
+	if p.pduUserData != nil && globals.ExtendedPDUOutput {
 		pduUserDataString = p.pduUserData.String()
 		if strings.Contains(pduUserDataString, "\n") {
 			pduUserDataString = "\n" + pduUserDataString + "\n"
 		}
 		pduUserDataString = "pduUserData: " + pduUserDataString + " ,"
+	} else if p.pduUserData != nil {
+		if bytes, err := p.pduUserData.Serialize(); err != nil {
+			pduUserDataString = "pduUserData: " + err.Error() + " ,"
+		} else {
+			pduUserDataString = "pduUserData: " + Btox(bytes, ".") + " ,"
+		}
 	}
 	return fmt.Sprintf("__PCI{%spduSource: %s, pduDestination: %s}", pduUserDataString, p.pduSource, p.pduDestination)
 }
diff --git a/plc4go/internal/bacnetip/PDU.go b/plc4go/internal/bacnetip/PDU.go
index cb3551d3b7..4b1e4eb0c0 100644
--- a/plc4go/internal/bacnetip/PDU.go
+++ b/plc4go/internal/bacnetip/PDU.go
@@ -799,6 +799,22 @@ func uint32ToIpv4(number uint32) net.IP {
 	return ipv4
 }
 
+// PackIpAddr Given an IP address tuple like ('1.2.3.4', 47808) return the six-octet string
+// useful for a BACnet address.
+func PackIpAddr(addrTuple *AddressTuple[string, uint16]) (octetString []byte) {
+	addr, port := addrTuple.Left, addrTuple.Right
+	octetString = append(net.ParseIP(addr).To4(), uint16ToPort(port)...)
+	return
+}
+
+// UnpackIpAddr Given a six-octet BACnet address, return an IP address tuple.
+func UnpackIpAddr(addr []byte) (addrTuple *AddressTuple[string, uint16]) {
+	ip := ipv4ToUint32(addr[:4])
+	port := portToUint16(addr[4:])
+
+	return &AddressTuple[string, uint16]{uint32ToIpv4(ip).String(), port}
+}
+
 func NewLocalStation(localLog zerolog.Logger, addr any, route *Address) (*Address, error) {
 	l := &Address{
 		log: localLog,
diff --git a/plc4go/internal/bacnetip/bvll.go b/plc4go/internal/bacnetip/bvll.go
index b664af2067..bac477bcf6 100644
--- a/plc4go/internal/bacnetip/bvll.go
+++ b/plc4go/internal/bacnetip/bvll.go
@@ -325,6 +325,7 @@ func (w *WriteBroadcastDistributionTable) produceBvlciBDT(entries []readWriteMod
 	}
 	return
 }
+
 func (w *WriteBroadcastDistributionTable) Encode(bvlpdu Arg) error {
 	switch bvlpdu := bvlpdu.(type) {
 	case BVLPDU:
@@ -419,6 +420,7 @@ func (w *ReadBroadcastDistributionTable) String() string {
 
 type ReadBroadcastDistributionTableAck struct {
 	*_BVLPDU
+
 	bvlciBDT []*Address
 }
 
@@ -514,27 +516,108 @@ func (w *ReadBroadcastDistributionTableAck) String() string {
 	return fmt.Sprintf("ReadBroadcastDistributionTableAck{%v, bvlciBDT: %v}", w._BVLPDU, w.bvlciBDT)
 }
 
-// TODO: finish
 type ForwardedNPDU struct {
 	*_BVLPDU
+
+	bvlciAddress *Address
 }
 
 var _ BVLPDU = (*ForwardedNPDU)(nil)
 
-func NewForwardedNPDU() (BVLPDU, error) {
+func NewForwardedNPDU(pdu PDU, opts ...func(*ForwardedNPDU)) (*ForwardedNPDU, error) {
 	b := &ForwardedNPDU{}
-	b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
+	for _, opt := range opts {
+		opt(b)
+	}
+	switch npdu := pdu.(type) {
+	case readWriteModel.NPDUExactly:
+		b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCForwardedNPDU(b.produceInnerNPDU(npdu))).(*_BVLPDU)
+	case nil:
+		b._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")
+		}
+		b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCForwardedNPDU(b.produceInnerNPDU(parse))).(*_BVLPDU)
+	}
 	return b, nil
 }
 
-func (b *ForwardedNPDU) Encode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func WithForwardedNPDUAddress(addr *Address) func(*ForwardedNPDU) {
+	return func(b *ForwardedNPDU) {
+		b.bvlciAddress = addr
+	}
 }
 
-func (b *ForwardedNPDU) Decode(pdu Arg) error {
-	// TODO: finish
-	return nil
+func (w *ForwardedNPDU) GetBvlciAddress() *Address {
+	return w.bvlciAddress
+}
+
+func (w *ForwardedNPDU) produceInnerNPDU(inNpdu readWriteModel.NPDU) (ip []uint8, port uint16, npdu readWriteModel.NPDU, bvlcPayloadLength uint16) {
+	ip = w.bvlciAddress.AddrAddress[:4]
+	port = uint16(47808)
+	if w.bvlciAddress.AddrPort != nil {
+		port = *w.bvlciAddress.AddrPort
+	}
+	npdu = inNpdu
+	return
+}
+
+func (w *ForwardedNPDU) Encode(bvlpdu Arg) error {
+	switch bvlpdu := bvlpdu.(type) {
+	case BVLPDU:
+		if err := bvlpdu.Update(w); err != nil {
+			return errors.Wrap(err, "error updating BVLPDU")
+		}
+
+		// encode the addrress
+		bvlpdu.PutData(w.bvlciAddress.AddrAddress...)
+
+		// encode the rest of the data
+		bvlpdu.PutData(w.GetPduData()...)
+
+		bvlpdu.setBVLC(w.bvlc)
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (w *ForwardedNPDU) 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.BVLCForwardedNPDUExactly:
+			switch bvlc := pduUserData.(type) {
+			case readWriteModel.BVLCForwardedNPDU:
+				addr := bvlc.GetIp()
+				port := bvlc.GetPort()
+				var portArray = make([]byte, 2)
+				binary.BigEndian.PutUint16(portArray, port)
+				var err error
+				address, err := NewAddress(zerolog.Nop(), append(addr, portArray...))
+				if err != nil {
+					return errors.Wrap(err, "error creating address")
+				}
+				w.bvlciAddress = address
+
+				w.setBVLC(bvlc)
+			}
+		}
+		return nil
+	default:
+		return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
+	}
+}
+
+func (w *ForwardedNPDU) String() string {
+	return fmt.Sprintf("ForwardedNPDU{%v, bvlciAddress: %v}", w._BVLPDU, w.bvlciAddress)
 }
 
 // TODO: finish
@@ -844,7 +927,7 @@ func init() {
 			return v
 		},
 		0x04: func() interface{ Decode(Arg) error } {
-			v, _ := NewForwardedNPDU()
+			v, _ := NewForwardedNPDU(nil)
 			return v
 		},
 		0x05: func() interface{ Decode(Arg) error } {
diff --git a/plc4go/internal/bacnetip/comp.go b/plc4go/internal/bacnetip/comp.go
index fd37ac093f..a35d64cc02 100644
--- a/plc4go/internal/bacnetip/comp.go
+++ b/plc4go/internal/bacnetip/comp.go
@@ -64,6 +64,10 @@ func (a Args) Get0MultiplexServer() *_MultiplexServer {
 func (a Args) String() string {
 	r := ""
 	for i, ea := range a {
+		switch tea := ea.(type) {
+		case []byte:
+			ea = Btox(tea, ".")
+		}
 		r += fmt.Sprintf("%d: %v, ", i, ea)
 	}
 	if r != "" {
@@ -93,12 +97,16 @@ func NewKWArgs(kw ...any) KWArgs {
 func (k KWArgs) String() string {
 	r := ""
 	for kk, ea := range k {
+		switch tea := ea.(type) {
+		case []byte:
+			ea = Btox(tea, ".")
+		}
 		r += fmt.Sprintf("%s=%v, ", kk, ea)
 	}
 	if r != "" {
 		r = r[:len(r)-2]
 	}
-	return r
+	return "{" + r + "}"
 }
 
 type KnownKey string
diff --git a/plc4go/internal/bacnetip/tests/state_machine.go b/plc4go/internal/bacnetip/tests/state_machine.go
index 8fe2c04cda..77b5b93d69 100644
--- a/plc4go/internal/bacnetip/tests/state_machine.go
+++ b/plc4go/internal/bacnetip/tests/state_machine.go
@@ -299,7 +299,11 @@ func MatchPdu(localLog zerolog.Logger, pdu bacnetip.PDU, pduType any, pduAttrs m
 				return a.Equals(b)
 			})
 		case bacnetip.KWBvlciAddress:
-			panic("implement me")
+			nni, ok := pdu.(*bacnetip.ForwardedNPDU)
+			if !ok {
+				return false
+			}
+			return nni.GetBvlciAddress().Equals(attrValue)
 		case bacnetip.KWFdAddress:
 			panic("implement me")
 		case bacnetip.KWFdTTL:
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 a937a00ede..e82f94e584 100644
--- a/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go
+++ b/plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go
@@ -63,6 +63,14 @@ func ReadBroadcastDistributionTableAck(bdt ...*bacnetip.Address) *bacnetip.ReadB
 	return readBroadcastDistributionTable
 }
 
+func ForwardNPDU(addr *bacnetip.Address, pduBytes []byte) *bacnetip.ForwardedNPDU {
+	npdu, err := bacnetip.NewForwardedNPDU(bacnetip.NewPDU(&bacnetip.MessageBridge{Bytes: pduBytes}), bacnetip.WithForwardedNPDUAddress(addr))
+	if err != nil {
+		panic(err)
+	}
+	return npdu
+}
+
 type TestAnnexJCodecSuite struct {
 	suite.Suite
 
@@ -279,6 +287,42 @@ func (suite *TestAnnexJCodecSuite) TestReadBroadcastDistributionTableAck() {
 	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ReadBroadcastDistributionTableAck)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciBDT, []*bacnetip.Address{addr}))
 }
 
+func (suite *TestAnnexJCodecSuite) TestForwardNPDU() {
+	// Read an empty TableAck
+	addr, err := bacnetip.NewAddress(zerolog.Nop(), "192.168.0.1")
+	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.04.0013" + //   bvlci // TODO: length was 0e before
+		"c0.a8.00.01.ba.c0" + // original source address
+		// "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(ForwardNPDU(addr, 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.MessageBridge{Bytes: pduBytes})), bacnetip.NoKWArgs)
+	suite.Assert().NoError(err)
+	err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ForwardedNPDU)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciAddress, addr, bacnetip.KWPDUData, xpdu))
+}
+
 func TestAnnexJCodec(t *testing.T) {
 	suite.Run(t, new(TestAnnexJCodecSuite))
 }