Skip to content

Commit

Permalink
feat(plc4go/bacnetip): ForwardedNPDU
Browse files Browse the repository at this point in the history
  • Loading branch information
sruehl committed Aug 22, 2024
1 parent c5d0cbd commit a86fe67
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 13 deletions.
9 changes: 8 additions & 1 deletion plc4go/internal/bacnetip/CommunicationsModule.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)
}
Expand Down
16 changes: 16 additions & 0 deletions plc4go/internal/bacnetip/PDU.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
103 changes: 93 additions & 10 deletions plc4go/internal/bacnetip/bvll.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ func (w *WriteBroadcastDistributionTable) produceBvlciBDT(entries []readWriteMod
}
return
}

func (w *WriteBroadcastDistributionTable) Encode(bvlpdu Arg) error {
switch bvlpdu := bvlpdu.(type) {
case BVLPDU:
Expand Down Expand Up @@ -419,6 +420,7 @@ func (w *ReadBroadcastDistributionTable) String() string {

type ReadBroadcastDistributionTableAck struct {
*_BVLPDU

bvlciBDT []*Address
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 } {
Expand Down
10 changes: 9 additions & 1 deletion plc4go/internal/bacnetip/comp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion plc4go/internal/bacnetip/tests/state_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
44 changes: 44 additions & 0 deletions plc4go/internal/bacnetip/tests/test_bvll/test_codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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))
}

0 comments on commit a86fe67

Please sign in to comment.