diff --git a/examples/accesslevel/accesslevel.go b/examples/accesslevel/accesslevel.go index 65755282..1a882db2 100644 --- a/examples/accesslevel/accesslevel.go +++ b/examples/accesslevel/accesslevel.go @@ -56,6 +56,6 @@ func main() { case v == nil: log.Print("v == nil") default: - log.Print(v.Value) + log.Print(v.Value()) } } diff --git a/examples/crypto/crypto.go b/examples/crypto/crypto.go index 7461cb89..a473dec0 100644 --- a/examples/crypto/crypto.go +++ b/examples/crypto/crypto.go @@ -72,7 +72,7 @@ func main() { log.Fatal(err) } if v != nil { - fmt.Printf("Server's Time | Conn 1 %s | ", v.Value) + fmt.Printf("Server's Time | Conn 1 %s | ", v.Value()) } else { log.Print("v == nil") } @@ -101,7 +101,7 @@ func main() { log.Fatal(err) } if v != nil { - fmt.Printf("Conn 2: %s\n", v.Value) + fmt.Printf("Conn 2: %s\n", v.Value()) } else { log.Print("v == nil") } diff --git a/examples/datetime/datetime.go b/examples/datetime/datetime.go index b4ab3292..cc5bff75 100644 --- a/examples/datetime/datetime.go +++ b/examples/datetime/datetime.go @@ -60,6 +60,6 @@ func main() { case v == nil: log.Print("v == nil") default: - log.Print(v.Value) + log.Print(v.Value()) } } diff --git a/examples/history-read/history-read.go b/examples/history-read/history-read.go index fc509fbf..b971de78 100644 --- a/examples/history-read/history-read.go +++ b/examples/history-read/history-read.go @@ -95,7 +95,7 @@ func main() { "%s - %s - %v \n", nodes[nodeNum].NodeID.String(), value.SourceTimestamp.Format(time.RFC3339), - value.Value.Value, + value.Value.Value(), ) } } diff --git a/examples/read/read.go b/examples/read/read.go index 4a953593..93105402 100644 --- a/examples/read/read.go +++ b/examples/read/read.go @@ -51,5 +51,5 @@ func main() { if resp.Results[0].Status != ua.StatusOK { log.Fatalf("Status not OK: %v", resp.Results[0].Status) } - log.Print(resp.Results[0].Value.Value) + log.Print(resp.Results[0].Value.Value()) } diff --git a/examples/subscribe/subscribe.go b/examples/subscribe/subscribe.go index 1692de2b..23948585 100644 --- a/examples/subscribe/subscribe.go +++ b/examples/subscribe/subscribe.go @@ -99,7 +99,7 @@ func main() { switch x := res.Value.(type) { case *ua.DataChangeNotification: for _, item := range x.MonitoredItems { - data := item.Value.Value.Value + data := item.Value.Value.Value() log.Printf("MonitoredItem with client handle %v = %v", item.ClientHandle, data) } diff --git a/examples/udt/udt.go b/examples/udt/udt.go index 8b2fa406..14eb0423 100644 --- a/examples/udt/udt.go +++ b/examples/udt/udt.go @@ -68,7 +68,7 @@ func main() { case v == nil: log.Print("v == nil") default: - log.Printf("val: %#v", v.Value.(*ua.ExtensionObject).Value) + log.Printf("val: %#v", v.Value().(*ua.ExtensionObject).Value) } } diff --git a/node.go b/node.go index ddf10117..e8993718 100644 --- a/node.go +++ b/node.go @@ -41,7 +41,7 @@ func (n *Node) BrowseName() (*ua.QualifiedName, error) { if err != nil { return nil, err } - return v.Value.(*ua.QualifiedName), nil + return v.Value().(*ua.QualifiedName), nil } // DisplayName returns the display name of the node. @@ -50,7 +50,7 @@ func (n *Node) DisplayName() (*ua.LocalizedText, error) { if err != nil { return nil, err } - return v.Value.(*ua.LocalizedText), nil + return v.Value().(*ua.LocalizedText), nil } // AccessLevel returns the access level of the node. @@ -61,7 +61,7 @@ func (n *Node) AccessLevel() (ua.AccessLevelType, error) { if err != nil { return 0, err } - return ua.AccessLevelType(v.Value.(uint8)), nil + return ua.AccessLevelType(v.Value().(uint8)), nil } // HasAccessLevel returns true if all bits from mask are @@ -80,7 +80,7 @@ func (n *Node) UserAccessLevel() (ua.AccessLevelType, error) { if err != nil { return 0, err } - return ua.AccessLevelType(v.Value.(uint8)), nil + return ua.AccessLevelType(v.Value().(uint8)), nil } // HasUserAccessLevel returns true if all bits from mask are diff --git a/ua/variant.go b/ua/variant.go index c4695c47..901950bb 100644 --- a/ua/variant.go +++ b/ua/variant.go @@ -5,27 +5,31 @@ package ua import ( + "errors" "fmt" + "reflect" "time" ) -// These flags define the size and dimension of a Variant value. const ( + // VariantArrayDimensions flags whether the array has more than one dimension VariantArrayDimensions = 0x40 - VariantArrayValues = 0x80 + + // VariantArrayValues flags whether the value is an array. + VariantArrayValues = 0x80 ) // Variant is a union of the built-in types. // // Specification: Part 6, 5.2.2.16 type Variant struct { - // EncodingMask contains the type and the array flags + // mask contains the type and the array flags // bits 0:5: built-in type id 1-25 // bit 6: array dimensions // bit 7: array values - EncodingMask byte + mask byte - // ArrayLength is the number of elements in the array. + // arrayLength is the number of elements in the array. // This field is only present if the 'array values' // flag is set. // @@ -33,24 +37,24 @@ type Variant struct { // field specifies the total number of elements. The original array can be // reconstructed from the dimensions that are encoded after the value // field. - ArrayLength int32 + arrayLength int32 - // ArrayDimensionsLength is the numer of dimensions. + // arrayDimensionsLength is the numer of dimensions. // This field is only present if the 'array dimensions' flag // is set. - ArrayDimensionsLength int32 + arrayDimensionsLength int32 - // ArrayDimensions is the size for each dimension. + // arrayDimensions is the size for each dimension. // This field is only present if the 'array dimensions' flag // is set. - ArrayDimensions []int32 + arrayDimensions []int32 - Value interface{} + value interface{} } func NewVariant(v interface{}) (*Variant, error) { va := &Variant{} - if err := va.Set(v); err != nil { + if err := va.set(v); err != nil { return nil, err } return va, nil @@ -64,250 +68,375 @@ func MustVariant(v interface{}) *Variant { return va } +func (m *Variant) EncodingMask() byte { + return m.mask +} + // Type returns the type id of the value. func (m *Variant) Type() TypeID { - return TypeID(m.EncodingMask & 0x3f) + return TypeID(m.mask & 0x3f) } func (m *Variant) setType(t TypeID) { - m.EncodingMask = byte(t & 0x3f) + m.mask |= byte(t & 0x3f) } +// Has returns whether given encoding mask bits are set. func (m *Variant) Has(mask byte) bool { - return m.EncodingMask&mask == mask + return m.mask&mask == mask +} + +// ArrayLength returns the total number of elements for one and multi-dimensional +// array values. +func (m *Variant) ArrayLength() int32 { + return m.arrayLength +} + +// ArrayDimensions returns the dimensions of multi-dimensional arrays. +func (m *Variant) ArrayDimensions() []int32 { + return m.arrayDimensions } +// Value returns the value. +func (m *Variant) Value() interface{} { + return m.Value +} + +// Decode implements the codec interface. func (m *Variant) Decode(b []byte) (int, error) { buf := NewBuffer(b) + m.mask = buf.ReadByte() - m.EncodingMask = buf.ReadByte() + // check the type + typ, ok := variantTypeIDToType[m.Type()] + if !ok { + return buf.Pos(), fmt.Errorf("invalid type id: %d", m.Type()) + } - elems := 1 - if m.Has(VariantArrayValues) { - m.ArrayLength = buf.ReadInt32() - elems = int(m.ArrayLength) - } - - values := make([]interface{}, elems) - for i := 0; i < elems; i++ { - switch m.Type() { - case TypeIDBoolean: - values[i] = buf.ReadBool() - case TypeIDSByte: - values[i] = buf.ReadInt8() - case TypeIDByte: - values[i] = buf.ReadByte() - case TypeIDInt16: - values[i] = buf.ReadInt16() - case TypeIDUint16: - values[i] = buf.ReadUint16() - case TypeIDInt32: - values[i] = buf.ReadInt32() - case TypeIDUint32: - values[i] = buf.ReadUint32() - case TypeIDInt64: - values[i] = buf.ReadInt64() - case TypeIDUint64: - values[i] = buf.ReadUint64() - case TypeIDFloat: - values[i] = buf.ReadFloat32() - case TypeIDDouble: - values[i] = buf.ReadFloat64() - case TypeIDString: - values[i] = buf.ReadString() - case TypeIDDateTime: - values[i] = buf.ReadTime() - case TypeIDGUID: - v := new(GUID) - buf.ReadStruct(v) - values[i] = v - case TypeIDByteString: - values[i] = buf.ReadBytes() - case TypeIDXMLElement: - values[i] = XmlElement(buf.ReadString()) - case TypeIDNodeID: - v := new(NodeID) - buf.ReadStruct(v) - values[i] = v - case TypeIDExpandedNodeID: - v := new(ExpandedNodeID) - buf.ReadStruct(v) - values[i] = v - case TypeIDStatusCode: - values[i] = StatusCode(buf.ReadUint32()) - case TypeIDQualifiedName: - v := new(QualifiedName) - buf.ReadStruct(v) - values[i] = v - case TypeIDLocalizedText: - v := new(LocalizedText) - buf.ReadStruct(v) - values[i] = v - case TypeIDExtensionObject: - v := new(ExtensionObject) - buf.ReadStruct(v) - values[i] = v - case TypeIDDataValue: - v := new(DataValue) - buf.ReadStruct(v) - values[i] = v - case TypeIDVariant: - // todo(fs): limit recursion depth to 100 - v := new(Variant) - buf.ReadStruct(v) - values[i] = v - case TypeIDDiagnosticInfo: - // todo(fs): limit recursion depth to 100 - v := new(DiagnosticInfo) - buf.ReadStruct(v) - values[i] = v - } + // read single value and return + if !m.Has(VariantArrayValues) { + m.value = m.decodeValue(buf) + return buf.Pos(), buf.Error() + } + + // get total array length (flattened for multi-dimensional arrays) + m.arrayLength = buf.ReadInt32() + + // read flattened array elements + n := int(m.arrayLength) + vals := reflect.MakeSlice(reflect.SliceOf(typ), n, n) + for i := 0; i < n; i++ { + vals.Index(i).Set(reflect.ValueOf(m.decodeValue(buf))) } + // check for dimensions of multi-dimensional array if m.Has(VariantArrayDimensions) { - m.ArrayDimensionsLength = buf.ReadInt32() - m.ArrayDimensions = make([]int32, m.ArrayDimensionsLength) - for i := 0; i < int(m.ArrayDimensionsLength); i++ { - m.ArrayDimensions[i] = buf.ReadInt32() + m.arrayDimensionsLength = buf.ReadInt32() + m.arrayDimensions = make([]int32, m.arrayDimensionsLength) + for i := 0; i < int(m.arrayDimensionsLength); i++ { + m.arrayDimensions[i] = buf.ReadInt32() + } + } + + // return early if there is an error since the rest of the code + // depends on the assumption that the array dimensions were read + // correctly. + if buf.Error() != nil { + return buf.Pos(), buf.Error() + } + + // validate that the total number of elements + // matches the product of the array dimensions + if m.arrayDimensionsLength > 0 { + count := int32(1) + for i := range m.arrayDimensions { + count *= m.arrayDimensions[i] + } + if count != m.arrayLength { + return buf.Pos(), errUnbalancedSlice } } - m.Value = values - if elems == 1 { - m.Value = values[0] + // handle one-dimensional arrays + if m.arrayDimensionsLength < 2 { + m.value = vals.Interface() + return buf.Pos(), buf.Error() } + // handle multi-dimensional arrays + // convert dimensions to []int to avoid lots of type casts + dims := make([]int, len(m.arrayDimensions)) + for i := range m.arrayDimensions { + dims[i] = int(m.arrayDimensions[i]) + } + m.value = split(0, 0, vals.Len(), dims, vals).Interface() + return buf.Pos(), buf.Error() } +// split recursively creates a multi-dimensional array from a set of values +// and some given dimensions. +func split(level, i, j int, dims []int, vals reflect.Value) reflect.Value { + if level == len(dims)-1 { + a := vals.Slice(i, j) + // fmt.Printf("split: level:%d i:%d j:%d dims:%v a:%#v\n", level, i, j, dims, a.Interface()) + return a + } + + // split next level + var elems []reflect.Value + if vals.Len() > 0 { + step := (j - i) / dims[level] + for ; i < j; i += step { + elems = append(elems, split(level+1, i, i+step, dims, vals)) + } + } else { + for k := 0; k < dims[level]; k++ { + elems = append(elems, split(level+1, 0, 0, dims, vals)) + } + } + + // now construct the typed slice, i.e. [](type of inner slice) + innerT := elems[0].Type() + a := reflect.MakeSlice(reflect.SliceOf(innerT), len(elems), len(elems)) + for k := range elems { + a.Index(k).Set(elems[k]) + } + // fmt.Printf("split: level:%d i:%d j:%d dims:%v a:%#v\n", level, i, j, dims, a.Interface()) + return a +} + +// decodeValue reads a single value of the base type from the buffer. +func (m *Variant) decodeValue(buf *Buffer) interface{} { + switch m.Type() { + case TypeIDBoolean: + return buf.ReadBool() + case TypeIDSByte: + return buf.ReadInt8() + case TypeIDByte: + return buf.ReadByte() + case TypeIDInt16: + return buf.ReadInt16() + case TypeIDUint16: + return buf.ReadUint16() + case TypeIDInt32: + return buf.ReadInt32() + case TypeIDUint32: + return buf.ReadUint32() + case TypeIDInt64: + return buf.ReadInt64() + case TypeIDUint64: + return buf.ReadUint64() + case TypeIDFloat: + return buf.ReadFloat32() + case TypeIDDouble: + return buf.ReadFloat64() + case TypeIDString: + return buf.ReadString() + case TypeIDDateTime: + return buf.ReadTime() + case TypeIDGUID: + v := new(GUID) + buf.ReadStruct(v) + return v + case TypeIDByteString: + return buf.ReadBytes() + case TypeIDXMLElement: + return XmlElement(buf.ReadString()) + case TypeIDNodeID: + v := new(NodeID) + buf.ReadStruct(v) + return v + case TypeIDExpandedNodeID: + v := new(ExpandedNodeID) + buf.ReadStruct(v) + return v + case TypeIDStatusCode: + return StatusCode(buf.ReadUint32()) + case TypeIDQualifiedName: + v := new(QualifiedName) + buf.ReadStruct(v) + return v + case TypeIDLocalizedText: + v := new(LocalizedText) + buf.ReadStruct(v) + return v + case TypeIDExtensionObject: + v := new(ExtensionObject) + buf.ReadStruct(v) + return v + case TypeIDDataValue: + v := new(DataValue) + buf.ReadStruct(v) + return v + case TypeIDVariant: + // todo(fs): limit recursion depth to 100 + v := new(Variant) + buf.ReadStruct(v) + return v + case TypeIDDiagnosticInfo: + // todo(fs): limit recursion depth to 100 + v := new(DiagnosticInfo) + buf.ReadStruct(v) + return v + default: + return nil + } +} + +// Encode implements the codec interface. func (m *Variant) Encode() ([]byte, error) { buf := NewBuffer(nil) - buf.WriteByte(m.EncodingMask) + buf.WriteByte(m.mask) if m.Has(VariantArrayValues) { - buf.WriteInt32(m.ArrayLength) + buf.WriteInt32(m.arrayLength) } - switch v := m.Value.(type) { - case bool: - buf.WriteBool(v) - case int8: - buf.WriteInt8(v) - case byte: - buf.WriteByte(v) - case int16: - buf.WriteInt16(v) - case uint16: - buf.WriteUint16(v) - case int32: - buf.WriteInt32(v) - case uint32: - buf.WriteUint32(v) - case int64: - buf.WriteInt64(v) - case uint64: - buf.WriteUint64(v) - case float32: - buf.WriteFloat32(v) - case float64: - buf.WriteFloat64(v) - case string: - buf.WriteString(v) - case time.Time: - buf.WriteTime(v) - case *GUID: - buf.WriteStruct(v) - case []byte: - buf.WriteByteString(v) - case XmlElement: - buf.WriteString(string(v)) - case *NodeID: - buf.WriteStruct(v) - case *ExpandedNodeID: - buf.WriteStruct(v) - case StatusCode: - buf.WriteUint32(uint32(v)) - case *QualifiedName: - buf.WriteStruct(v) - case *LocalizedText: - buf.WriteStruct(v) - case *ExtensionObject: - buf.WriteStruct(v) - case *DataValue: - buf.WriteStruct(v) - case *Variant: - buf.WriteStruct(v) - case *DiagnosticInfo: - buf.WriteStruct(v) - } + m.encode(buf, reflect.ValueOf(m.value)) if m.Has(VariantArrayDimensions) { - buf.WriteInt32(m.ArrayDimensionsLength) - for i := 0; i < int(m.ArrayDimensionsLength); i++ { - buf.WriteInt32(m.ArrayDimensions[i]) + buf.WriteInt32(m.arrayDimensionsLength) + for i := 0; i < int(m.arrayDimensionsLength); i++ { + buf.WriteInt32(m.arrayDimensions[i]) } } return buf.Bytes(), buf.Error() } -func (m *Variant) Set(v interface{}) error { - switch v.(type) { +// encode recursively writes the values to the buffer. +func (m *Variant) encode(buf *Buffer, val reflect.Value) { + if val.Kind() != reflect.Slice || m.Type() == TypeIDByteString { + m.encodeValue(buf, val.Interface()) + return + } + for i := 0; i < val.Len(); i++ { + m.encode(buf, val.Index(i)) + } +} + +// encodeValue writes a single value of the base type to the buffer. +func (m *Variant) encodeValue(buf *Buffer, v interface{}) { + switch x := v.(type) { case bool: - m.setType(TypeIDBoolean) + buf.WriteBool(x) case int8: - m.setType(TypeIDSByte) + buf.WriteInt8(x) case byte: - m.setType(TypeIDByte) + buf.WriteByte(x) case int16: - m.setType(TypeIDInt16) + buf.WriteInt16(x) case uint16: - m.setType(TypeIDUint16) + buf.WriteUint16(x) case int32: - m.setType(TypeIDInt32) + buf.WriteInt32(x) case uint32: - m.setType(TypeIDUint32) + buf.WriteUint32(x) case int64: - m.setType(TypeIDInt64) + buf.WriteInt64(x) case uint64: - m.setType(TypeIDUint64) + buf.WriteUint64(x) case float32: - m.setType(TypeIDFloat) + buf.WriteFloat32(x) case float64: - m.setType(TypeIDDouble) + buf.WriteFloat64(x) case string: - m.setType(TypeIDString) + buf.WriteString(x) case time.Time: - m.setType(TypeIDDateTime) + buf.WriteTime(x) case *GUID: - m.setType(TypeIDGUID) + buf.WriteStruct(x) case []byte: - m.setType(TypeIDByteString) + buf.WriteByteString(x) case XmlElement: - m.setType(TypeIDXMLElement) + buf.WriteString(string(x)) case *NodeID: - m.setType(TypeIDNodeID) + buf.WriteStruct(x) case *ExpandedNodeID: - m.setType(TypeIDExpandedNodeID) + buf.WriteStruct(x) case StatusCode: - m.setType(TypeIDStatusCode) + buf.WriteUint32(uint32(x)) case *QualifiedName: - m.setType(TypeIDQualifiedName) + buf.WriteStruct(x) case *LocalizedText: - m.setType(TypeIDLocalizedText) + buf.WriteStruct(x) case *ExtensionObject: - m.setType(TypeIDExtensionObject) + buf.WriteStruct(x) case *DataValue: - m.setType(TypeIDDataValue) + buf.WriteStruct(x) case *Variant: - m.setType(TypeIDVariant) + buf.WriteStruct(x) case *DiagnosticInfo: - m.setType(TypeIDDiagnosticInfo) - default: + buf.WriteStruct(x) + } +} + +// errUnbalancedSlice indicates a multi-dimensional array has different +// number of elements on the same level. +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) { + // ByteString is its own type + if v.Type() == reflect.TypeOf([]byte{}) { + return v.Type(), nil, 1, nil + } + + // element type + if v.Kind() != reflect.Slice { + return v.Type(), nil, 1, nil + } + + // empty array + if v.Len() == 0 { + return v.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() { + return nil, nil, 0, errUnbalancedSlice + } + } + } + + // recurse to inner slice or element type + typ, dim, count, err = sliceDim(v.Index(0)) + if err != nil { + return nil, nil, 0, err + } + return typ, append([]int32{int32(v.Len())}, dim...), count * int32(v.Len()), nil +} + +// set sets the value and updates the flags according to the type. +func (m *Variant) set(v interface{}) error { + // set array length and dimensions if value is a slice + et, dim, count, err := sliceDim(reflect.ValueOf(v)) + if err != nil { + return err + } + + if len(dim) > 0 { + m.mask |= VariantArrayValues + m.arrayLength = count + } + + if len(dim) > 1 { + m.mask |= VariantArrayDimensions + m.arrayDimensionsLength = int32(len(dim)) + m.arrayDimensions = dim + } + + typeid, ok := variantTypeToTypeID[et] + if !ok { return fmt.Errorf("opcua: cannot set variant to %T", v) } - m.Value = v + m.setType(typeid) + m.value = v return nil } @@ -316,72 +445,112 @@ func (m *Variant) Set(v interface{}) error { func (m *Variant) String() string { switch m.Type() { case TypeIDString: - return m.Value.(string) + return m.value.(string) case TypeIDLocalizedText: - return m.Value.(*LocalizedText).Text + return m.value.(*LocalizedText).Text case TypeIDQualifiedName: - return m.Value.(*QualifiedName).Name + return m.value.(*QualifiedName).Name default: return "" - //return fmt.Sprintf("%v", m.Value) + //return fmt.Sprintf("%v", m.value) } } +// Bool returns the boolean value if the type is Boolean. func (m *Variant) Bool() bool { switch m.Type() { case TypeIDBoolean: - return m.Value.(bool) + return m.value.(bool) default: return false } } +// Float returns the float value if the type is one of the float types. func (m *Variant) Float() float64 { switch m.Type() { case TypeIDFloat: - return float64(m.Value.(float32)) + return float64(m.value.(float32)) case TypeIDDouble: - return m.Value.(float64) + return m.value.(float64) default: return 0 } } +// Int returns the int value if the type is one of the int types. func (m *Variant) Int() int64 { switch m.Type() { case TypeIDSByte: - return int64(m.Value.(int8)) + return int64(m.value.(int8)) case TypeIDInt16: - return int64(m.Value.(int16)) + return int64(m.value.(int16)) case TypeIDInt32: - return int64(m.Value.(int32)) + return int64(m.value.(int32)) case TypeIDInt64: - return m.Value.(int64) + return m.value.(int64) default: return 0 } } +// Uint returns the uint value if the type is one of the uint types. func (m *Variant) Uint() uint64 { switch m.Type() { case TypeIDByte: - return uint64(m.Value.(byte)) + return uint64(m.value.(byte)) case TypeIDUint16: - return uint64(m.Value.(uint16)) + return uint64(m.value.(uint16)) case TypeIDUint32: - return uint64(m.Value.(uint32)) + return uint64(m.value.(uint32)) case TypeIDUint64: - return m.Value.(uint64) + return m.value.(uint64) default: return 0 } } +// Time returns the time value if the type is DateTime. func (m *Variant) Time() time.Time { switch m.Type() { case TypeIDDateTime: - return m.Value.(time.Time) + return m.value.(time.Time) default: return time.Time{} } } + +var variantTypeToTypeID = map[reflect.Type]TypeID{} +var variantTypeIDToType = map[TypeID]reflect.Type{ + TypeIDBoolean: reflect.TypeOf(false), + TypeIDSByte: reflect.TypeOf(int8(0)), + TypeIDByte: reflect.TypeOf(uint8(0)), + TypeIDInt16: reflect.TypeOf(int16(0)), + TypeIDUint16: reflect.TypeOf(uint16(0)), + TypeIDInt32: reflect.TypeOf(int32(0)), + TypeIDUint32: reflect.TypeOf(uint32(0)), + TypeIDInt64: reflect.TypeOf(int64(0)), + TypeIDUint64: reflect.TypeOf(uint64(0)), + TypeIDFloat: reflect.TypeOf(float32(0)), + TypeIDDouble: reflect.TypeOf(float64(0)), + TypeIDString: reflect.TypeOf(string("")), + TypeIDDateTime: reflect.TypeOf(time.Time{}), + TypeIDGUID: reflect.TypeOf(new(GUID)), + TypeIDByteString: reflect.TypeOf([]byte{}), + TypeIDXMLElement: reflect.TypeOf(XmlElement("")), + TypeIDNodeID: reflect.TypeOf(new(NodeID)), + TypeIDExpandedNodeID: reflect.TypeOf(new(ExpandedNodeID)), + TypeIDStatusCode: reflect.TypeOf(StatusCode(0)), + TypeIDQualifiedName: reflect.TypeOf(new(QualifiedName)), + TypeIDLocalizedText: reflect.TypeOf(new(LocalizedText)), + TypeIDExtensionObject: reflect.TypeOf(new(ExtensionObject)), + TypeIDDataValue: reflect.TypeOf(new(DataValue)), + TypeIDVariant: reflect.TypeOf(new(Variant)), + TypeIDDiagnosticInfo: reflect.TypeOf(new(DiagnosticInfo)), +} + +func init() { + for id, t := range variantTypeIDToType { + variantTypeToTypeID[t] = id + } +} diff --git a/ua/variant_test.go b/ua/variant_test.go index fdae48b4..362fb48c 100644 --- a/ua/variant_test.go +++ b/ua/variant_test.go @@ -5,8 +5,12 @@ package ua import ( + "fmt" + "reflect" "testing" "time" + + "github.com/pascaldekloe/goe/verify" ) func TestVariant(t *testing.T) { @@ -257,7 +261,7 @@ func TestVariant(t *testing.T) { 0x16, // TypeID 0x01, 0x00, 0x41, 0x01, - // EncodingMask + // mask 0x01, // Length 0x0d, 0x00, 0x00, 0x00, @@ -289,7 +293,7 @@ func TestVariant(t *testing.T) { 0x16, // TypeID 0x01, 0x00, 0x60, 0x03, - // EncodingMask + // mask 0x01, // Length 0x86, 0x00, 0x00, 0x00, @@ -344,9 +348,9 @@ func TestVariant(t *testing.T) { Bytes: []byte{ // variant encoding mask 0x17, - // EncodingMask + // mask 0x01, - // Value + // value 0x0a, // type 0x19, 0x04, 0x20, 0x40, // value }, @@ -376,10 +380,243 @@ func TestVariant(t *testing.T) { 0x01, 0x01, 0x00, 0x00, 0x00, }, }, + { + Name: "[]uint32", + Struct: MustVariant([]uint32{1, 2, 3}), + Bytes: []byte{ + // variant encoding mask + 0x87, + // array length + 0x03, 0x00, 0x00, 0x00, + // array values + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + }, + }, + { + Name: "[3][2]uint32", + Struct: MustVariant([][]uint32{{1, 1}, {2, 2}, {3, 3}}), + Bytes: []byte{ + // variant encoding mask + 0xc7, + // array length + 0x06, 0x00, 0x00, 0x00, + // array values + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + // array dimensions length + 0x02, 0x00, 0x00, 0x00, + // array dimensions + 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + }, + }, + { + Name: "[3][2][1]uint32", + Struct: MustVariant([][][]uint32{ + {{1}, {1}}, + {{2}, {2}}, + {{3}, {3}}, + }), + Bytes: []byte{ + // variant encoding mask + 0xc7, + // array length + 0x06, 0x00, 0x00, 0x00, + // array values + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + // array dimensions length + 0x03, 0x00, 0x00, 0x00, + // array dimensions + 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + }, + }, + { + Name: "[0][0][0]uint32", + Struct: MustVariant([][][]uint32{ + {{}, {}}, + {{}, {}}, + {{}, {}}, + }), + Bytes: []byte{ + // variant encoding mask + 0xc7, + // array length + 0x00, 0x00, 0x00, 0x00, + // array values + // array dimensions length + 0x03, 0x00, 0x00, 0x00, + // array dimensions + 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }, + }, } RunCodecTest(t, cases) } +func TestSet(t *testing.T) { + tests := []struct { + v interface{} + va *Variant + err error + }{ + { + v: []byte{0xca, 0xfe}, + va: &Variant{ + mask: byte(TypeIDByteString), + value: []byte{0xca, 0xfe}, + }, + }, + { + v: [][]byte{{0xca, 0xfe}, {0xaf, 0xfe}}, + va: &Variant{ + mask: byte(VariantArrayValues | TypeIDByteString), + arrayLength: 2, + value: [][]byte{{0xca, 0xfe}, {0xaf, 0xfe}}, + }, + }, + { + v: int32(5), + va: &Variant{ + mask: byte(TypeIDInt32), + value: int32(5), + }, + }, + { + v: []int32{5}, + va: &Variant{ + mask: byte(VariantArrayValues | TypeIDInt32), + arrayLength: 1, + value: []int32{5}, + }, + }, + { + v: [][]int32{{5}, {5}, {5}}, + va: &Variant{ + mask: byte(VariantArrayDimensions | VariantArrayValues | TypeIDInt32), + arrayLength: 3, + arrayDimensionsLength: 2, + arrayDimensions: []int32{3, 1}, + value: [][]int32{{5}, {5}, {5}}, + }, + }, + { + v: [][][]int32{ + {{}, {}}, + {{}, {}}, + {{}, {}}, + }, + va: &Variant{ + mask: byte(VariantArrayDimensions | VariantArrayValues | TypeIDInt32), + arrayLength: 0, + arrayDimensionsLength: 3, + arrayDimensions: []int32{3, 2, 0}, + value: [][][]int32{ + {{}, {}}, + {{}, {}}, + {{}, {}}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%T", tt.v), func(t *testing.T) { + va, err := NewVariant(tt.v) + if got, want := err, tt.err; got != want { + t.Fatalf("got error %v want %v", got, want) + } + verify.Values(t, "variant", va, tt.va) + }) + } +} + +func TestSliceDim(t *testing.T) { + tests := []struct { + v interface{} + et reflect.Type + dim []int32 + len int32 + err error + }{ + // happy flows + { + v: "a", + et: reflect.TypeOf(""), + dim: nil, + len: 1, + }, + { + v: 1, + et: reflect.TypeOf(int(0)), + dim: nil, + len: 1, + }, + { + v: []int{}, + et: reflect.TypeOf(int(0)), + dim: []int32{0}, + len: 0, + }, + { + v: []int{1, 2, 3}, + et: reflect.TypeOf(int(0)), + dim: []int32{3}, + len: 3, + }, + { + v: [][]int{{1, 1}, {2, 2}, {3, 3}}, + et: reflect.TypeOf(int(0)), + dim: []int32{3, 2}, + len: 6, + }, + { + v: [][]int{{}, {}, {}}, + et: reflect.TypeOf(int(0)), + dim: []int32{3, 0}, + len: 0, + }, + + // error flows + { + v: [][]int{{1, 1}, {2, 2, 2}, {3}}, + err: errUnbalancedSlice, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%T", tt.v), func(t *testing.T) { + et, dim, len, err := sliceDim(reflect.ValueOf(tt.v)) + if got, want := err, tt.err; got != want { + t.Fatalf("got error %v want %v", got, want) + } + if got, want := et, tt.et; got != want { + t.Fatalf("got type %v want %v", got, want) + } + if got, want := dim, tt.dim; !reflect.DeepEqual(got, want) { + t.Fatalf("got dimensions %v want %v", got, want) + } + if got, want := len, tt.len; got != want { + t.Fatalf("got len %v want %v", got, want) + } + }) + } +} + func TestVariantUnsupportedType(t *testing.T) { _, err := NewVariant(int(5)) if err == nil {