Skip to content

Commit

Permalink
Issue #678: Fix Variant to handle nil slices
Browse files Browse the repository at this point in the history
Fix the Variant encoder and decoder to handle nil slices
correctly by setting the length to -1.

Closes #678
  • Loading branch information
magiconair committed Dec 9, 2024
1 parent 51f94ee commit 199ccf6
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 41 deletions.
20 changes: 20 additions & 0 deletions ua/datatypes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ func TestDataValue(t *testing.T) {
0x80, 0x3b, 0xe8, 0xb3, 0x92, 0x4e, 0xd4, 0x01,
},
},
{
Name: "value with nil slice, source timestamp, server timestamp",
Struct: &DataValue{
EncodingMask: 0x0d,
Value: MustVariant([]string(nil)),
SourceTimestamp: time.Date(2018, time.September, 17, 14, 28, 29, 112000000, time.UTC),
ServerTimestamp: time.Date(2018, time.September, 17, 14, 28, 29, 112000000, time.UTC),
},
Bytes: []byte{
// EncodingMask
0x0d,
// Value
0x8c, // type
0xff, 0xff, 0xff, 0xff, // value
// SourceTimestamp
0x80, 0x3b, 0xe8, 0xb3, 0x92, 0x4e, 0xd4, 0x01,
// SeverTimestamp
0x80, 0x3b, 0xe8, 0xb3, 0x92, 0x4e, 0xd4, 0x01,
},
},
}
RunCodecTest(t, cases)
}
Expand Down
78 changes: 50 additions & 28 deletions ua/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,29 @@ func (m *Variant) Decode(b []byte) (int, error) {

// read flattened array elements
n := int(m.arrayLength)
if n < 0 || n > MaxVariantArrayLength {
if n > MaxVariantArrayLength {
return buf.Pos(), StatusBadEncodingLimitsExceeded
}

// get the type for the slice
sliceType := reflect.SliceOf(typ)
if m.Type() == TypeIDByte {
sliceType = reflect.TypeOf(ByteArray{})
}

var vals reflect.Value
switch m.Type() {
case TypeIDByte:
vals = reflect.MakeSlice(reflect.TypeOf(ByteArray{}), n, n)
switch {
// decode a nil slice
case n == -1:
vals = reflect.Zero(reflect.MakeSlice(sliceType, 0, 0).Type())
m.value = vals.Interface()

// decode a slice with values
default:
vals = reflect.MakeSlice(reflect.SliceOf(typ), n, n)
}
for i := 0; i < n; i++ {
vals.Index(i).Set(reflect.ValueOf(m.decodeValue(buf)))
vals = reflect.MakeSlice(sliceType, n, n)
for i := 0; i < n; i++ {
vals.Index(i).Set(reflect.ValueOf(m.decodeValue(buf)))
}
}

// check for dimensions of multi-dimensional array
Expand Down Expand Up @@ -416,9 +426,11 @@ var errUnbalancedSlice = errors.New("unbalanced multi-dimensional array")

// sliceDim determines the element type, dimensions and the total length
// of a one or multi-dimensional slice.
func sliceDim(v reflect.Value) (typ reflect.Type, dim []int32, count int32, err error) {
//
// If the value is a nil slice then count is -1.
func sliceDim(val reflect.Value) (typ reflect.Type, dim []int32, count int32, err error) {
// null type
if v.Kind() == reflect.Invalid {
if val.Kind() == reflect.Invalid {
return nil, nil, 0, nil
}

Expand All @@ -430,54 +442,64 @@ func sliceDim(v reflect.Value) (typ reflect.Type, dim []int32, count int32, err
// array of Byte.
//
// https://github.com/gopcua/opcua/issues/463
if v.Type() == reflect.TypeOf([]byte{}) && v.Type() != reflect.TypeOf(ByteArray{}) {
return v.Type(), nil, 1, nil
if val.Type() == reflect.TypeOf([]byte{}) && val.Type() != reflect.TypeOf(ByteArray{}) {
return val.Type(), nil, 1, nil
}

// element type
if v.Kind() != reflect.Slice {
return v.Type(), nil, 1, nil
if val.Kind() != reflect.Slice {
return val.Type(), nil, 1, nil
}

// nil array
if val.IsNil() {
return val.Type().Elem(), nil, -1, nil
}

// empty array
if v.Len() == 0 {
return v.Type().Elem(), append([]int32{0}, dim...), 0, nil
if val.Len() == 0 {
return val.Type().Elem(), append([]int32{0}, dim...), 0, nil
}

// check that inner slices all have the same length
if v.Index(0).Kind() == reflect.Slice {
for i := 0; i < v.Len(); i++ {
if v.Index(i).Len() != v.Index(0).Len() {
if val.Index(0).Kind() == reflect.Slice {
for i := 0; i < val.Len(); i++ {
if val.Index(i).Len() != val.Index(0).Len() {
return nil, nil, 0, errUnbalancedSlice
}
}
}

// recurse to inner slice or element type
typ, dim, count, err = sliceDim(v.Index(0))
typ, dim, count, err = sliceDim(val.Index(0))
if err != nil {
return nil, nil, 0, err
}
return typ, append([]int32{int32(v.Len())}, dim...), count * int32(v.Len()), nil
return typ, append([]int32{int32(val.Len())}, dim...), count * int32(val.Len()), nil
}

// set sets the value and updates the flags according to the type.
func (m *Variant) set(v interface{}) error {
val := reflect.ValueOf(v)

// set array length and dimensions if value is a slice
et, dim, count, err := sliceDim(reflect.ValueOf(v))
et, dim, count, err := sliceDim(val)
if err != nil {
return err
}

if len(dim) > 0 {
m.mask |= VariantArrayValues
switch {
case len(dim) > 1:
m.mask |= VariantArrayValues | VariantArrayDimensions
m.arrayLength = count
}

if len(dim) > 1 {
m.mask |= VariantArrayDimensions
m.arrayDimensionsLength = int32(len(dim))
m.arrayDimensions = dim

case len(dim) > 0 || count == -1:
m.mask |= VariantArrayValues
m.arrayLength = count
m.arrayDimensionsLength = 0
m.arrayDimensions = nil
}

typeid, ok := variantTypeToTypeID[et]
Expand Down
23 changes: 10 additions & 13 deletions ua/variant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,16 @@ func TestVariant(t *testing.T) {
0x01, 0x00, 0x00, 0x00,
},
},
{
Name: "[]string(nil)",
Struct: MustVariant([]string(nil)),
Bytes: []byte{
// variant encoding mask
0x8c,
// array length
0xff, 0xff, 0xff, 0xff,
},
},
}
RunCodecTest(t, cases)
}
Expand Down Expand Up @@ -543,19 +553,6 @@ func TestArray(t *testing.T) {
t.Fatalf("got error %#v want %#v", got, want)
}
})
t.Run("length negative", func(t *testing.T) {
b := []byte{
// variant encoding mask
0x87,
// array length
0xff, 0xff, 0xff, 0xff, // -1
}

_, err := Decode(b, MustVariant([]uint32{0}))
if got, want := err, StatusBadEncodingLimitsExceeded; !errors.Equal(got, want) {
t.Fatalf("got error %#v want %#v", got, want)
}
})
t.Run("length too big", func(t *testing.T) {
b := []byte{
// variant encoding mask
Expand Down

0 comments on commit 199ccf6

Please sign in to comment.