Repository: gopcua/opcua
Pull request #364 (2020-08-16 18:47:34)

ua: add support for encoding null variants
Follow-up to https://github.com/gopcua/opcua/pull/284, add a codec test case for a variant with a value of zero and ensure that it can be encoded without yielding a run-time panic.

What do you think @kung-foo, @magiconair?

magiconair: Which codepath does the test check? 
ceh: > Which codepath does the test check?

Encoding a NULL variant.

The following program:
```go
package main

import (
	"github.com/gopcua/opcua/ua"
)

func main() {
	v := ua.Variant{}
	_, err := v.Decode([]byte{0x0})
	if err != nil {
		panic(err)
	}
	v.Encode()
}
```

Causes a run-time panic on the current main branch:
```
panic: reflect: call of reflect.Value.Interface on zero Value

goroutine 1 [running]:
reflect.valueInterface(0x0, 0x0, 0x0, 0x1, 0x8, 0x4eb960)
	/usr/local/go/src/reflect/value.go:1005 +0x1a5
reflect.Value.Interface(...)
	/usr/local/go/src/reflect/value.go:1000
github.com/gopcua/opcua/ua.(*Variant).encode(0xc000135f40, 0xc000135ec8, 0x0, 0x0, 0x0)
	/home/ceh/code/src/github.com/gopcua/opcua/ua/variant.go:327 +0x119
github.com/gopcua/opcua/ua.(*Variant).Encode(0xc000135f40, 0xc0000153e3, 0x1, 0x1, 0x1, 0x0)
	/home/ceh/code/src/github.com/gopcua/opcua/ua/variant.go:312 +0x180
main.main()
	/home/ceh/nullvariant.go:13 +0x89
exit status 2
```
magiconair: But this doesn't cover the `sliceDim(nil)` case, correct? I'm looking at the PR and see two fixes but only one additional test. I'm not sure if the fixes are related. Can you elaborate?

Patch:
diff --git a/ua/variant.go b/ua/variant.go
index 72d37f1..07f938d 100644
--- a/ua/variant.go
+++ b/ua/variant.go
@@ -112,6 +112,11 @@ func (m *Variant) Decode(b []byte) (int, error) {
 	buf := NewBuffer(b)
 	m.mask = buf.ReadByte()
 
+	// a null value specifies that no other fields are encoded
+	if m.Type() == TypeIDNull {
+		return buf.Pos(), buf.Error()
+	}
+
 	// check the type
 	typ, ok := variantTypeIDToType[m.Type()]
 	if !ok {
@@ -302,9 +307,13 @@ func (m *Variant) decodeValue(buf *Buffer) interface{} {
 // Encode implements the codec interface.
 func (m *Variant) Encode() ([]byte, error) {
 	buf := NewBuffer(nil)
-
 	buf.WriteByte(m.mask)
 
+	// a null value specifies that no other fields are encoded
+	if m.Type() == TypeIDNull {
+		return buf.Bytes(), buf.Error()
+	}
+
 	if m.Has(VariantArrayValues) {
 		buf.WriteInt32(m.arrayLength)
 	}
@@ -395,6 +404,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) {
+	// null type
+	if v.Kind() == reflect.Invalid {
+		return nil, nil, 0, nil
+	}
+
 	// ByteString is its own type
 	if v.Type() == reflect.TypeOf([]byte{}) {
 		return v.Type(), nil, 1, nil


Test patch:
diff --git a/ua/variant_test.go b/ua/variant_test.go
index 0611dc1..e64ed26 100644
--- a/ua/variant_test.go
+++ b/ua/variant_test.go
@@ -17,6 +17,11 @@ import (
 
 func TestVariant(t *testing.T) {
 	cases := []CodecTestCase{
+		{
+			Name:   "null",
+			Struct: MustVariant(nil),
+			Bytes:  []byte{0x0},
+		},
 		{
 			Name:   "boolean",
 			Struct: MustVariant(false),
@@ -613,6 +618,13 @@ func TestSet(t *testing.T) {
 		va  *Variant
 		err error
 	}{
+		{
+			v: nil,
+			va: &Variant{
+				mask:  byte(TypeIDNull),
+				value: nil,
+			},
+		},
 		{
 			v: []byte{0xca, 0xfe},
 			va: &Variant{
@@ -693,6 +705,13 @@ func TestSliceDim(t *testing.T) {
 		err error
 	}{
 		// happy flows
+		{
+			v:   nil,
+			et:  nil,
+			dim: nil,
+			len: 0,
+			err: nil,
+		},
 		{
 			v:   "a",
 			et:  reflect.TypeOf(""),