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/19 19:16:19 UTC
(plc4x) 07/07: feat(plc4go/bacnet): add primitive tag and taglist
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
commit 8ecdf2bac4ae3c69e763190d74c8ba2c4688eb0a
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:14:26 2024 +0200
feat(plc4go/bacnet): add primitive tag and taglist
---
plc4go/internal/bacnetip/PDU.go | 159 ++++++-
plc4go/internal/bacnetip/comp.go | 39 +-
plc4go/internal/bacnetip/primitivedata.go | 419 ++++++++++++++++-
.../tests/test_primitive_data/test_tag_test.go | 518 ++++++++++++++++++++-
4 files changed, 1112 insertions(+), 23 deletions(-)
diff --git a/plc4go/internal/bacnetip/PDU.go b/plc4go/internal/bacnetip/PDU.go
index 301fa79137..2b3a933e3e 100644
--- a/plc4go/internal/bacnetip/PDU.go
+++ b/plc4go/internal/bacnetip/PDU.go
@@ -20,17 +20,22 @@
package bacnetip
import (
+ "context"
"encoding/binary"
"fmt"
- readWriteModel "github.com/apache/plc4x/plc4go/protocols/bacnetip/readwrite/model"
- "github.com/apache/plc4x/plc4go/spi"
- "github.com/pkg/errors"
- "github.com/rs/zerolog"
+ "io"
"net"
"net/netip"
"regexp"
"strconv"
"strings"
+
+ readWriteModel "github.com/apache/plc4x/plc4go/protocols/bacnetip/readwrite/model"
+ "github.com/apache/plc4x/plc4go/spi"
+ "github.com/apache/plc4x/plc4go/spi/utils"
+
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog"
)
var _shortMask = 0xFFFF
@@ -873,6 +878,126 @@ func (p *_PCI) String() string {
return fmt.Sprintf("_PCI{%s, expectingReply: %t, networkPriority: %s}", p.__PCI, p.expectingReply, p.networkPriority)
}
+type PDUData interface {
+ GetPduData() []byte
+ Get() (byte, error)
+ GetShort() (int16, error)
+ GetLong() (int64, error)
+ GetData(dlen int) ([]byte, error)
+ Put(byte)
+ PutData(...byte)
+ PutShort(int16)
+ PutLong(int64)
+}
+
+type _PDUDataRequirements interface {
+ getPDUData() []byte
+}
+
+// _PDUData is basically a bridge to spi.Message
+type _PDUData struct {
+ _PDUDataRequirements
+
+ cachedData []byte
+ done bool
+}
+
+var _ PDUData = (*_PDUData)(nil)
+
+func newPDUData(requirements _PDUDataRequirements) *_PDUData {
+ return &_PDUData{
+ _PDUDataRequirements: requirements,
+ }
+}
+
+func NewPDUData(args Args) PDUData {
+ return newPDUData(args[0].(_PDUDataRequirements))
+}
+
+func (d *_PDUData) GetPduData() []byte {
+ d.checkCache()
+ return d.cachedData
+}
+
+func (d *_PDUData) Get() (byte, error) {
+ d.checkCache()
+ if d.cachedData == nil {
+ return 0, io.EOF
+ }
+ octet := d.cachedData[0]
+ d.cachedData = d.cachedData[1:]
+ if d.cachedData == nil {
+ d.done = true
+ }
+ return octet, nil
+}
+
+func (d *_PDUData) GetData(dlen int) ([]byte, error) {
+ d.checkCache()
+ if len(d.cachedData) < dlen {
+ return nil, io.EOF
+ }
+ data := d.cachedData[:dlen]
+ d.cachedData = d.cachedData[dlen:]
+ if d.cachedData == nil {
+ d.done = true
+ }
+ return data, nil
+}
+
+func (d *_PDUData) GetShort() (int16, error) {
+ d.checkCache()
+ data, err := d.GetData(2)
+ if err != nil {
+ return 0, err
+ }
+ return int16(binary.BigEndian.Uint16(data)), nil
+}
+
+func (d *_PDUData) GetLong() (int64, error) {
+ d.checkCache()
+ data, err := d.GetData(4)
+ if err != nil {
+ return 0, err
+ }
+ return int64(binary.BigEndian.Uint64(data)), nil
+}
+
+func (d *_PDUData) Put(n byte) {
+ d.checkCache()
+ d.cachedData = append(d.cachedData, n)
+}
+
+func (d *_PDUData) PutData(n ...byte) {
+ d.checkCache()
+ d.cachedData = append(d.cachedData, n...)
+}
+
+func (d *_PDUData) PutShort(n int16) {
+ d.checkCache()
+ ba := make([]byte, 2)
+ binary.BigEndian.PutUint16(ba, uint16(n))
+ d.cachedData = append(d.cachedData, ba...)
+}
+
+func (d *_PDUData) PutLong(n int64) {
+ d.checkCache()
+ ba := make([]byte, 4)
+ binary.BigEndian.PutUint64(ba, uint64(n))
+ d.cachedData = append(d.cachedData, ba...)
+}
+
+func (d *_PDUData) checkCache() {
+ if !d.done && d.cachedData == nil {
+ d.cachedData = d.getPDUData()
+ }
+}
+
+func (d *_PDUData) deepCopy() *_PDUData {
+ copyPDUData := *d
+ return ©PDUData
+}
+
type PDU interface {
fmt.Stringer
GetMessage() spi.Message
@@ -887,43 +1012,49 @@ type PDU interface {
type _PDU struct {
*_PCI
+ *_PDUData
}
func NewPDU(msg spi.Message, pduOptions ...PDUOption) PDU {
p := &_PDU{
- newPCI(msg, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE),
+ _PCI: newPCI(msg, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE),
}
for _, option := range pduOptions {
option(p)
}
+ p._PDUData = newPDUData(p)
return p
}
func NewPDUFromPDU(pdu PDU, pduOptions ...PDUOption) PDU {
msg := pdu.(*_PDU).pduUserData
p := &_PDU{
- newPCI(msg, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()),
+ _PCI: newPCI(msg, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()),
}
for _, option := range pduOptions {
option(p)
}
+ p._PDUData = newPDUData(p)
return p
}
func NewPDUFromPDUWithNewMessage(pdu PDU, msg spi.Message, pduOptions ...PDUOption) PDU {
p := &_PDU{
- newPCI(msg, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()),
+ _PCI: newPCI(msg, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()),
}
for _, option := range pduOptions {
option(p)
}
+ p._PDUData = newPDUData(p)
return p
}
func NewPDUWithAllOptions(msg spi.Message, pduSource *Address, pduDestination *Address, expectingReply bool, networkPriority readWriteModel.NPDUNetworkPriority) *_PDU {
- return &_PDU{
- newPCI(msg, pduSource, pduDestination, expectingReply, networkPriority),
+ p := &_PDU{
+ _PCI: newPCI(msg, pduSource, pduDestination, expectingReply, networkPriority),
}
+ p._PDUData = newPDUData(p)
+ return p
}
type PDUOption func(pdu *_PDU)
@@ -952,6 +1083,14 @@ func WithPDUNetworkPriority(networkPriority readWriteModel.NPDUNetworkPriority)
}
}
+func (p *_PDU) getPDUData() []byte {
+ writeBufferByteBased := utils.NewWriteBufferByteBased()
+ if err := p.GetMessage().SerializeWithWriteBuffer(context.Background(), writeBufferByteBased); err != nil {
+ panic(err) // TODO: graceful handle
+ }
+ return writeBufferByteBased.GetBytes()
+}
+
func (p *_PDU) GetMessage() spi.Message {
return p.pduUserData
}
@@ -981,7 +1120,7 @@ func (p *_PDU) GetNetworkPriority() readWriteModel.NPDUNetworkPriority {
}
func (p *_PDU) deepCopy() *_PDU {
- return &_PDU{p._PCI.deepCopy()}
+ return &_PDU{_PCI: p._PCI.deepCopy(), _PDUData: p._PDUData.deepCopy()}
}
func (p *_PDU) DeepCopy() PDU {
diff --git a/plc4go/internal/bacnetip/comp.go b/plc4go/internal/bacnetip/comp.go
index 099b9a5197..611557a881 100644
--- a/plc4go/internal/bacnetip/comp.go
+++ b/plc4go/internal/bacnetip/comp.go
@@ -19,7 +19,13 @@
package bacnetip
-import "fmt"
+import (
+ "context"
+ "fmt"
+
+ "github.com/apache/plc4x/plc4go/spi"
+ "github.com/apache/plc4x/plc4go/spi/utils"
+)
type Arg any
@@ -99,3 +105,34 @@ const (
kwActorError = knownKey("actorError")
kwError = knownKey("error")
)
+
+type MessageBridge struct {
+ Bytes []byte
+}
+
+var _ spi.Message = (*MessageBridge)(nil)
+var _ _PDUDataRequirements = (*MessageBridge)(nil)
+
+func (m *MessageBridge) String() string {
+ return Btox(m.Bytes)
+}
+
+func (m *MessageBridge) Serialize() ([]byte, error) {
+ return m.Bytes, nil
+}
+
+func (m *MessageBridge) SerializeWithWriteBuffer(_ context.Context, writeBuffer utils.WriteBuffer) error {
+ return writeBuffer.WriteByteArray("Bytes", m.Bytes)
+}
+
+func (m *MessageBridge) GetLengthInBytes(_ context.Context) uint16 {
+ return uint16(len(m.Bytes))
+}
+
+func (m *MessageBridge) GetLengthInBits(ctx context.Context) uint16 {
+ return m.GetLengthInBytes(ctx) * 8
+}
+
+func (m *MessageBridge) getPDUData() []byte {
+ return m.Bytes
+}
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index 6d1f69af50..e446ae661f 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -26,6 +26,7 @@ import (
"fmt"
"math"
"regexp"
+ "slices"
"strconv"
"strings"
"time"
@@ -87,7 +88,9 @@ func NewTag(args Args) (*Tag, error) {
return t, nil
}
if len(args) == 1 {
- t.decode(args[0])
+ if err := t.Decode(args[0].(PDUData)); err != nil {
+ return nil, errors.New("error decoding")
+ }
} else if len(args) >= 2 {
t.set(args)
} else {
@@ -96,7 +99,142 @@ func NewTag(args Args) (*Tag, error) {
return t, nil
}
-func (t *Tag) decode(arg any) {
+func (t *Tag) Decode(pdu PDUData) error {
+ tag, err := pdu.Get()
+ if err != nil {
+ return errors.Wrap(err, "error decoding tag")
+ }
+
+ // extract the type
+ t.tagClass = model.TagClass(tag >> 3 & 0x01)
+
+ // extract the tag number
+ t.tagNumber = uint(tag >> 4)
+ if t.tagNumber == 0x0f {
+ get, err := pdu.Get()
+ if err != nil {
+ return errors.Wrap(err, "error decoding get")
+ }
+ t.tagNumber = uint(get)
+ }
+
+ // extract the length
+ t.tagLVT = int(tag & 0x07)
+ if t.tagLVT == 5 {
+ get, err := pdu.Get()
+ if err != nil {
+ return errors.Wrap(err, "error decoding get")
+ }
+ t.tagLVT = int(get)
+ if t.tagLVT == 254 {
+ get, err := pdu.GetShort()
+ if err != nil {
+ return errors.Wrap(err, "error decoding get")
+ }
+ t.tagLVT = int(get)
+ } else if t.tagLVT == 255 {
+ get, err := pdu.GetLong()
+ if err != nil {
+ return errors.Wrap(err, "error decoding get")
+ }
+ t.tagLVT = int(get)
+ }
+ } else if t.tagLVT == 6 {
+ t.tagClass = TagOpeningTagClass
+ t.tagLVT = 0
+ } else if t.tagLVT == 7 {
+ t.tagClass = TagClosingTagClass
+ t.tagLVT = 0
+ }
+
+ if t.tagClass == model.TagClass_APPLICATION_TAGS && t.tagNumber == uint(model.BACnetDataType_BOOLEAN) {
+ // tagLVT contains value
+ t.tagData = []byte{}
+ } else {
+ // tagLVT contains length
+ t.tagData, err = pdu.GetData(t.tagLVT)
+ if err != nil {
+ return errors.Wrap(err, "error decoding tag data")
+ }
+ }
+ return nil
+}
+
+func (t *Tag) Encode(pdu PDUData) {
+ var data byte
+ // check for special encoding
+ if t.tagClass == model.TagClass_CONTEXT_SPECIFIC_TAGS {
+ data = 0x08
+ } else if t.tagClass == TagOpeningTagClass {
+ data = 0x0E
+ } else if t.tagClass == TagClosingTagClass {
+ data = 0x0F
+ } else {
+ data = 0
+ }
+
+ // encode the tag number part
+ if t.tagNumber < 15 {
+ data += byte(t.tagNumber) << 4
+ } else {
+ data += 0x05
+ }
+
+ // encode the length/value/type part
+ if t.tagLVT < 5 {
+ data += byte(t.tagLVT)
+ } else {
+ data += 0xF0
+ }
+
+ // save this and the extended tag value
+ pdu.Put(data)
+ if t.tagNumber >= 15 {
+ pdu.Put(byte(t.tagNumber))
+ }
+
+ // really short lengths are already done
+ if t.tagLVT >= 5 {
+ if t.tagLVT <= 253 {
+ pdu.Put(byte(t.tagLVT))
+ } else if t.tagLVT <= 65535 {
+ pdu.Put(254)
+ pdu.PutShort(int16(t.tagLVT))
+ } else {
+ pdu.Put(255)
+ pdu.PutLong(int64(t.tagLVT))
+ }
+ }
+
+ // now put the data
+ pdu.PutData(t.tagData...)
+}
+
+func (t *Tag) AppToContext(context uint) (*ContextTag, error) {
+ if t.tagClass != model.TagClass_APPLICATION_TAGS {
+ return nil, errors.New("application tag required")
+ }
+ if t.tagNumber == uint(model.BACnetDataType_BOOLEAN) {
+ return NewContextTag(NewArgs(context, []byte{byte(t.tagLVT)}))
+ }
+ return NewContextTag(NewArgs(context, t.tagData))
+}
+
+func (t *Tag) ContextToApp(context uint) (any, error) {
+ if t.tagClass != model.TagClass_CONTEXT_SPECIFIC_TAGS {
+ return nil, errors.New("context tag required")
+ }
+ if t.tagNumber == uint(model.BACnetDataType_BOOLEAN) {
+ return NewTag(NewArgs(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_BOOLEAN, t.tagData[0], []byte{}))
+ }
+ return NewApplicationTag(NewArgs(context, t.tagData))
+}
+
+// TODO: pretty sure that is not tagListItem but we use that for now because lazy
+func (t *Tag) AppToObject() (TagListItem, error) {
+ if t.tagClass != model.TagClass_APPLICATION_TAGS {
+ return nil, errors.New("context tag required")
+ }
panic("implement me")
}
@@ -129,6 +267,9 @@ func (t *Tag) set(args Args) {
return
}
t.tagData = args[3].([]byte)
+ if len(args) > 4 {
+ panic("oh no")
+ }
}
func (t *Tag) setAppData(tagNumber uint, tdata []byte) {
@@ -146,6 +287,10 @@ func (t *Tag) GetTagNumber() uint {
return t.tagNumber
}
+func (t *Tag) GetTagLvt() int {
+ return t.tagLVT
+}
+
func (t *Tag) GetTagData() []byte {
return t.tagData
}
@@ -168,25 +313,279 @@ func (t *Tag) Equals(other any) bool {
}
type ApplicationTag struct {
- model.BACnetApplicationTag
- // TODO: implement me
+ *Tag
+}
+
+func NewApplicationTag(args Args) (*ApplicationTag, error) {
+ a := &ApplicationTag{}
+ switch len(args) {
+ case 1:
+ if _, ok := args[0].(PDUData); !ok {
+ return nil, errors.New("invalid argument")
+ }
+ var err error
+ a.Tag, err = NewTag(args)
+ if err != nil {
+ return nil, errors.New("error creating tag")
+ }
+ if a.tagClass != model.TagClass_APPLICATION_TAGS {
+ return nil, errors.New("error creating tag: invalid tag class")
+ }
+ return a, nil
+ case 2:
+ var tnum any
+ tnum, ok := args[0].(uint)
+ if !ok {
+ tnum, ok = args[0].(int)
+ if !ok {
+ return nil, errors.New("error creating tag: invalid tag number")
+ }
+ }
+ tdata := args[1].([]byte)
+ var err error
+ a.Tag, err = NewTag(NewArgs(model.TagClass_APPLICATION_TAGS, tnum, len(tdata), tdata))
+ if err != nil {
+ return nil, errors.New("error creating tag")
+ }
+ return a, nil
+ default:
+ return nil, errors.New("requires type and data or pdu data")
+ }
}
type ContextTag struct {
- model.BACnetContextTag
- // TODO: implement me
+ *Tag
+}
+
+func NewContextTag(args Args) (*ContextTag, error) {
+ c := &ContextTag{}
+ switch len(args) {
+ case 1:
+ if _, ok := args[0].(PDUData); !ok {
+ return nil, errors.New("invalid argument")
+ }
+ var err error
+ c.Tag, err = NewTag(args)
+ if err != nil {
+ return nil, errors.New("error creating tag")
+ }
+ if c.tagClass != model.TagClass_CONTEXT_SPECIFIC_TAGS {
+ return nil, errors.New("error creating tag: invalid tag class")
+ }
+ return c, nil
+ case 2:
+ var tnum any
+ tnum, ok := args[0].(uint)
+ if !ok {
+ tnum, ok = args[0].(int)
+ if !ok {
+ return nil, errors.New("error creating tag: invalid tag number")
+ }
+ }
+ tdata := args[1].([]byte)
+ var err error
+ c.Tag, err = NewTag(NewArgs(model.TagClass_CONTEXT_SPECIFIC_TAGS, tnum, len(tdata), tdata))
+ if err != nil {
+ return nil, errors.New("error creating tag")
+ }
+ return c, nil
+ default:
+ return nil, errors.New("requires type and data or pdu data")
+ }
}
type OpeningTag struct {
- // TODO: implement me
+ *Tag
+}
+
+func NewOpeningTag(context Arg) (*OpeningTag, error) {
+ o := &OpeningTag{}
+ switch context.(type) {
+ case int, uint:
+ var err error
+ o.Tag, err = NewTag(NewArgs(TagOpeningTagClass, context))
+ if err != nil {
+ return nil, errors.Wrap(err, "error creating tag")
+ }
+ return o, nil
+ default:
+ return nil, errors.New("invalid argument")
+ }
}
type ClosingTag struct {
- // TODO: implement me
+ *Tag
+}
+
+func NewClosingTag(context Arg) (*ClosingTag, error) {
+ o := &ClosingTag{}
+ switch context.(type) {
+ case int, uint:
+ var err error
+ o.Tag, err = NewTag(NewArgs(TagClosingTagClass, context))
+ if err != nil {
+ return nil, errors.Wrap(err, "error creating tag")
+ }
+ return o, nil
+ default:
+ return nil, errors.New("invalid argument")
+ }
+}
+
+// TODO: if Tag would be an interface we could get rid of this
+type TagListItem interface {
+ GetTagClass() model.TagClass
+ GetTagNumber() uint
+ GetTagLvt() int
+ GetTagData() []byte
+ Encode(pdu PDUData)
+ Decode(pdu PDUData) error
}
type TagList struct {
- // TODO: implement me
+ tagList []TagListItem
+}
+
+func NewTagList(arg Arg) *TagList {
+ t := &TagList{}
+ switch arg := arg.(type) {
+ case []any:
+ args := arg
+ for _, a := range args {
+ t.tagList = append(t.tagList, a.(TagListItem))
+ }
+ case []TagListItem:
+ args := arg
+ for _, a := range args {
+ t.tagList = append(t.tagList, a.(TagListItem))
+ }
+ case Args:
+ args := arg
+ for _, a := range args {
+ t.tagList = append(t.tagList, a.(TagListItem))
+ }
+ }
+ return t
+}
+
+func (b *TagList) Append(tag *Tag) {
+ b.tagList = append(b.tagList, tag)
+}
+
+func (b *TagList) Extend(tags ...*Tag) {
+ for _, tag := range tags {
+ b.tagList = append(b.tagList, tag)
+ }
+}
+
+func (b *TagList) Peek() TagListItem {
+ if len(b.tagList) < 1 {
+ return nil
+ }
+ return b.tagList[0]
+}
+
+func (b *TagList) Push(tag TagListItem) {
+ b.tagList = append([]TagListItem{tag}, b.tagList...)
+}
+
+func (b *TagList) Pop() TagListItem {
+ if len(b.tagList) < 1 {
+ return nil
+ }
+ item := b.tagList[0]
+ b.tagList = b.tagList[1:]
+ return item
+}
+
+// GetContext Return a tag or a list of tags context encoded.
+func (b *TagList) GetContext(context uint) (any, error) {
+ // forward pass
+ i := 0
+ for i < len(b.tagList) {
+ tag := b.tagList[i]
+
+ switch tag.GetTagClass() {
+ case model.TagClass_APPLICATION_TAGS: // skip application stuff
+ case model.TagClass_CONTEXT_SPECIFIC_TAGS: // check for context encoded atomic value
+ if tag.GetTagNumber() == context {
+ return tag, nil
+ }
+ case TagOpeningTagClass:
+ keeper := tag.GetTagNumber() == context
+ var rslt []TagListItem
+ i += 1
+ lvl := 0
+ innerSearch:
+ for i < len(b.tagList) {
+ tag := b.tagList[i]
+ switch tag.GetTagClass() {
+ case TagOpeningTagClass:
+ lvl += 1
+ case TagClosingTagClass:
+ lvl -= 1
+ if lvl < 0 {
+ break innerSearch
+ }
+ }
+
+ rslt = append(rslt, tag)
+ i += 1
+ }
+
+ // make sure everything balances
+ if lvl >= 1 {
+ return nil, errors.New("mismatched open/close tag")
+ }
+
+ // get everything we need
+ if keeper {
+ return NewTagList(rslt), nil
+ }
+ }
+ i += 1
+ }
+ return nil, nil
+}
+
+func (b *TagList) Encode(data PDUData) {
+ for _, tag := range b.tagList {
+ tag.Encode(data)
+ }
+}
+
+func (b *TagList) Decode(data PDUData) error {
+ for len(data.GetPduData()) != 0 {
+ var tag TagListItem
+ tag, err := NewTag(NewArgs(data))
+ if err != nil {
+ return errors.Wrap(err, "error creating tag")
+ }
+ switch tag.GetTagClass() {
+ case model.TagClass_APPLICATION_TAGS:
+ case model.TagClass_CONTEXT_SPECIFIC_TAGS:
+ tag, err = NewContextTag(NewArgs(tag.GetTagNumber(), tag.GetTagData()))
+ if err != nil {
+ panic(err)
+ }
+ case model.TagClass(TagOpeningTagClass):
+ tag, err = NewOpeningTag(tag.GetTagNumber())
+ if err != nil {
+ panic(err)
+ }
+ case model.TagClass(TagClosingTagClass):
+ tag, err = NewClosingTag(tag.GetTagNumber())
+ if err != nil {
+ panic(err)
+ }
+ }
+ b.tagList = append(b.tagList, tag)
+ }
+ return nil
+}
+
+func (b *TagList) GetTagList() []TagListItem {
+ return b.tagList
}
type ComparableAndOrdered interface {
@@ -920,8 +1319,6 @@ func (d *Double) String() string {
}
type OctetString struct {
- //*Atomic[...] no support for []byte
-
value []byte
}
diff --git a/plc4go/internal/bacnetip/tests/test_primitive_data/test_tag_test.go b/plc4go/internal/bacnetip/tests/test_primitive_data/test_tag_test.go
index ed516b5011..24845bfaa1 100644
--- a/plc4go/internal/bacnetip/tests/test_primitive_data/test_tag_test.go
+++ b/plc4go/internal/bacnetip/tests/test_primitive_data/test_tag_test.go
@@ -19,4 +19,520 @@
package test_primitive_data
-// TODO: implement me
+import (
+ "testing"
+
+ "github.com/apache/plc4x/plc4go/internal/bacnetip"
+ "github.com/apache/plc4x/plc4go/protocols/bacnetip/readwrite/model"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func ApplicationTag(args ...any) *bacnetip.ApplicationTag {
+ tag, err := bacnetip.NewApplicationTag(args)
+ if err != nil {
+ panic(err)
+ }
+ return tag
+}
+
+func ContextTag(args ...any) *bacnetip.ContextTag {
+ tag, err := bacnetip.NewContextTag(args)
+ if err != nil {
+ panic(err)
+ }
+ return tag
+}
+
+func OpeningTag(context any) *bacnetip.OpeningTag {
+ openingTag, err := bacnetip.NewOpeningTag(context)
+ if err != nil {
+ panic(err)
+ }
+ return openingTag
+}
+
+func ClosingTag(context any) *bacnetip.ClosingTag {
+ closingTag, err := bacnetip.NewClosingTag(context)
+ if err != nil {
+ panic(err)
+ }
+ return closingTag
+}
+
+func TagList(tags ...bacnetip.TagListItem) *bacnetip.TagList {
+ return bacnetip.NewTagList(tags)
+}
+
+func PDUData(args ...any) bacnetip.PDUData {
+ if args == nil {
+ return bacnetip.NewPDUData(bacnetip.NewArgs(&bacnetip.MessageBridge{}))
+ } else {
+ return bacnetip.NewPDUData(bacnetip.NewArgs(&bacnetip.MessageBridge{Bytes: args[0].([]byte)}))
+ }
+}
+
+func TagTuple(tag *bacnetip.Tag) (tagClass model.TagClass, tagNumber uint, tagLVT int, tagData []byte) {
+ return tag.GetTagClass(), tag.GetTagNumber(), tag.GetTagLvt(), tag.GetTagData()
+}
+
+// Build PDU from the string, decode the tag, convert to an object.
+func objDecode(blob []byte) any {
+ data := PDUData(blob)
+ tag := Tag(data)
+ obj, err := tag.AppToObject()
+ if err != nil {
+ panic(err) // TODO
+ }
+ return obj
+}
+
+// Encode the object into a tag, encode it in a PDU, return the data.
+func objEncode(obj any) []byte {
+ tag := Tag()
+ obj.(interface{ Encode(any) }).Encode(tag) //TODO: what should we use
+ data := PDUData()
+ tag.Encode(data)
+ return data.GetPduData()
+}
+
+// Convert the value (a primitive object) to a hex encoded string,
+//
+// convert the hex encoded string to and object, and compare the results to
+// each other.
+func objEndec(t *testing.T, obj any, x string) {
+ // convert the hex strings to a blobs
+ blob := xtob(x)
+
+ // decode the blob into an object
+ obj2 := objDecode(blob)
+
+ // encode the tag into a blob
+ blob2 := objEncode(obj)
+
+ // compare the results
+ assert.Equal(t, obj, obj2)
+ assert.Equal(t, blob, blob2)
+}
+
+// Build PDU from the string, decode the tag, convert to an object.
+func contextDecode(blob []byte) *bacnetip.ContextTag {
+ data := PDUData(blob)
+ tag := ContextTag(data)
+ return tag
+}
+
+// Encode the object into a tag, encode it in a PDU, return the data.
+func contextEncode(tag *bacnetip.ContextTag) []byte {
+ data := PDUData()
+ tag.Encode(data)
+ return data.GetPduData()
+}
+
+// Convert the value (a primitive object) to a hex encoded string,
+// convert the hex encoded string to and object, and compare the results to
+// each other.
+func contextEndec(t *testing.T, tnum int, x string, y string) {
+ // convert the hex strings to a blobs
+ tdata := xtob(x)
+ blob1 := xtob(y)
+
+ // make a context tag
+ tag1 := ContextTag(tnum, tdata)
+
+ // decode the blob into a tag
+ tag2 := contextDecode(blob1)
+
+ // encode the tag into a blob
+ blob2 := contextEncode(tag1)
+
+ // compare the results
+ assert.Equal(t, tag1, tag2)
+ assert.Equal(t, blob1, blob2)
+}
+
+// Build PDU from the string, decode the tag, convert to an object.
+func openingDecode(blob []byte) *bacnetip.OpeningTag {
+ data := PDUData(blob)
+ tag := OpeningTag(data)
+ return tag
+}
+
+// Encode the object into a tag, encode it in a PDU, return the data.
+func openingEncode(tag *bacnetip.OpeningTag) []byte {
+ data := PDUData()
+ tag.Encode(data)
+ return data.GetPduData()
+}
+
+// Convert the value (a primitive object) to a hex encoded string,
+// convert the hex encoded string to and object, and compare the results to
+// each other.
+func openingEndec(t *testing.T, tnum int, x string) {
+
+ // convert the hex string to a blob
+ blob1 := xtob(x)
+
+ // make a context tag
+ tag1 := OpeningTag(tnum)
+
+ // decode the blob into a tag
+ tag2 := openingDecode(blob1)
+
+ // encode the tag into a blob
+ blob2 := openingEncode(tag1)
+
+ // compare the results
+ assert.Equal(t, tag1, tag2)
+ assert.Equal(t, blob1, blob2)
+}
+
+// Build PDU from the string, decode the tag, convert to an object.
+func closingDecode(blob []byte) *bacnetip.ClosingTag {
+ data := PDUData(blob)
+ tag := ClosingTag(data)
+ return tag
+}
+
+// Encode the object into a tag, encode it in a PDU, return the data.
+func closingEncode(tag *bacnetip.ClosingTag) []byte {
+ data := PDUData()
+ tag.Encode(data)
+ return data.GetPduData()
+}
+
+// Convert the value (a primitive object) to a hex encoded string,
+// convert the hex encoded string to and object, and compare the results to
+// each other.
+func closingEndec(t *testing.T, tnum int, x string) {
+ // convert the hex string to a blob
+ blob1 := xtob(x)
+
+ // make a context tag
+ tag1 := ClosingTag(tnum)
+
+ // decode the blob into a tag
+ tag2 := closingDecode(blob1)
+
+ // encode the tag into a blob
+ blob2 := closingEncode(tag1)
+
+ // compare the result
+ assert.Equal(t, tag1, tag2)
+ assert.Equal(t, blob1, blob2)
+}
+
+func TestTag(t *testing.T) {
+ tag := Tag()
+ assert.Equal(t, model.TagClass(0), tag.GetTagClass())
+ assert.Equal(t, uint(0), tag.GetTagNumber())
+
+ // must have a valid encoded tag to extract the from the data
+ data := PDUData(xtob(""))
+ assert.Panics(t, func() {
+ Tag(data)
+ })
+
+ // must have two values, class and number
+ assert.Panics(t, func() {
+ Tag(0)
+ })
+
+ tag = Tag(0, 1)
+ assert.Equal(t, model.TagClass(0), tag.GetTagClass())
+ assert.Equal(t, uint(1), tag.GetTagNumber())
+
+ tag = Tag(0, 1, 2)
+ assert.Equal(t, model.TagClass(0), tag.GetTagClass())
+ assert.Equal(t, uint(1), tag.GetTagNumber())
+ assert.Equal(t, 2, tag.GetTagLvt())
+
+ // tag data must be bytes of bytearray
+ assert.Panics(t, func() {
+ Tag(0, 1, 2, 3)
+ })
+}
+
+func TestApplicationTag(t *testing.T) {
+ tag := ApplicationTag(0, xtob(""))
+ _ = tag
+
+ assert.Panics(t, func() {
+ ApplicationTag(0)
+ })
+}
+
+func TestGenericApplicationToContext(t *testing.T) {
+ t.Skip("Not yet ready") // TODO: implement me
+ // create and application
+ tag := ApplicationTag(0, xtob("01"))
+
+ // convert it to context tagged, context 0
+ ctag, err := tag.AppToContext(0)
+ require.NoError(t, err)
+
+ // create a context tag with the same shape
+ ttag := ContextTag(0, xtob("00"))
+
+ // check to see if they are the same
+ assert.Equal(t, ctag, ttag)
+
+ // convert the context tag back to an application tag
+ dtag, err := ctag.ContextToApp(uint(model.BACnetDataType_BOOLEAN))
+ require.NoError(t, err)
+
+ // check to see it round-tripped
+ assert.Equal(t, dtag, tag)
+}
+
+func TestBooleanApplicationToContext(t *testing.T) {
+ t.Skip("Not yet ready") // TODO: implement me
+ // create and application
+ tag := Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_BOOLEAN, 0)
+
+ // convert it to context tagged, context 0
+ ctag, err := tag.AppToContext(0)
+ require.NoError(t, err)
+
+ // create a context tag with the same shape
+ ttag := ContextTag(0, xtob("00"))
+
+ // check to see if they are the same
+ assert.Equal(t, ctag, ttag)
+
+ // convert the context tag back to an application tag
+ dtag, err := ctag.ContextToApp(uint(model.BACnetDataType_BOOLEAN))
+ require.NoError(t, err)
+
+ // check to see it round-tripped
+ assert.Equal(t, dtag, tag)
+}
+
+func TestBooleanApplicationToObject(t *testing.T) {
+ t.Skip("Not yet ready") // TODO: implement me
+ // null
+ objEndec(t, Null(), "00")
+
+ // boolean
+ objEndec(t, Boolean(true), "11")
+ objEndec(t, Boolean(true), "10")
+
+ // unsigned
+ objEndec(t, Unsigned(0), "2100")
+ objEndec(t, Unsigned(1), "2101")
+ objEndec(t, Unsigned(127), "217F")
+ objEndec(t, Unsigned(128), "2180")
+
+ // integer
+ objEndec(t, Integer(0), "3100")
+ objEndec(t, Integer(1), "3101")
+ objEndec(t, Integer(-1), "31FF")
+ objEndec(t, Integer(128), "320080")
+ objEndec(t, Integer(-128), "3180")
+
+ // real
+ objEndec(t, Real(0), "4400000000")
+ objEndec(t, Real(1), "443F800000")
+ objEndec(t, Real(-1), "44BF800000")
+ objEndec(t, Real(73.5), "4442930000")
+
+ // double
+ objEndec(t, Double(0), "55080000000000000000")
+ objEndec(t, Double(1), "55083FF0000000000000")
+ objEndec(t, Double(-1), "5508BFF0000000000000")
+ objEndec(t, Double(73.5), "55084052600000000000")
+
+ // octet string
+ objEndec(t, OctetString(xtob("")), "60")
+ objEndec(t, OctetString(xtob("01")), "6101")
+ objEndec(t, OctetString(xtob("0102")), "620102")
+ objEndec(t, OctetString(xtob("010203040506")), "6506010203040506")
+
+ // character string
+ objEndec(t, CharacterString(""), "7100")
+ objEndec(t, CharacterString("a"), "720061")
+ objEndec(t, CharacterString("abcde"), "7506006162636465")
+
+ // bit string
+ objEndec(t, BitString([]bool{}), "8100")
+ objEndec(t, BitString([]bool{false}), "820700")
+ objEndec(t, BitString([]bool{true}), "820780")
+ objEndec(t, BitString([]bool{true, true, true, true, true}), "8203F8")
+ objEndec(t, BitString([]bool{true, true, true, true, true, true, true, true, true, true}), "8306FFC0")
+
+ // enumerated
+ objEndec(t, Enumerated(0), "9100")
+ objEndec(t, Enumerated(1), "9101")
+ objEndec(t, Enumerated(127), "917F")
+ objEndec(t, Enumerated(128), "9180")
+
+ // date
+ objEndec(t, Date(bacnetip.DateTuple{1, 2, 3, 4}), "A401020304")
+ objEndec(t, Date(bacnetip.DateTuple{255, 2, 3, 4}), "A4FF020304")
+ objEndec(t, Date(bacnetip.DateTuple{1, 255, 3, 4}), "A401FF0304")
+ objEndec(t, Date(bacnetip.DateTuple{1, 2, 255, 4}), "A40102FF04")
+ objEndec(t, Date(bacnetip.DateTuple{1, 2, 3, 255}), "A4010203FF")
+
+ // time
+ objEndec(t, Time(bacnetip.TimeTuple{1, 2, 3, 4}), "B401020304")
+ objEndec(t, Time(bacnetip.TimeTuple{255, 2, 3, 4}), "B4FF020304")
+ objEndec(t, Time(bacnetip.TimeTuple{1, 255, 3, 4}), "B401FF0304")
+ objEndec(t, Time(bacnetip.TimeTuple{1, 2, 255, 4}), "B40102FF04")
+ objEndec(t, Time(bacnetip.TimeTuple{1, 2, 3, 255}), "B4010203FF")
+
+ // object identifier
+ objEndec(t, ObjectIdentifier(0, 0), "C400000000")
+ objEndec(t, ObjectIdentifier(1, 0), "C400400000")
+ objEndec(t, ObjectIdentifier(0, 2), "C400000002")
+ objEndec(t, ObjectIdentifier(3, 4), "C400C00004")
+}
+
+func TestContextTag(t *testing.T) {
+ t.Skip("Not yet ready") // TODO: implement me
+ ContextTag(0, xtob(""))
+
+ contextEndec(t, 0, "", "08")
+ contextEndec(t, 1, "01", "1901")
+ contextEndec(t, 2, "0102", "2A0102")
+ contextEndec(t, 3, "010203", "3B010203")
+ contextEndec(t, 14, "010203", "EB010203")
+ contextEndec(t, 15, "010203", "FB0F010203")
+}
+
+func TestOpeningTag(t *testing.T) {
+ t.Skip("Not yet ready") // TODO: implement me
+ OpeningTag(0)
+
+ openingEndec(t, 0, "0E")
+ openingEndec(t, 1, "1E")
+ openingEndec(t, 2, "2E")
+ openingEndec(t, 3, "3E")
+ openingEndec(t, 14, "EE")
+ openingEndec(t, 15, "FE0F")
+ openingEndec(t, 254, "FEFE")
+}
+
+func TestClosingTag(t *testing.T) {
+ t.Skip("Not yet ready") // TODO: implement me
+ ClosingTag(0)
+
+ closingEndec(t, 0, "0f")
+ closingEndec(t, 1, "1F")
+ closingEndec(t, 2, "2F")
+ closingEndec(t, 3, "3F")
+ closingEndec(t, 14, "EF")
+ closingEndec(t, 15, "FF0F")
+ closingEndec(t, 254, "FFFE")
+}
+
+func TestTagList(t *testing.T) {
+ _ = TagList()
+}
+
+func TestPeek(t *testing.T) {
+ tag0 := IntegerTag("00")
+ taglist := TagList(tag0)
+
+ // peek at the first tag
+ assert.Equal(t, tag0, taglist.Peek())
+
+ // pop of the front
+ tag1 := taglist.Pop()
+ var emptyList = make([]bacnetip.TagListItem, 0)
+ assert.Equal(t, emptyList, taglist.GetTagList())
+
+ // push if back to the front
+ taglist.Push(tag1)
+ assert.Equal(t, []bacnetip.TagListItem{tag1}, taglist.GetTagList())
+}
+
+func TestGetContext(t *testing.T) {
+ tagListData := []bacnetip.TagListItem{
+ ContextTag(0, xtob("00")),
+ ContextTag(1, xtob("01")),
+ OpeningTag(2),
+ IntegerTag("03"),
+ OpeningTag(0),
+ IntegerTag("04"),
+ ClosingTag(0),
+ ClosingTag(2),
+ }
+ taglist := TagList(tagListData...)
+
+ // known to be a simple context encoded element
+ context0, err := taglist.GetContext(0)
+ require.NoError(t, err)
+ assert.Equal(t, tagListData[0], context0)
+
+ // known to be a simple context encoded list of element(s)
+ context2, err := taglist.GetContext(2)
+ require.NoError(t, err)
+ assert.Equal(t, tagListData[3:7], context2.(*bacnetip.TagList).GetTagList())
+
+ // known missing context
+ context3, err := taglist.GetContext(3)
+ require.NoError(t, err)
+ assert.Equal(t, nil, context3)
+}
+
+func TestEndec0(t *testing.T) { // Test bracketed application tagged integer encoding and decoding.
+ tagList := TagList()
+
+ data := PDUData()
+ tagList.Encode(data)
+ var nothing []byte
+ assert.Equal(t, nothing, data.GetPduData())
+
+ tagList = TagList()
+ err := tagList.Decode(data)
+ assert.NoError(t, err)
+ var noItems []bacnetip.TagListItem
+ assert.Equal(t, noItems, tagList.GetTagList())
+}
+
+func TestEndec1(t *testing.T) { // Test bracketed application tagged integer encoding and decoding.
+ tag0 := IntegerTag("00")
+ tag1 := IntegerTag("01")
+ tagList := TagList(tag0, tag1)
+
+ data := PDUData()
+ tagList.Encode(data)
+ assert.Equal(t, xtob("31003101"), data.GetPduData())
+
+ tagList = TagList()
+ err := tagList.Decode(data)
+ assert.NoError(t, err)
+ assert.Equal(t, []bacnetip.TagListItem{tag0, tag1}, tagList.GetTagList())
+}
+
+func TestEndec2(t *testing.T) { // Test bracketed application tagged integer encoding and decoding.
+ tag0 := ContextTag(0, xtob("00"))
+ tag1 := ContextTag(1, xtob("01"))
+ tagList := TagList(tag0, tag1)
+
+ data := PDUData()
+ tagList.Encode(data)
+ assert.Equal(t, xtob("09001901"), data.GetPduData())
+
+ tagList = TagList()
+ err := tagList.Decode(data)
+ assert.NoError(t, err)
+ assert.Equal(t, []bacnetip.TagListItem{tag0, tag1}, tagList.GetTagList())
+}
+
+func TestEndec3(t *testing.T) { // Test bracketed application tagged integer encoding and decoding.
+ tag0 := OpeningTag(0)
+ tag1 := IntegerTag("0102")
+ tag2 := ClosingTag(0)
+ tagList := TagList(tag0, tag1, tag2)
+
+ data := PDUData()
+ tagList.Encode(data)
+ assert.Equal(t, xtob("0E3201020F"), data.GetPduData())
+
+ tagList = TagList()
+ err := tagList.Decode(data)
+ assert.NoError(t, err)
+ assert.Equal(t, []bacnetip.TagListItem{tag0, tag1, tag2}, tagList.GetTagList())
+}