// Copyright 2018-2019 opcua authors. All rights reserved. // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file. package ua import ( "github.com/gopcua/opcua/errors" "github.com/gopcua/opcua/id" ) // eotypes contains all known extension objects. var eotypes = NewTypeRegistry() // init registers known built-in extension objects. func init() { RegisterExtensionObject(NewNumericNodeID(0, id.AnonymousIdentityToken_Encoding_DefaultBinary), new(AnonymousIdentityToken)) RegisterExtensionObject(NewNumericNodeID(0, id.UserNameIdentityToken_Encoding_DefaultBinary), new(UserNameIdentityToken)) RegisterExtensionObject(NewNumericNodeID(0, id.X509IdentityToken_Encoding_DefaultBinary), new(X509IdentityToken)) RegisterExtensionObject(NewNumericNodeID(0, id.IssuedIdentityToken_Encoding_DefaultBinary), new(IssuedIdentityToken)) RegisterExtensionObject(NewNumericNodeID(0, id.ServerStatusDataType_Encoding_DefaultBinary), new(ServerStatusDataType)) RegisterExtensionObject(NewNumericNodeID(0, id.DataChangeNotification_Encoding_DefaultBinary), new(DataChangeNotification)) RegisterExtensionObject(NewNumericNodeID(0, id.ReadRawModifiedDetails_Encoding_DefaultBinary), new(ReadRawModifiedDetails)) RegisterExtensionObject(NewNumericNodeID(0, id.HistoryData_Encoding_DefaultBinary), new(HistoryData)) RegisterExtensionObject(NewNumericNodeID(0, id.SubscriptionDiagnosticsDataType_Encoding_DefaultBinary), new(SubscriptionDiagnosticsDataType)) } // RegisterExtensionObject registers a new extension object type. // It panics if the type or the id is already registered. func RegisterExtensionObject(typeID *NodeID, v interface{}) { if err := eotypes.Register(typeID.String(), v); err != nil { panic("Extension object " + err.Error()) } } // These flags define the value type of an ExtensionObject. // They cannot be combined. const ( ExtensionObjectEmpty = 0 ExtensionObjectBinary = 1 ExtensionObjectXML = 2 ) // ExtensionObject is encoded as sequence of bytes prefixed by the NodeId of its DataTypeEncoding // and the number of bytes encoded. // // Specification: Part 6, 5.2.2.15 type ExtensionObject struct { EncodingMask uint8 TypeID *ExpandedNodeID Value interface{} } func NewExtensionObject(value interface{}) *ExtensionObject { e := &ExtensionObject{ TypeID: ExtensionObjectTypeID(value), Value: value, } e.UpdateMask() return e } func (e *ExtensionObject) Decode(b []byte) (int, error) { buf := NewBuffer(b) e.TypeID = new(ExpandedNodeID) buf.ReadStruct(e.TypeID) e.EncodingMask = buf.ReadByte() if e.EncodingMask == ExtensionObjectEmpty { return buf.Pos(), buf.Error() } length := buf.ReadUint32() if length == 0 || length == 0xffffffff || buf.Error() != nil { return buf.Pos(), buf.Error() } body := NewBuffer(buf.ReadN(int(length))) if buf.Error() != nil { return buf.Pos(), buf.Error() } if e.EncodingMask == ExtensionObjectXML { e.Value = new(XMLElement) body.ReadStruct(e.Value) return buf.Pos(), body.Error() } typeID := e.TypeID.NodeID.String() e.Value = eotypes.New(typeID) if e.Value == nil { return buf.Pos(), errors.Errorf("invalid extension object with id %s", typeID) } body.ReadStruct(e.Value) return buf.Pos(), body.Error() } func (e *ExtensionObject) Encode() ([]byte, error) { buf := NewBuffer(nil) if e == nil { e = &ExtensionObject{TypeID: NewTwoByteExpandedNodeID(0), EncodingMask: ExtensionObjectEmpty} } buf.WriteStruct(e.TypeID) buf.WriteByte(e.EncodingMask) if e.EncodingMask == ExtensionObjectEmpty { return buf.Bytes(), buf.Error() } body := NewBuffer(nil) body.WriteStruct(e.Value) if body.Error() != nil { return nil, body.Error() } buf.WriteUint32(uint32(body.Len())) buf.Write(body.Bytes()) return buf.Bytes(), buf.Error() } func (e *ExtensionObject) UpdateMask() { if e.Value == nil { e.EncodingMask = ExtensionObjectEmpty } else if _, ok := e.Value.(*XMLElement); ok { e.EncodingMask = ExtensionObjectXML } else { e.EncodingMask = ExtensionObjectBinary } } func ExtensionObjectTypeID(v interface{}) *ExpandedNodeID { switch v.(type) { case *AnonymousIdentityToken: return NewFourByteExpandedNodeID(0, id.AnonymousIdentityToken_Encoding_DefaultBinary) case *UserNameIdentityToken: return NewFourByteExpandedNodeID(0, id.UserNameIdentityToken_Encoding_DefaultBinary) case *X509IdentityToken: return NewFourByteExpandedNodeID(0, id.X509IdentityToken_Encoding_DefaultBinary) case *IssuedIdentityToken: return NewFourByteExpandedNodeID(0, id.IssuedIdentityToken_Encoding_DefaultBinary) case *ServerStatusDataType: return NewFourByteExpandedNodeID(0, id.ServerStatusDataType_Encoding_DefaultBinary) default: return NewTwoByteExpandedNodeID(0) } }