Skip to content

Commit a86fe67

Browse files
committed
feat(plc4go/bacnetip): ForwardedNPDU
1 parent c5d0cbd commit a86fe67

File tree

6 files changed

+175
-13
lines changed

6 files changed

+175
-13
lines changed

plc4go/internal/bacnetip/CommunicationsModule.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"strconv"
2626
"strings"
2727

28+
"github.com/apache/plc4x/plc4go/internal/bacnetip/globals"
2829
"github.com/apache/plc4x/plc4go/spi"
2930
"github.com/apache/plc4x/plc4go/spi/utils"
3031

@@ -151,12 +152,18 @@ func (p *__PCI) GetLengthInBits(ctx context.Context) uint16 {
151152

152153
func (p *__PCI) String() string {
153154
pduUserDataString := ""
154-
if p.pduUserData != nil {
155+
if p.pduUserData != nil && globals.ExtendedPDUOutput {
155156
pduUserDataString = p.pduUserData.String()
156157
if strings.Contains(pduUserDataString, "\n") {
157158
pduUserDataString = "\n" + pduUserDataString + "\n"
158159
}
159160
pduUserDataString = "pduUserData: " + pduUserDataString + " ,"
161+
} else if p.pduUserData != nil {
162+
if bytes, err := p.pduUserData.Serialize(); err != nil {
163+
pduUserDataString = "pduUserData: " + err.Error() + " ,"
164+
} else {
165+
pduUserDataString = "pduUserData: " + Btox(bytes, ".") + " ,"
166+
}
160167
}
161168
return fmt.Sprintf("__PCI{%spduSource: %s, pduDestination: %s}", pduUserDataString, p.pduSource, p.pduDestination)
162169
}

plc4go/internal/bacnetip/PDU.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,22 @@ func uint32ToIpv4(number uint32) net.IP {
799799
return ipv4
800800
}
801801

802+
// PackIpAddr Given an IP address tuple like ('1.2.3.4', 47808) return the six-octet string
803+
// useful for a BACnet address.
804+
func PackIpAddr(addrTuple *AddressTuple[string, uint16]) (octetString []byte) {
805+
addr, port := addrTuple.Left, addrTuple.Right
806+
octetString = append(net.ParseIP(addr).To4(), uint16ToPort(port)...)
807+
return
808+
}
809+
810+
// UnpackIpAddr Given a six-octet BACnet address, return an IP address tuple.
811+
func UnpackIpAddr(addr []byte) (addrTuple *AddressTuple[string, uint16]) {
812+
ip := ipv4ToUint32(addr[:4])
813+
port := portToUint16(addr[4:])
814+
815+
return &AddressTuple[string, uint16]{uint32ToIpv4(ip).String(), port}
816+
}
817+
802818
func NewLocalStation(localLog zerolog.Logger, addr any, route *Address) (*Address, error) {
803819
l := &Address{
804820
log: localLog,

plc4go/internal/bacnetip/bvll.go

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ func (w *WriteBroadcastDistributionTable) produceBvlciBDT(entries []readWriteMod
325325
}
326326
return
327327
}
328+
328329
func (w *WriteBroadcastDistributionTable) Encode(bvlpdu Arg) error {
329330
switch bvlpdu := bvlpdu.(type) {
330331
case BVLPDU:
@@ -419,6 +420,7 @@ func (w *ReadBroadcastDistributionTable) String() string {
419420

420421
type ReadBroadcastDistributionTableAck struct {
421422
*_BVLPDU
423+
422424
bvlciBDT []*Address
423425
}
424426

@@ -514,27 +516,108 @@ func (w *ReadBroadcastDistributionTableAck) String() string {
514516
return fmt.Sprintf("ReadBroadcastDistributionTableAck{%v, bvlciBDT: %v}", w._BVLPDU, w.bvlciBDT)
515517
}
516518

517-
// TODO: finish
518519
type ForwardedNPDU struct {
519520
*_BVLPDU
521+
522+
bvlciAddress *Address
520523
}
521524

522525
var _ BVLPDU = (*ForwardedNPDU)(nil)
523526

524-
func NewForwardedNPDU() (BVLPDU, error) {
527+
func NewForwardedNPDU(pdu PDU, opts ...func(*ForwardedNPDU)) (*ForwardedNPDU, error) {
525528
b := &ForwardedNPDU{}
526-
b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
529+
for _, opt := range opts {
530+
opt(b)
531+
}
532+
switch npdu := pdu.(type) {
533+
case readWriteModel.NPDUExactly:
534+
b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCForwardedNPDU(b.produceInnerNPDU(npdu))).(*_BVLPDU)
535+
case nil:
536+
b._BVLPDU = NewBVLPDU(nil).(*_BVLPDU)
537+
default:
538+
// TODO: re-encode seems expensive... check if there is a better option (e.g. only do it on the message bridge)
539+
data := pdu.GetPduData()
540+
parse, err := readWriteModel.NPDUParse(context.Background(), data, uint16(len(data)))
541+
if err != nil {
542+
return nil, errors.Wrap(err, "error re-encoding")
543+
}
544+
b._BVLPDU = NewBVLPDU(readWriteModel.NewBVLCForwardedNPDU(b.produceInnerNPDU(parse))).(*_BVLPDU)
545+
}
527546
return b, nil
528547
}
529548

530-
func (b *ForwardedNPDU) Encode(pdu Arg) error {
531-
// TODO: finish
532-
return nil
549+
func WithForwardedNPDUAddress(addr *Address) func(*ForwardedNPDU) {
550+
return func(b *ForwardedNPDU) {
551+
b.bvlciAddress = addr
552+
}
533553
}
534554

535-
func (b *ForwardedNPDU) Decode(pdu Arg) error {
536-
// TODO: finish
537-
return nil
555+
func (w *ForwardedNPDU) GetBvlciAddress() *Address {
556+
return w.bvlciAddress
557+
}
558+
559+
func (w *ForwardedNPDU) produceInnerNPDU(inNpdu readWriteModel.NPDU) (ip []uint8, port uint16, npdu readWriteModel.NPDU, bvlcPayloadLength uint16) {
560+
ip = w.bvlciAddress.AddrAddress[:4]
561+
port = uint16(47808)
562+
if w.bvlciAddress.AddrPort != nil {
563+
port = *w.bvlciAddress.AddrPort
564+
}
565+
npdu = inNpdu
566+
return
567+
}
568+
569+
func (w *ForwardedNPDU) Encode(bvlpdu Arg) error {
570+
switch bvlpdu := bvlpdu.(type) {
571+
case BVLPDU:
572+
if err := bvlpdu.Update(w); err != nil {
573+
return errors.Wrap(err, "error updating BVLPDU")
574+
}
575+
576+
// encode the addrress
577+
bvlpdu.PutData(w.bvlciAddress.AddrAddress...)
578+
579+
// encode the rest of the data
580+
bvlpdu.PutData(w.GetPduData()...)
581+
582+
bvlpdu.setBVLC(w.bvlc)
583+
return nil
584+
default:
585+
return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
586+
}
587+
}
588+
589+
func (w *ForwardedNPDU) Decode(bvlpdu Arg) error {
590+
switch bvlpdu := bvlpdu.(type) {
591+
case BVLPDU:
592+
if err := w.Update(bvlpdu); err != nil {
593+
return errors.Wrap(err, "error updating BVLPDU")
594+
}
595+
switch pduUserData := bvlpdu.GetPDUUserData().(type) {
596+
case readWriteModel.BVLCForwardedNPDUExactly:
597+
switch bvlc := pduUserData.(type) {
598+
case readWriteModel.BVLCForwardedNPDU:
599+
addr := bvlc.GetIp()
600+
port := bvlc.GetPort()
601+
var portArray = make([]byte, 2)
602+
binary.BigEndian.PutUint16(portArray, port)
603+
var err error
604+
address, err := NewAddress(zerolog.Nop(), append(addr, portArray...))
605+
if err != nil {
606+
return errors.Wrap(err, "error creating address")
607+
}
608+
w.bvlciAddress = address
609+
610+
w.setBVLC(bvlc)
611+
}
612+
}
613+
return nil
614+
default:
615+
return errors.Errorf("invalid BVLPDU type %T", bvlpdu)
616+
}
617+
}
618+
619+
func (w *ForwardedNPDU) String() string {
620+
return fmt.Sprintf("ForwardedNPDU{%v, bvlciAddress: %v}", w._BVLPDU, w.bvlciAddress)
538621
}
539622

540623
// TODO: finish
@@ -844,7 +927,7 @@ func init() {
844927
return v
845928
},
846929
0x04: func() interface{ Decode(Arg) error } {
847-
v, _ := NewForwardedNPDU()
930+
v, _ := NewForwardedNPDU(nil)
848931
return v
849932
},
850933
0x05: func() interface{ Decode(Arg) error } {

plc4go/internal/bacnetip/comp.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func (a Args) Get0MultiplexServer() *_MultiplexServer {
6464
func (a Args) String() string {
6565
r := ""
6666
for i, ea := range a {
67+
switch tea := ea.(type) {
68+
case []byte:
69+
ea = Btox(tea, ".")
70+
}
6771
r += fmt.Sprintf("%d: %v, ", i, ea)
6872
}
6973
if r != "" {
@@ -93,12 +97,16 @@ func NewKWArgs(kw ...any) KWArgs {
9397
func (k KWArgs) String() string {
9498
r := ""
9599
for kk, ea := range k {
100+
switch tea := ea.(type) {
101+
case []byte:
102+
ea = Btox(tea, ".")
103+
}
96104
r += fmt.Sprintf("%s=%v, ", kk, ea)
97105
}
98106
if r != "" {
99107
r = r[:len(r)-2]
100108
}
101-
return r
109+
return "{" + r + "}"
102110
}
103111

104112
type KnownKey string

plc4go/internal/bacnetip/tests/state_machine.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,11 @@ func MatchPdu(localLog zerolog.Logger, pdu bacnetip.PDU, pduType any, pduAttrs m
299299
return a.Equals(b)
300300
})
301301
case bacnetip.KWBvlciAddress:
302-
panic("implement me")
302+
nni, ok := pdu.(*bacnetip.ForwardedNPDU)
303+
if !ok {
304+
return false
305+
}
306+
return nni.GetBvlciAddress().Equals(attrValue)
303307
case bacnetip.KWFdAddress:
304308
panic("implement me")
305309
case bacnetip.KWFdTTL:

plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ func ReadBroadcastDistributionTableAck(bdt ...*bacnetip.Address) *bacnetip.ReadB
6363
return readBroadcastDistributionTable
6464
}
6565

66+
func ForwardNPDU(addr *bacnetip.Address, pduBytes []byte) *bacnetip.ForwardedNPDU {
67+
npdu, err := bacnetip.NewForwardedNPDU(bacnetip.NewPDU(&bacnetip.MessageBridge{Bytes: pduBytes}), bacnetip.WithForwardedNPDUAddress(addr))
68+
if err != nil {
69+
panic(err)
70+
}
71+
return npdu
72+
}
73+
6674
type TestAnnexJCodecSuite struct {
6775
suite.Suite
6876

@@ -279,6 +287,42 @@ func (suite *TestAnnexJCodecSuite) TestReadBroadcastDistributionTableAck() {
279287
err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ReadBroadcastDistributionTableAck)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciBDT, []*bacnetip.Address{addr}))
280288
}
281289

290+
func (suite *TestAnnexJCodecSuite) TestForwardNPDU() {
291+
// Read an empty TableAck
292+
addr, err := bacnetip.NewAddress(zerolog.Nop(), "192.168.0.1")
293+
xpdu, err := bacnetip.Xtob(
294+
// "deadbeef", // forwarded PDU // TODO: this is not a ndpu so we just exploded with that. We use the iartn for that for now
295+
// TODO: this below is from us as upstream message is not parsable
296+
"01.80" + // version, network layer message
297+
"01 0001 0002 0003", // message type and network list
298+
)
299+
suite.Require().NoError(err)
300+
pduBytes, err := bacnetip.Xtob("81.04.0013" + // bvlci // TODO: length was 0e before
301+
"c0.a8.00.01.ba.c0" + // original source address
302+
// "deadbeef", // forwarded PDU // TODO: this is not a ndpu so we just exploded with that. We use the iartn for that for now
303+
// TODO: this below is from us as upstream message is not parsable
304+
"01.80" + // version, network layer message
305+
"01 0001 0002 0003", // message type and network list
306+
)
307+
suite.Require().NoError(err)
308+
{ // Parse with plc4x parser to validate
309+
parse, err := readWriteModel.BVLCParse(testutils.TestContext(suite.T()), pduBytes)
310+
suite.Assert().NoError(err)
311+
if parse != nil {
312+
suite.T().Log("\n" + parse.String())
313+
}
314+
}
315+
316+
err = suite.Request(bacnetip.NewArgs(ForwardNPDU(addr, xpdu)), bacnetip.NoKWArgs)
317+
suite.Assert().NoError(err)
318+
err = suite.Indication(bacnetip.NoArgs, bacnetip.NewKWArgs(bacnetip.KWPDUData, pduBytes))
319+
suite.Assert().NoError(err)
320+
321+
err = suite.Response(bacnetip.NewArgs(bacnetip.NewPDU(&bacnetip.MessageBridge{Bytes: pduBytes})), bacnetip.NoKWArgs)
322+
suite.Assert().NoError(err)
323+
err = suite.Confirmation(bacnetip.NewArgs((*bacnetip.ForwardedNPDU)(nil)), bacnetip.NewKWArgs(bacnetip.KWBvlciAddress, addr, bacnetip.KWPDUData, xpdu))
324+
}
325+
282326
func TestAnnexJCodec(t *testing.T) {
283327
suite.Run(t, new(TestAnnexJCodecSuite))
284328
}

0 commit comments

Comments
 (0)