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:12 UTC
(plc4x) branch develop updated (68fa1fad66 -> 8ecdf2bac4)
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
from 68fa1fad66 feat(plc4go/bacnet): add primitive OctetString
new 1d292b15c4 refactor(plc4go/bacnet): changes on bit string
new d0d84ada58 refactor(plc4go/bacnet): changes on date
new f863c968ea feat(plc4go/bacnet): added primitive time
new 7911ac8f07 feat(plc4go/bacnet): added primitive unsigned
new bbaa04381b feat(plc4go/bacnet): added primitive time
new 32485d0345 fix(plc4go/bacnet): fix primitive bitstring
new 8ecdf2bac4 feat(plc4go/bacnet): add primitive tag and taglist
The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
plc4go/internal/bacnetip/PDU.go | 159 ++-
plc4go/internal/bacnetip/comp.go | 39 +-
plc4go/internal/bacnetip/primitivedata.go | 1162 +++++++++++++-------
.../test_primitive_data/test_bit_string_test.go | 12 +-
.../tests/test_primitive_data/test_date_test.go | 10 +-
.../tests/test_primitive_data/test_tag_test.go | 518 ++++++++-
.../tests/test_primitive_data/test_time_test.go | 122 +-
.../test_primitive_data/test_unsigned_test.go | 185 +++-
8 files changed, 1802 insertions(+), 405 deletions(-)
(plc4x) 05/07: feat(plc4go/bacnet): added primitive time
Posted by sr...@apache.org.
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 bbaa04381b7646d9c8c6948760f6ffbb5c667d75
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:13:22 2024 +0200
feat(plc4go/bacnet): added primitive time
---
plc4go/internal/bacnetip/primitivedata.go | 259 ++++++++++--------------------
1 file changed, 87 insertions(+), 172 deletions(-)
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index f7b991c74f..283e3dc714 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -1808,132 +1808,44 @@ func NewTime(arg Arg, args Args) (*Time, error) {
}
return d, nil
case TimeTuple:
- d.year, d.month, d.day, d.dayOfWeek = arg.Year, arg.Month, arg.Day, arg.DayOfWeek
- var tempTime time.Time
- //tempTime.AddTime(d.year, d.month, d.day)
- d.value = tempTime.UnixNano() - (time.Time{}.UnixNano()) // TODO: check this
+ d.value = arg
case string:
// lower case everything
arg = strings.ToLower(arg)
+ timeRegex := regexp.MustCompile(`^([*]|[0-9]+)[:]([*]|[0-9]+)(?:[:]([*]|[0-9]+)(?:[.]([*]|[0-9]+))?)?$`)
+ if !timeRegex.MatchString(arg) {
+ return nil, errors.New("invalid time pattern")
+ }
// make a list of the contents from matching patterns
- matches := [][]string{}
-
- if len(matches) == 0 {
+ match := timeRegex.FindStringSubmatch(arg)[1:]
+ if len(match) == 0 {
return nil, errors.New("unmatched")
}
- var match []string
- if len(matches) == 1 {
- match = matches[0]
- } else {
- // check to see if they really are the same
- panic("what to do here")
- }
-
- // extract the year and normalize
- matchedYear := match[0]
- if matchedYear == "*" || matchedYear == "" {
- year = 0xff
- } else {
- yearParse, err := strconv.ParseInt(matchedYear, 10, 64)
- if err != nil {
- return nil, errors.Wrap(err, "error parsing year")
- }
- year = int(yearParse)
- if year == 0xff {
- return d, nil
- }
- if year < 35 {
- year += 2000
- } else if year < 100 {
- year += 1900
- } else if year < 1900 {
- return nil, errors.New("invalid year")
- }
- }
-
- // extract the month and normalize
- matchedmonth := match[0]
- if specialMonth, ok := _special_mon[matchedmonth]; ok {
- month = specialMonth
- } else {
- monthParse, err := strconv.ParseInt(matchedmonth, 10, 64)
- if err != nil {
- return nil, errors.Wrap(err, "error parsing month")
- }
- month = int(monthParse)
- if month == 0xff {
- return d, nil
- }
- if month == 0 || month > 14 {
- return nil, errors.New("invalid month")
- }
- }
-
- // extract the day and normalize
- matchedday := match[0]
- if specialday, ok := _special_day[matchedday]; ok {
- day = specialday
- } else {
- dayParse, err := strconv.ParseInt(matchedday, 10, 64)
- if err != nil {
- return nil, errors.Wrap(err, "error parsing day")
- }
- day = int(dayParse)
- if day == 0xff {
- return d, nil
- }
- if day == 0 || day > 34 {
- return nil, errors.New("invalid day")
- }
- }
-
- // extract the dayOfWeek and normalize
- matcheddayOfWeek := match[0]
- if specialdayOfWeek, ok := _special_dow[matcheddayOfWeek]; ok {
- dayOfWeek = specialdayOfWeek
- } else if matcheddayOfWeek == "" {
- return d, nil
- } else {
- dayOfWeekParse, err := strconv.ParseInt(matcheddayOfWeek, 10, 64)
- if err != nil {
- return nil, errors.Wrap(err, "error parsing dayOfWeek")
- }
- dayOfWeek = int(dayOfWeekParse)
- if dayOfWeek == 0xff {
- return d, nil
- }
- if dayOfWeek > 7 {
- return nil, errors.New("invalid dayOfWeek")
+ var tupList []int
+ for _, s := range match {
+ if s == "*" {
+ tupList = append(tupList, 255)
+ } else if s == "" {
+ if slices.Contains(match, "*") {
+ tupList = append(tupList, 255)
+ } else {
+ tupList = append(tupList, 0)
+ }
+ } else {
+ i, _ := strconv.Atoi(s)
+ tupList = append(tupList, i)
}
}
-
- // year becomes the correct octet
- if year != 0xff {
- year -= 1900
- }
-
- // save the value
- d.year = year
- d.month = month
- d.day = day
- d.dayOfWeek = dayOfWeek
-
- var tempTime time.Time
- //tempTime.AddTime(year, month, day)
- d.value = tempTime.UnixNano() - (time.Time{}.UnixNano()) // TODO: check this
-
- // calculate the day of the week
- if dayOfWeek == 0 {
- d.calcDayOfWeek()
+ if tupList[3] != 0xff {
+ tupList[3] *= 10
}
+ d.value = TimeTuple{tupList[0], tupList[1], tupList[2], tupList[3]}
+ case time.Duration:
+ d.value = TimeTuple{int(arg.Hours()), int(arg.Minutes()), int(arg.Seconds()), int(arg.Milliseconds() * 10)}
case *Time:
d.value = arg.value
- d.year = arg.year
- d.month = arg.month
- d.day = arg.day
- d.dayOfWeek = arg.dayOfWeek
case float32:
d.now(arg)
default:
@@ -1943,47 +1855,15 @@ func NewTime(arg Arg, args Args) (*Time, error) {
return d, nil
}
-func (d *Time) GetTupleValue() (year int, month int, day int, dayOfWeek int) {
- return d.year, d.month, d.day, d.dayOfWeek
-}
-
-func (d *Time) calcDayOfWeek() {
- year, month, day, dayOfWeek := d.year, d.month, d.day, d.dayOfWeek
-
- // assume the worst
- dayOfWeek = 255
-
- // check for special values
- if year == 255 {
- return
- } else if _, ok := _special_mon_inv[month]; ok {
- return
- } else if _, ok := _special_day_inv[month]; ok {
- return
- } else {
- var today time.Time
- //today = time.Time(year+1900, time.Month(month), day, 0, 0, 0, 0, time.UTC)
- panic(today) // TODO: implement me
- }
-
- // put it back together
- d.year = year
- d.month = month
- d.day = day
- d.dayOfWeek = dayOfWeek
-}
-
-func (d *Time) now(arg float32) {
+func (t *Time) now(arg float32) {
panic("implement me") // TODO
}
-func (d *Time) Encode(tag *Tag) {
- var b []byte
- binary.BigEndian.AppendUint64(b, uint64(d.value))
- tag.setAppData(uint(model.BACnetDataType_TIME), b)
+func (t *Time) Encode(tag *Tag) {
+ tag.setAppData(uint(model.BACnetDataType_TIME), []byte{byte(t.value.Hour), byte(t.value.Minute), byte(t.value.Second), byte(t.value.Hundredth)})
}
-func (d *Time) Decode(tag *Tag) error {
+func (t *Time) Decode(tag *Tag) error {
if tag.tagClass != model.TagClass_APPLICATION_TAGS || tag.tagNumber != uint(model.BACnetDataType_TIME) {
return errors.New("Time application tag required")
}
@@ -1991,40 +1871,75 @@ func (d *Time) Decode(tag *Tag) error {
return errors.New("invalid tag length")
}
- arg := tag.tagData
- year, month, day, dayOfWeek := arg[0], arg[1], arg[2], arg[3]
- var tempTime time.Time
- //tempTime.AddTime(int(year), int(month), int(day))
- d.value = tempTime.UnixNano() - (time.Time{}.UnixNano()) // TODO: check this
- d.year, d.month, d.day, d.dayOfWeek = int(year), int(month), int(day), int(dayOfWeek)
+ tagData := tag.tagData
+
+ t.value = TimeTuple{int(tagData[0]), int(tagData[1]), int(tagData[2]), int(tagData[3])}
return nil
}
-func (d *Time) IsValid(arg any) bool {
+func (t *Time) IsValid(arg any) bool {
_, ok := arg.(bool)
return ok
}
-func (d *Time) String() string {
- year, month, day, dayOfWeek := d.year, d.month, d.day, d.dayOfWeek
- yearStr := "*"
- if year != 255 {
- yearStr = strconv.Itoa(year + 1900)
+func (t *Time) Compare(other any) int {
+ switch other := other.(type) {
+ case *Time:
+ _ = other // TODO: implement
+ return -1
+ default:
+ return -1
}
- monthStr := strconv.Itoa(month)
- if ms, ok := _special_mon_inv[month]; ok {
- monthStr = ms
+}
+
+func (t *Time) LowerThan(other any) bool {
+ switch other := other.(type) {
+ case *Time:
+ _ = other // TODO: implement
+ return false
+ default:
+ return false
}
- dayStr := strconv.Itoa(day)
- if ms, ok := _special_day_inv[day]; ok {
- dayStr = ms
+}
+
+func (t *Time) Equals(other any) bool {
+ return t.value == other
+}
+
+func (t *Time) GetValue() TimeTuple {
+ return t.value
+}
+
+func (t *Time) Coerce(arg Time) TimeTuple {
+ return arg.GetValue()
+}
+
+func (t *Time) String() string {
+ // rip it apart
+ hour, minute, second, hundredth := t.value.Hour, t.value.Minute, t.value.Second, t.value.Hundredth
+
+ rslt := "Time("
+ if hour == 255 {
+ rslt += "*:"
+ } else {
+ rslt += fmt.Sprintf("%02d:", hour)
}
- dowStr := strconv.Itoa(dayOfWeek)
- if ms, ok := _special_dow_inv[dayOfWeek]; ok {
- dowStr = ms
+ if minute == 255 {
+ rslt += "*:"
+ } else {
+ rslt += fmt.Sprintf("%02d:", minute)
}
-
- return fmt.Sprintf("Time(%s-%s-%s %s)", yearStr, monthStr, dayStr, dowStr)
+ if second == 255 {
+ rslt += "*."
+ } else {
+ rslt += fmt.Sprintf("%02d.", second)
+ }
+ if hundredth == 255 {
+ rslt += "*)"
+ } else {
+ rslt += fmt.Sprintf("%02d)", hundredth)
+ }
+ return rslt
}
// ObjectTypeContract provides a set of functions which can be overwritten by a sub struct
(plc4x) 02/07: refactor(plc4go/bacnet): changes on date
Posted by sr...@apache.org.
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 d0d84ada58edfbee0bc9b58221ba99aba4d6bf5a
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:10:02 2024 +0200
refactor(plc4go/bacnet): changes on date
---
plc4go/internal/bacnetip/primitivedata.go | 98 ++++++++++++----------
.../tests/test_primitive_data/test_date_test.go | 10 +--
2 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index 5d91a3b0e1..76a2581c0f 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -1412,17 +1412,11 @@ type DateTuple struct {
}
type Date struct {
- *Atomic[int64]
-
- year int
- month int
- day int
- dayOfWeek int
+ value DateTuple
}
func NewDate(arg Arg, args Args) (*Date, error) {
d := &Date{}
- d.Atomic = NewAtomic[int64](d)
year := 255
if len(args) > 0 {
year = args[0].(int)
@@ -1430,22 +1424,22 @@ func NewDate(arg Arg, args Args) (*Date, error) {
if year >= 1900 {
year = year - 1900
}
- d.year = year
+ d.value.Year = year
month := 0xff
if len(args) > 1 {
month = args[1].(int)
}
- d.month = month
+ d.value.Month = month
day := 0xff
if len(args) > 2 {
day = args[2].(int)
}
- d.day = day
+ d.value.Day = day
dayOfWeek := 0xff
if len(args) > 3 {
dayOfWeek = args[3].(int)
}
- d.dayOfWeek = dayOfWeek
+ d.value.DayOfWeek = dayOfWeek
if arg == nil {
return d, nil
@@ -1458,10 +1452,7 @@ func NewDate(arg Arg, args Args) (*Date, error) {
}
return d, nil
case DateTuple:
- d.year, d.month, d.day, d.dayOfWeek = arg.Year, arg.Month, arg.Day, arg.DayOfWeek
- var tempTime time.Time
- tempTime.AddDate(d.year, d.month, d.day)
- d.value = tempTime.UnixNano() - (time.Time{}.UnixNano()) // TODO: check this
+ d.value = arg
case string:
// lower case everything
arg = strings.ToLower(arg)
@@ -1570,14 +1561,10 @@ func NewDate(arg Arg, args Args) (*Date, error) {
}
// save the value
- d.year = year
- d.month = month
- d.day = day
- d.dayOfWeek = dayOfWeek
-
- var tempTime time.Time
- tempTime.AddDate(year, month, day)
- d.value = tempTime.UnixNano() - (time.Time{}.UnixNano()) // TODO: check this
+ d.value.Year = year
+ d.value.Month = month
+ d.value.Day = day
+ d.value.DayOfWeek = dayOfWeek
// calculate the day of the week
if dayOfWeek == 0 {
@@ -1585,10 +1572,6 @@ func NewDate(arg Arg, args Args) (*Date, error) {
}
case *Date:
d.value = arg.value
- d.year = arg.year
- d.month = arg.month
- d.day = arg.day
- d.dayOfWeek = arg.dayOfWeek
case float32:
d.now(arg)
default:
@@ -1598,12 +1581,8 @@ func NewDate(arg Arg, args Args) (*Date, error) {
return d, nil
}
-func (d *Date) GetTupleValue() (year int, month int, day int, dayOfWeek int) {
- return d.year, d.month, d.day, d.dayOfWeek
-}
-
func (d *Date) calcDayOfWeek() {
- year, month, day, dayOfWeek := d.year, d.month, d.day, d.dayOfWeek
+ year, month, day, dayOfWeek := d.value.Year, d.value.Month, d.value.Day, d.value.DayOfWeek
// assume the worst
dayOfWeek = 255
@@ -1618,14 +1597,15 @@ func (d *Date) calcDayOfWeek() {
} else {
var today time.Time
today = time.Date(year+1900, time.Month(month), day, 0, 0, 0, 0, time.UTC)
- panic(today) // TODO: implement me
+ today.Add(24 * time.Hour)
+ dayOfWeek = int(today.Weekday())
}
// put it back together
- d.year = year
- d.month = month
- d.day = day
- d.dayOfWeek = dayOfWeek
+ d.value.Year = year
+ d.value.Month = month
+ d.value.Day = day
+ d.value.DayOfWeek = dayOfWeek
}
func (d *Date) now(arg float32) {
@@ -1633,9 +1613,7 @@ func (d *Date) now(arg float32) {
}
func (d *Date) Encode(tag *Tag) {
- var b []byte
- binary.BigEndian.AppendUint64(b, uint64(d.value))
- tag.setAppData(uint(model.BACnetDataType_DATE), b)
+ tag.setAppData(uint(model.BACnetDataType_DATE), []byte{byte(d.value.Year), byte(d.value.Month), byte(d.value.Day), byte(d.value.DayOfWeek)})
}
func (d *Date) Decode(tag *Tag) error {
@@ -1648,10 +1626,7 @@ func (d *Date) Decode(tag *Tag) error {
arg := tag.tagData
year, month, day, dayOfWeek := arg[0], arg[1], arg[2], arg[3]
- var tempTime time.Time
- tempTime.AddDate(int(year), int(month), int(day))
- d.value = tempTime.UnixNano() - (time.Time{}.UnixNano()) // TODO: check this
- d.year, d.month, d.day, d.dayOfWeek = int(year), int(month), int(day), int(dayOfWeek)
+ d.value.Year, d.value.Month, d.value.Day, d.value.DayOfWeek = int(year), int(month), int(day), int(dayOfWeek)
return nil
}
@@ -1660,8 +1635,41 @@ func (d *Date) IsValid(arg any) bool {
return ok
}
+func (d *Date) Compare(other any) int {
+ switch other := other.(type) {
+ case *Date:
+ _ = other //TODO: implement me
+ return -1
+ default:
+ return -1
+ }
+}
+
+func (d *Date) LowerThan(other any) bool {
+ switch other := other.(type) {
+ case *Date:
+ // return d.getLong() < other.getLong()
+ _ = other // TODO: implement me
+ return false
+ default:
+ return false
+ }
+}
+
+func (d *Date) Equals(other any) bool {
+ return d.value == other
+}
+
+func (d *Date) GetValue() DateTuple {
+ return d.value
+}
+
+func (d *Date) Coerce(arg Date) DateTuple {
+ return arg.GetValue()
+}
+
func (d *Date) String() string {
- year, month, day, dayOfWeek := d.year, d.month, d.day, d.dayOfWeek
+ year, month, day, dayOfWeek := d.value.Year, d.value.Month, d.value.Day, d.value.DayOfWeek
yearStr := "*"
if year != 255 {
yearStr = strconv.Itoa(year + 1900)
diff --git a/plc4go/internal/bacnetip/tests/test_primitive_data/test_date_test.go b/plc4go/internal/bacnetip/tests/test_primitive_data/test_date_test.go
index 425ce4590c..32481ded9f 100644
--- a/plc4go/internal/bacnetip/tests/test_primitive_data/test_date_test.go
+++ b/plc4go/internal/bacnetip/tests/test_primitive_data/test_date_test.go
@@ -78,9 +78,7 @@ func DateEndec(t *testing.T, v string, x string) {
func TestDate(t *testing.T) {
obj := Date()
- assert.Equal(t, int64(0), obj.GetValue())
- year, month, day, week := obj.GetTupleValue()
- assert.Equal(t, []any{0xff, 0xff, 0xff, 0xff}, []any{year, month, day, week})
+ assert.Equal(t, bacnetip.DateTuple{Year: 0xff, Month: 0xff, Day: 0xff, DayOfWeek: 0xff}, obj.GetValue())
assert.Panics(t, func() {
Date("some string")
@@ -89,16 +87,14 @@ func TestDate(t *testing.T) {
func TestDateTuple(t *testing.T) {
obj := Date(bacnetip.DateTuple{Year: 1, Month: 2, Day: 3, DayOfWeek: 4})
- year, month, day, week := obj.GetTupleValue()
- assert.Equal(t, []any{1, 2, 3, 4}, []any{year, month, day, week})
+ assert.Equal(t, bacnetip.DateTuple{Year: 1, Month: 2, Day: 3, DayOfWeek: 4}, obj.GetValue())
assert.Equal(t, "Date(1901-2-3 thu)", obj.String())
}
func TestDateTag(t *testing.T) {
tag := Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_DATE, 4, xtob("01020304"))
obj := Date(tag)
- year, month, day, week := obj.GetTupleValue()
- assert.Equal(t, []any{1, 2, 3, 4}, []any{year, month, day, week})
+ assert.Equal(t, bacnetip.DateTuple{Year: 1, Month: 2, Day: 3, DayOfWeek: 4}, obj.GetValue())
tag = Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_BOOLEAN, 0, xtob(""))
assert.Panics(t, func() {
(plc4x) 01/07: refactor(plc4go/bacnet): changes on bit string
Posted by sr...@apache.org.
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 1d292b15c4481e59149877c38760adb0f582b508
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:09:00 2024 +0200
refactor(plc4go/bacnet): changes on bit string
---
plc4go/internal/bacnetip/primitivedata.go | 20 +++++++++-----------
.../test_primitive_data/test_bit_string_test.go | 12 ++++++------
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index 49d777d619..5d91a3b0e1 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -1012,10 +1012,8 @@ type BitStringExtension interface {
}
type BitString struct {
- *Atomic[int] // TODO: implement properly
-
bitStringExtension BitStringExtension
- Value []bool
+ value []bool
}
func NewBitString(arg ...any) (*BitString, error) {
@@ -1033,7 +1031,7 @@ func NewBitStringWithExtension(bitStringExtension BitStringExtension, arg ...any
return nil, errors.New("too many arguments")
}
if bitStringExtension != nil {
- b.Value = make([]bool, bitStringExtension.GetBitLen())
+ b.value = make([]bool, bitStringExtension.GetBitLen())
}
switch arg := arg[0].(type) {
case *Tag:
@@ -1042,9 +1040,9 @@ func NewBitStringWithExtension(bitStringExtension BitStringExtension, arg ...any
return nil, errors.Wrap(err, "decoding tag failed")
}
case []int:
- b.Value = make([]bool, len(arg))
+ b.value = make([]bool, len(arg))
for i, v := range arg {
- b.Value[i] = v != 0
+ b.value[i] = v != 0
}
case []string:
bitNames := make(map[string]int)
@@ -1053,15 +1051,15 @@ func NewBitStringWithExtension(bitStringExtension BitStringExtension, arg ...any
}
for _, bit := range arg {
bit, ok := bitNames[bit]
- if !ok || bit < 0 || bit > len(b.Value) {
+ if !ok || bit < 0 || bit > len(b.value) {
return nil, errors.New("constructorElement out of range")
}
- b.Value[bit] = true
+ b.value[bit] = true
}
case *BitString:
- b.Value = arg.Value[:]
+ b.value = arg.value[:]
case model.BACnetApplicationTagBitStringExactly:
- b.Value = arg.GetPayload().GetData()
+ b.value = arg.GetPayload().GetData()
default:
return nil, errors.Errorf("no support for %T yet", arg)
}
@@ -1069,7 +1067,7 @@ func NewBitStringWithExtension(bitStringExtension BitStringExtension, arg ...any
}
func (b *BitString) Decode(tag *Tag) error {
- if tag.GetTagClass() != model.TagClass_APPLICATION_TAGS || tag.GetTagNumber() != uint(TagBitStringAppTag) {
+ if tag.GetTagClass() != model.TagClass_APPLICATION_TAGS || tag.GetTagNumber() != uint(model.BACnetDataType_BIT_STRING) {
return errors.New("bit string application tag required")
}
if len(tag.GetTagData()) == 0 {
diff --git a/plc4go/internal/bacnetip/tests/test_primitive_data/test_bit_string_test.go b/plc4go/internal/bacnetip/tests/test_primitive_data/test_bit_string_test.go
index 012fe2cb2a..6f949d3aa6 100644
--- a/plc4go/internal/bacnetip/tests/test_primitive_data/test_bit_string_test.go
+++ b/plc4go/internal/bacnetip/tests/test_primitive_data/test_bit_string_test.go
@@ -111,15 +111,15 @@ func bitStringEndec(t *testing.T, v []int, x string) {
func TestBitString(t *testing.T) {
obj := BitString()
- assert.Len(t, obj.Value, 0)
+ assert.Len(t, obj.GetValue(), 0)
assert.Equal(t, `BitString()`, obj.String())
obj = BitString([]int{0})
- assert.Equal(t, []bool{false}, obj.Value)
+ assert.Equal(t, []bool{false}, obj.GetValue())
assert.Equal(t, `BitString(0)`, obj.String())
obj = BitString([]int{0, 1})
- assert.Equal(t, []bool{false, true}, obj.Value)
+ assert.Equal(t, []bool{false, true}, obj.GetValue())
assert.Equal(t, `BitString(0,1)`, obj.String())
assert.Panics(t, func() {
@@ -133,7 +133,7 @@ func TestBitString(t *testing.T) {
func TestBitStringSample(t *testing.T) {
obj := SampleBitString()
- assert.Equal(t, []bool(nil), obj.Value)
+ assert.Equal(t, []bool(nil), obj.GetValue())
obj = SampleBitString([]int{1})
assert.Equal(t, `BitString(b0)`, obj.String())
@@ -149,11 +149,11 @@ func TestBitStringSample(t *testing.T) {
func TestBitStringTag(t *testing.T) {
tag := Tag(bacnetip.TagApplicationTagClass, bacnetip.TagBitStringAppTag, 1, xtob("08"))
obj := BitString(tag)
- assert.Len(t, obj.Value, 0)
+ assert.Len(t, obj.GetValue(), 0)
tag = Tag(bacnetip.TagApplicationTagClass, bacnetip.TagBitStringAppTag, 1, xtob("0102"))
obj = BitString(tag)
- assert.Equal(t, []bool{false, false, false, false, false, false, true}, obj.Value)
+ assert.Equal(t, []bool{false, false, false, false, false, false, true}, obj.GetValue())
tag = Tag(bacnetip.TagApplicationTagClass, bacnetip.TagBitStringAppTag, 1, xtob(""))
assert.Panics(t, func() {
(plc4x) 06/07: fix(plc4go/bacnet): fix primitive bitstring
Posted by sr...@apache.org.
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 32485d0345e961a3eb43f1fc5235769a9de15ed7
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:14:03 2024 +0200
fix(plc4go/bacnet): fix primitive bitstring
---
plc4go/internal/bacnetip/primitivedata.go | 36 ++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index 283e3dc714..6d1f69af50 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -1163,15 +1163,15 @@ func (b *BitString) Decode(tag *Tag) error {
// trim off the unused bits
if unused != 0 && unused != 8 {
- b.Value = data[:len(data)-int(unused)]
+ b.value = data[:len(data)-int(unused)]
} else {
- b.Value = data
+ b.value = data
}
return nil
}
func (b *BitString) Encode(tag *Tag) {
- used := len(b.Value) % 8
+ used := len(b.value) % 8
unused := 8 - used
if unused == 8 {
unused = 0
@@ -1181,7 +1181,7 @@ func (b *BitString) Encode(tag *Tag) {
data := []byte{byte(unused)}
// build and append each packed octet
- bits := append(b.Value, make([]bool, unused)...)
+ bits := append(b.value, make([]bool, unused)...)
for i := range len(bits) / 8 {
i = i * 8
x := byte(0)
@@ -1199,6 +1199,32 @@ func (b *BitString) Encode(tag *Tag) {
tag.setAppData(uint(model.BACnetDataType_BIT_STRING), data)
}
+func (b *BitString) Compare(other any) int {
+ switch other := other.(type) {
+ case *BitString:
+ return len(b.value) - len(other.value)
+ default:
+ return -1
+ }
+}
+
+func (b *BitString) LowerThan(other any) bool {
+ switch other := other.(type) {
+ case *BitString:
+ return len(b.value) < len(other.value)
+ default:
+ return false
+ }
+}
+
+func (b *BitString) Equals(other any) bool {
+ return b == other
+}
+
+func (b *BitString) GetValue() []bool {
+ return b.value
+}
+
func (b *BitString) String() string {
// flip the bit names
bitNames := map[int]string{}
@@ -1210,7 +1236,7 @@ func (b *BitString) String() string {
// build a list of values and/or names
var valueList []string
- for index, value := range b.Value {
+ for index, value := range b.value {
if name, ok := bitNames[index]; ok {
if value == true {
valueList = append(valueList, name)
(plc4x) 04/07: feat(plc4go/bacnet): added primitive unsigned
Posted by sr...@apache.org.
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 7911ac8f076f8fa62ddde64fdd681c930a091bba
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:12:25 2024 +0200
feat(plc4go/bacnet): added primitive unsigned
---
plc4go/internal/bacnetip/primitivedata.go | 285 +++++++++++++--------
.../test_primitive_data/test_unsigned_test.go | 185 ++++++++++++-
2 files changed, 363 insertions(+), 107 deletions(-)
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index 12831c2dad..f7b991c74f 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -400,221 +400,294 @@ func (b *Boolean) String() string {
return fmt.Sprintf("Boolean(%s)", value)
}
-// TODO: finish
type Unsigned struct {
- *Atomic[uint]
+ *Atomic[uint32]
*CommonMath
}
func NewUnsigned(arg Arg) (*Unsigned, error) {
- b := &Unsigned{}
- b.Atomic = NewAtomic[uint](b)
+ i := &Unsigned{}
+ i.Atomic = NewAtomic[uint32](i)
if arg == nil {
- return b, nil
+ return i, nil
}
switch arg := arg.(type) {
case *Tag:
- err := b.Decode(arg)
+ err := i.Decode(arg)
if err != nil {
return nil, errors.Wrap(err, "error decoding")
}
- return b, nil
- case bool:
- if arg {
- b.value = 1
+ return i, nil
+ case uint32:
+ i.value = arg
+ case uint:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned: %d", arg)
}
- case *Unsigned:
- b.value = arg.value
- case string:
- switch arg {
- case "True", "true":
- b.value = 1
- case "False", "false":
- default:
- return nil, errors.Errorf("invalid string: %s", arg)
+ i.value = uint32(arg)
+ case int32:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned: %d", arg)
}
+ i.value = uint32(arg)
+ case int:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned: %d", arg)
+ }
+ i.value = uint32(arg)
+ case *Unsigned:
+ i.value = arg.value
default:
return nil, errors.Errorf("invalid constructor datatype: %T", arg)
}
- return b, nil
+ return i, nil
}
-func (b *Unsigned) Encode(tag *Tag) {
- tag.set(NewArgs(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_UNSIGNED_INTEGER, b.value, []byte{}))
+func (i *Unsigned) Encode(tag *Tag) {
+ data := make([]byte, 4)
+ binary.BigEndian.PutUint32(data, uint32(i.value))
+
+ // reduce the value to the smallest number of bytes
+ for len(data) > 1 && data[0] == 0 {
+ data = data[1:]
+ }
+
+ tag.setAppData(uint(model.BACnetDataType_UNSIGNED_INTEGER), data)
}
-func (b *Unsigned) Decode(tag *Tag) error {
+func (i *Unsigned) Decode(tag *Tag) error {
if tag.tagClass != model.TagClass_APPLICATION_TAGS || tag.tagNumber != uint(model.BACnetDataType_UNSIGNED_INTEGER) {
return errors.New("Unsigned application tag required")
}
- if tag.tagLVT > 1 {
- return errors.New("invalid tag value")
+ if len(tag.tagData) == 0 {
+ return errors.New("invalid tag length")
}
+ tagData := tag.tagData
+
// get the data
- if tag.tagLVT == 1 {
- b.value = 1
+ rslt := uint32(0)
+ for _, c := range tagData {
+ rslt = (rslt << 8) + uint32(c)
}
+
+ // save the result
+ i.value = rslt
return nil
}
-func (b *Unsigned) IsValid(arg any) bool {
- _, ok := arg.(bool)
- return ok
+func (i *Unsigned) IsValid(arg any) bool {
+ switch arg := arg.(type) {
+ case string:
+ _, err := strconv.Atoi(arg)
+ return err == nil
+ case int:
+ return arg > 0
+ case int32:
+ return arg > 0
+ case uint:
+ return arg <= math.MaxUint32
+ case uint32:
+ return true
+ default:
+ return false
+ }
}
-func (b *Unsigned) String() string {
- value := "False"
- if b.value == 1 {
- value = "True"
- }
- return fmt.Sprintf("Unsigned(%s)", value)
+func (i *Unsigned) String() string {
+ return fmt.Sprintf("Unsigned(%d)", i.value)
}
-// TODO: finish
type Unsigned8 struct {
*Atomic[uint8]
+ *CommonMath
}
func NewUnsigned8(arg Arg) (*Unsigned8, error) {
- b := &Unsigned8{}
- b.Atomic = NewAtomic[uint8](b)
+ i := &Unsigned8{}
+ i.Atomic = NewAtomic[uint8](i)
if arg == nil {
- return b, nil
+ return i, nil
}
switch arg := arg.(type) {
case *Tag:
- err := b.Decode(arg)
+ err := i.Decode(arg)
if err != nil {
return nil, errors.Wrap(err, "error decoding")
}
- return b, nil
- case bool:
- if arg {
- b.value = 1
+ return i, nil
+ case uint8:
+ i.value = arg
+ case uint:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned8: %d", arg)
}
- case *Unsigned8:
- b.value = arg.value
- case string:
- switch arg {
- case "True", "true":
- b.value = 1
- case "False", "false":
- default:
- return nil, errors.Errorf("invalid string: %s", arg)
+ i.value = uint8(arg)
+ case int:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned8: %d", arg)
}
+ i.value = uint8(arg)
+ case *Unsigned8:
+ i.value = arg.value
default:
return nil, errors.Errorf("invalid constructor datatype: %T", arg)
}
- return b, nil
+ return i, nil
}
-func (b *Unsigned8) Encode(tag *Tag) {
- tag.set(NewArgs(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_UNSIGNED_INTEGER, b.value, []byte{}))
+func (i *Unsigned8) Encode(tag *Tag) {
+ data := make([]byte, 4)
+ binary.BigEndian.PutUint32(data, uint32(i.value))
+
+ // reduce the value to the smallest number of bytes
+ for len(data) > 1 && data[0] == 0 {
+ data = data[1:]
+ }
+
+ tag.setAppData(uint(model.BACnetDataType_UNSIGNED_INTEGER), data)
}
-func (b *Unsigned8) Decode(tag *Tag) error {
+func (i *Unsigned8) Decode(tag *Tag) error {
if tag.tagClass != model.TagClass_APPLICATION_TAGS || tag.tagNumber != uint(model.BACnetDataType_UNSIGNED_INTEGER) {
return errors.New("Unsigned8 application tag required")
}
- if tag.tagLVT > 1 {
- return errors.New("invalid tag value")
+ if len(tag.tagData) == 0 {
+ return errors.New("invalid tag length")
}
+ tagData := tag.tagData
+
// get the data
- if tag.tagLVT == 1 {
- b.value = 1
+ rslt := uint8(0)
+ for _, c := range tagData {
+ rslt = (rslt << 8) + uint8(c)
}
+
+ // save the result
+ i.value = rslt
return nil
}
-func (b *Unsigned8) IsValid(arg any) bool {
- _, ok := arg.(bool)
- return ok
+func (i *Unsigned8) IsValid(arg any) bool {
+ switch arg := arg.(type) {
+ case string:
+ _, err := strconv.Atoi(arg)
+ return err == nil
+ case int:
+ return arg > 0 && arg < 256
+ case int8:
+ return arg > 0
+ case uint:
+ return arg <= math.MaxUint32
+ case uint8:
+ return true
+ default:
+ return false
+ }
}
-func (b *Unsigned8) String() string {
- value := "False"
- if b.value == 1 {
- value = "True"
- }
- return fmt.Sprintf("Unsigned8(%s)", value)
+func (i *Unsigned8) String() string {
+ return fmt.Sprintf("Unsigned8(%d)", i.value)
}
-// TODO: finish
type Unsigned16 struct {
*Atomic[uint16]
+ *CommonMath
}
func NewUnsigned16(arg Arg) (*Unsigned16, error) {
- b := &Unsigned16{}
- b.Atomic = NewAtomic[uint16](b)
+ i := &Unsigned16{}
+ i.Atomic = NewAtomic[uint16](i)
if arg == nil {
- return b, nil
+ return i, nil
}
switch arg := arg.(type) {
case *Tag:
- err := b.Decode(arg)
+ err := i.Decode(arg)
if err != nil {
return nil, errors.Wrap(err, "error decoding")
}
- return b, nil
- case bool:
- if arg {
- b.value = 1
+ return i, nil
+ case uint16:
+ i.value = arg
+ case uint:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned16: %d", arg)
}
- case *Unsigned16:
- b.value = arg.value
- case string:
- switch arg {
- case "True", "true":
- b.value = 1
- case "False", "false":
- default:
- return nil, errors.Errorf("invalid string: %s", arg)
+ i.value = uint16(arg)
+ case int:
+ if !i.IsValid(arg) {
+ return nil, errors.Errorf("invalid Unsigned16: %d", arg)
}
+ i.value = uint16(arg)
+ case *Unsigned16:
+ i.value = arg.value
default:
return nil, errors.Errorf("invalid constructor datatype: %T", arg)
}
- return b, nil
+ return i, nil
}
-func (b *Unsigned16) Encode(tag *Tag) {
- tag.set(NewArgs(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_UNSIGNED_INTEGER, b.value, []byte{}))
+func (i *Unsigned16) Encode(tag *Tag) {
+ data := make([]byte, 4)
+ binary.BigEndian.PutUint32(data, uint32(i.value))
+
+ // reduce the value to the smallest number of bytes
+ for len(data) > 1 && data[0] == 0 {
+ data = data[1:]
+ }
+
+ tag.setAppData(uint(model.BACnetDataType_UNSIGNED_INTEGER), data)
}
-func (b *Unsigned16) Decode(tag *Tag) error {
+func (i *Unsigned16) Decode(tag *Tag) error {
if tag.tagClass != model.TagClass_APPLICATION_TAGS || tag.tagNumber != uint(model.BACnetDataType_UNSIGNED_INTEGER) {
return errors.New("Unsigned16 application tag required")
}
- if tag.tagLVT > 1 {
- return errors.New("invalid tag value")
+ if len(tag.tagData) == 0 {
+ return errors.New("invalid tag length")
}
+ tagData := tag.tagData
+
// get the data
- if tag.tagLVT == 1 {
- b.value = 1
+ rslt := uint16(0)
+ for _, c := range tagData {
+ rslt = (rslt << 8) + uint16(c)
}
+
+ // save the result
+ i.value = rslt
return nil
}
-func (b *Unsigned16) IsValid(arg any) bool {
- _, ok := arg.(bool)
- return ok
+func (i *Unsigned16) IsValid(arg any) bool {
+ switch arg := arg.(type) {
+ case string:
+ _, err := strconv.Atoi(arg)
+ return err == nil
+ case int:
+ return arg > 0 && arg < 65536
+ case int16:
+ return arg > 0
+ case uint:
+ return arg <= math.MaxUint32
+ case uint16:
+ return true
+ default:
+ return false
+ }
}
-func (b *Unsigned16) String() string {
- value := "False"
- if b.value == 1 {
- value = "True"
- }
- return fmt.Sprintf("Unsigned16(%s)", value)
+func (i *Unsigned16) String() string {
+ return fmt.Sprintf("Unsigned16(%d)", i.value)
}
type Integer struct {
diff --git a/plc4go/internal/bacnetip/tests/test_primitive_data/test_unsigned_test.go b/plc4go/internal/bacnetip/tests/test_primitive_data/test_unsigned_test.go
index ed516b5011..3845ec689a 100644
--- a/plc4go/internal/bacnetip/tests/test_primitive_data/test_unsigned_test.go
+++ b/plc4go/internal/bacnetip/tests/test_primitive_data/test_unsigned_test.go
@@ -19,4 +19,187 @@
package test_primitive_data
-// TODO: implement me
+import (
+ "math"
+ "testing"
+
+ "github.com/apache/plc4x/plc4go/internal/bacnetip"
+ "github.com/apache/plc4x/plc4go/protocols/bacnetip/readwrite/model"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Unsigned(arg ...any) *bacnetip.Unsigned {
+ if len(arg) == 0 {
+ unsigned, err := bacnetip.NewUnsigned(nil)
+ if err != nil {
+ panic(err)
+ }
+ return unsigned
+ }
+ unsigned, err := bacnetip.NewUnsigned(arg[0])
+ if err != nil {
+ panic(err)
+ }
+ return unsigned
+}
+
+func Unsigned8(arg ...any) *bacnetip.Unsigned8 {
+ if len(arg) == 0 {
+ unsigned, err := bacnetip.NewUnsigned8(nil)
+ if err != nil {
+ panic(err)
+ }
+ return unsigned
+ }
+ unsigned, err := bacnetip.NewUnsigned8(arg[0])
+ if err != nil {
+ panic(err)
+ }
+ return unsigned
+}
+
+func Unsigned16(arg ...any) *bacnetip.Unsigned16 {
+ if len(arg) == 0 {
+ unsigned, err := bacnetip.NewUnsigned16(nil)
+ if err != nil {
+ panic(err)
+ }
+ return unsigned
+ }
+ unsigned, err := bacnetip.NewUnsigned16(arg[0])
+ if err != nil {
+ panic(err)
+ }
+ return unsigned
+}
+
+func UnsignedTag(x string) *bacnetip.Tag {
+ b := xtob(x)
+ tag := Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_UNSIGNED_INTEGER, len(b), b)
+ return tag
+}
+
+// Encode a Unsigned object into a tag.
+func UnsignedEncode(obj *bacnetip.Unsigned) *bacnetip.Tag {
+ tag := Tag()
+ obj.Encode(tag)
+ return tag
+}
+
+// Decode a Unsigned application tag into a Unsigned.
+func UnsignedDecode(tag *bacnetip.Tag) *bacnetip.Unsigned {
+ obj := Unsigned(tag)
+
+ return obj
+}
+
+// Pass the value to Unsigned, construct a tag from the hex string,
+//
+// and compare results of encode and decoding each other.
+func UnsignedEndec(t *testing.T, v uint32, x string) {
+ tag := UnsignedTag(x)
+
+ obj := Unsigned(v)
+
+ assert.Equal(t, tag, UnsignedEncode(obj))
+ assert.Equal(t, obj, UnsignedDecode(tag))
+}
+
+func TestUnsigned(t *testing.T) {
+ obj := Unsigned()
+ assert.Equal(t, uint32(0), obj.GetValue())
+
+ assert.True(t, obj.IsValid(1))
+ assert.True(t, obj.IsValid("1"))
+ assert.True(t, obj.IsValid(math.MaxInt64))
+ assert.False(t, obj.IsValid(math.MinInt64))
+
+ assert.False(t, obj.IsValid(true))
+ assert.False(t, obj.IsValid(-1))
+ assert.False(t, obj.IsValid(1.0))
+ assert.Panics(t, func() {
+ Unsigned("some string")
+ })
+ assert.Panics(t, func() {
+ Unsigned(1.0)
+ })
+}
+
+func TestUnsignedInt(t *testing.T) {
+ obj := Unsigned(1)
+ assert.Equal(t, uint32(1), obj.GetValue())
+ assert.Equal(t, "Unsigned(1)", obj.String())
+
+ assert.Panics(t, func() {
+ Unsigned(-1)
+ })
+}
+
+func TestUnsignedInt8(t *testing.T) {
+ obj := Unsigned8(1)
+ assert.Equal(t, uint8(1), obj.GetValue())
+ assert.Equal(t, "Unsigned8(1)", obj.String())
+
+ assert.Panics(t, func() {
+ Unsigned8(256)
+ })
+}
+
+func TestUnsignedInt16(t *testing.T) {
+ obj := Unsigned16(1)
+ assert.Equal(t, uint16(1), obj.GetValue())
+ assert.Equal(t, "Unsigned16(1)", obj.String())
+
+ assert.Panics(t, func() {
+ Unsigned16(65536)
+ })
+}
+
+func TestUnsignedTag(t *testing.T) {
+ tag := Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_UNSIGNED_INTEGER, 1, xtob("01"))
+ obj := Unsigned(tag)
+ assert.Equal(t, obj.GetValue(), uint32(1))
+
+ tag = Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_BOOLEAN, 0, xtob(""))
+ assert.Panics(t, func() {
+ Unsigned(tag)
+ })
+
+ tag = Tag(model.TagClass_CONTEXT_SPECIFIC_TAGS, 0, 1, xtob("ff"))
+ assert.Panics(t, func() {
+ Unsigned(tag)
+ })
+
+ tag = Tag(bacnetip.TagOpeningTagClass, 0)
+ assert.Panics(t, func() {
+ Unsigned(tag)
+ })
+}
+
+func TestUnsignedCopy(t *testing.T) {
+ obj1 := Unsigned(12)
+ obj2 := Unsigned(obj1)
+ assert.Equal(t, uint32(12), obj2.GetValue())
+ assert.Equal(t, obj1, obj2)
+}
+
+func TestUnsignedEndec(t *testing.T) {
+ assert.Panics(t, func() {
+ Unsigned(UnsignedTag(""))
+ })
+ UnsignedEndec(t, 0, "00")
+ UnsignedEndec(t, 1, "01")
+ UnsignedEndec(t, 127, "7f")
+ UnsignedEndec(t, 128, "80")
+ UnsignedEndec(t, 255, "ff")
+
+ UnsignedEndec(t, 32767, "7fff")
+ UnsignedEndec(t, 32768, "8000")
+
+ UnsignedEndec(t, 8388607, "7fffff")
+ UnsignedEndec(t, 8388608, "800000")
+
+ UnsignedEndec(t, 2147483647, "7fffffff")
+ UnsignedEndec(t, 2147483648, "80000000")
+}
(plc4x) 03/07: feat(plc4go/bacnet): added primitive time
Posted by sr...@apache.org.
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 f863c968ea20373dce82fe7ba79e43756397da3f
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 19 21:10:48 2024 +0200
feat(plc4go/bacnet): added primitive time
---
plc4go/internal/bacnetip/primitivedata.go | 45 +++-----
.../tests/test_primitive_data/test_time_test.go | 122 ++++++++++++++++++++-
2 files changed, 138 insertions(+), 29 deletions(-)
diff --git a/plc4go/internal/bacnetip/primitivedata.go b/plc4go/internal/bacnetip/primitivedata.go
index 76a2581c0f..12831c2dad 100644
--- a/plc4go/internal/bacnetip/primitivedata.go
+++ b/plc4go/internal/bacnetip/primitivedata.go
@@ -1690,50 +1690,39 @@ func (d *Date) String() string {
return fmt.Sprintf("Date(%s-%s-%s %s)", yearStr, monthStr, dayStr, dowStr)
}
-// TODO: finish me
type TimeTuple struct {
- Year int
- Month int
- Day int
- DayOfWeek int
+ Hour int
+ Minute int
+ Second int
+ Hundredth int
}
-// TODO: finish me
type Time struct {
- *Atomic[int64]
-
- year int
- month int
- day int
- dayOfWeek int
+ value TimeTuple
}
func NewTime(arg Arg, args Args) (*Time, error) {
d := &Time{}
- d.Atomic = NewAtomic[int64](d)
- year := 255
+ hour := 255
if len(args) > 0 {
- year = args[0].(int)
- }
- if year >= 1900 {
- year = year - 1900
+ hour = args[0].(int)
}
- d.year = year
- month := 0xff
+ d.value.Hour = hour
+ minute := 0xff
if len(args) > 1 {
- month = args[1].(int)
+ minute = args[1].(int)
}
- d.month = month
- day := 0xff
+ d.value.Minute = minute
+ second := 0xff
if len(args) > 2 {
- day = args[2].(int)
+ second = args[2].(int)
}
- d.day = day
- dayOfWeek := 0xff
+ d.value.Second = second
+ hundredth := 0xff
if len(args) > 3 {
- dayOfWeek = args[3].(int)
+ hundredth = args[3].(int)
}
- d.dayOfWeek = dayOfWeek
+ d.value.Hundredth = hundredth
if arg == nil {
return d, nil
diff --git a/plc4go/internal/bacnetip/tests/test_primitive_data/test_time_test.go b/plc4go/internal/bacnetip/tests/test_primitive_data/test_time_test.go
index ed516b5011..6567ca9f16 100644
--- a/plc4go/internal/bacnetip/tests/test_primitive_data/test_time_test.go
+++ b/plc4go/internal/bacnetip/tests/test_primitive_data/test_time_test.go
@@ -19,4 +19,124 @@
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"
+)
+
+func Time(arg ...any) *bacnetip.Time {
+ if len(arg) == 0 {
+ Time, err := bacnetip.NewTime(nil, nil)
+ if err != nil {
+ panic(err)
+ }
+ return Time
+ }
+ Time, err := bacnetip.NewTime(arg[0], nil)
+ if err != nil {
+ panic(err)
+ }
+ return Time
+}
+
+// Convert a hex string to a character_string application tag.
+func timeTag(x string) *bacnetip.Tag {
+ b := xtob(x)
+ tag := Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_TIME, len(b), b)
+ return tag
+}
+
+// Encode a Time object into a tag.
+func timeEncode(obj *bacnetip.Time) *bacnetip.Tag {
+ tag := Tag()
+ obj.Encode(tag)
+ return tag
+}
+
+// Decode a Time application tag into a Time.
+func timeDecode(tag *bacnetip.Tag) *bacnetip.Time {
+ obj := Time(tag)
+
+ return obj
+}
+
+// Pass the value to Time, construct a tag from the hex string,
+//
+// and compare results of encode and decoding each other.
+func timeEndec(t *testing.T, v bacnetip.TimeTuple, x string) {
+ tag := timeTag(x)
+
+ obj := Time(v)
+
+ assert.Equal(t, tag, timeEncode(obj))
+ assert.Equal(t, obj, timeDecode(tag))
+}
+
+func TestTime(t *testing.T) {
+ obj := Time()
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 0xff, Minute: 0xff, Second: 0xff, Hundredth: 0xff}, obj.GetValue())
+
+ assert.Panics(t, func() {
+ Time("some string")
+ })
+}
+
+func TestTimeTuple(t *testing.T) {
+ obj := Time(bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 4})
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 4}, obj.GetValue())
+ assert.Equal(t, "Time(01:02:03.04)", obj.String())
+
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 0, Hundredth: 0}, Time("1:2").GetValue())
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 0}, Time("1:2:3").GetValue())
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 40}, Time("1:2:3.4").GetValue())
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 255, Second: 255, Hundredth: 255}, Time("1:*").GetValue())
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 255, Hundredth: 255}, Time("1:2:*").GetValue())
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 255}, Time("1:2:3.*").GetValue())
+}
+
+func TestTimeTag(t *testing.T) {
+ tag := Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_TIME, 4, xtob("01020304"))
+ obj := Time(tag)
+ assert.Equal(t, bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 4}, obj.GetValue())
+
+ tag = Tag(model.TagClass_APPLICATION_TAGS, model.BACnetDataType_BOOLEAN, 0, xtob(""))
+ assert.Panics(t, func() {
+ Time(tag)
+ })
+
+ tag = Tag(model.TagClass_CONTEXT_SPECIFIC_TAGS, 0, 1, xtob("ff"))
+ assert.Panics(t, func() {
+ Time(tag)
+ })
+
+ tag = Tag(bacnetip.TagOpeningTagClass, 0)
+ assert.Panics(t, func() {
+ Time(tag)
+ })
+}
+
+func TestTimeCopy(t *testing.T) {
+ obj1 := Time(bacnetip.TimeTuple{Hour: 1, Minute: 2, Second: 3, Hundredth: 4})
+ obj2 := Time(obj1)
+ assert.Equal(t, obj1, obj2)
+}
+
+func TestTimeNow(t *testing.T) {
+ // TODO: upstream doesn't tests this either
+}
+
+func TestTimeEndec(t *testing.T) {
+ assert.Panics(t, func() {
+ Time(timeTag(""))
+ })
+
+ timeEndec(t, bacnetip.TimeTuple{0, 0, 0, 0}, "00000000")
+ timeEndec(t, bacnetip.TimeTuple{1, 0, 0, 0}, "01000000")
+ timeEndec(t, bacnetip.TimeTuple{0, 2, 0, 0}, "00020000")
+ timeEndec(t, bacnetip.TimeTuple{0, 0, 3, 0}, "00000300")
+ timeEndec(t, bacnetip.TimeTuple{0, 0, 0, 4}, "00000004")
+}
(plc4x) 07/07: feat(plc4go/bacnet): add primitive tag and taglist
Posted by sr...@apache.org.
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())
+}