Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register encode/decode funcs instead of types #699

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ func (c *Client) ActivateSession(ctx context.Context, s *Session) error {
},
ClientSoftwareCertificates: nil,
LocaleIDs: s.cfg.LocaleIDs,
UserIdentityToken: ua.NewExtensionObject(s.cfg.UserIdentityToken),
UserIdentityToken: ua.NewExtensionObject(s.cfg.UserIdentityToken, extensionObjectTypeID(s.cfg.UserIdentityToken)),
UserTokenSignature: s.cfg.UserTokenSignature,
}
return c.SecureChannel().SendRequest(ctx, req, s.resp.AuthenticationToken, func(v interface{}) error {
Expand All @@ -895,6 +895,23 @@ func (c *Client) ActivateSession(ctx context.Context, s *Session) error {
})
}

func extensionObjectTypeID(v interface{}) *ua.ExpandedNodeID {
switch v.(type) {
case *ua.AnonymousIdentityToken:
return ua.NewFourByteExpandedNodeID(0, id.AnonymousIdentityToken_Encoding_DefaultBinary)
case *ua.UserNameIdentityToken:
return ua.NewFourByteExpandedNodeID(0, id.UserNameIdentityToken_Encoding_DefaultBinary)
case *ua.X509IdentityToken:
return ua.NewFourByteExpandedNodeID(0, id.X509IdentityToken_Encoding_DefaultBinary)
case *ua.IssuedIdentityToken:
return ua.NewFourByteExpandedNodeID(0, id.IssuedIdentityToken_Encoding_DefaultBinary)
case *ua.ServerStatusDataType:
return ua.NewFourByteExpandedNodeID(0, id.ServerStatusDataType_Encoding_DefaultBinary)
default:
return ua.NewTwoByteExpandedNodeID(0)
}
}

// CloseSession closes the current session.
//
// See Part 4, 5.6.4
Expand Down
4 changes: 3 additions & 1 deletion ua/activate_session_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package ua

import (
"testing"

"github.com/gopcua/opcua/id"
)

func TestActivateSessionRequest(t *testing.T) {
Expand All @@ -17,7 +19,7 @@ func TestActivateSessionRequest(t *testing.T) {
ClientSignature: &SignatureData{},
ClientSoftwareCertificates: nil,
LocaleIDs: nil,
UserIdentityToken: NewExtensionObject(&AnonymousIdentityToken{PolicyID: "anonymous"}),
UserIdentityToken: NewExtensionObject(&AnonymousIdentityToken{PolicyID: "anonymous"}, NewFourByteExpandedNodeID(0, id.AnonymousIdentityToken_Encoding_DefaultBinary)),
UserTokenSignature: &SignatureData{},
},
Bytes: flatten(
Expand Down
2 changes: 1 addition & 1 deletion ua/cancel_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestCancelRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
RequestHandle: 1,
},
Expand Down
2 changes: 1 addition & 1 deletion ua/cancel_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestCancelResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
CancelCount: 1,
},
Expand Down
2 changes: 1 addition & 1 deletion ua/close_secure_channel_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestCloseSecureChannelRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
},
Bytes: []byte{
Expand Down
2 changes: 1 addition & 1 deletion ua/close_secure_channel_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestCloseSecureChannelResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
},
Bytes: []byte{
Expand Down
2 changes: 1 addition & 1 deletion ua/close_session_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestCloseSessionRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
DeleteSubscriptions: true,
},
Expand Down
2 changes: 1 addition & 1 deletion ua/close_session_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestCloseSessionResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
},
Bytes: []byte{
Expand Down
3 changes: 1 addition & 2 deletions ua/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ type CodecTestCase struct {
Bytes []byte
}

// RunCodecTest tests encoding, decoding and length calclulation for the given
// object.
// RunCodecTest tests encoding, decoding and length calculation for the given object.
func RunCodecTest(t *testing.T, cases []CodecTestCase) {
t.Helper()

Expand Down
2 changes: 1 addition & 1 deletion ua/create_session_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestCreateSessionRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
ClientDescription: &ApplicationDescription{
ApplicationURI: "app-uri",
Expand Down
2 changes: 1 addition & 1 deletion ua/create_session_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestCreateSessionResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
SessionID: NewNumericNodeID(0, 1),
AuthenticationToken: NewByteStringNodeID(0, []byte{
Expand Down
2 changes: 1 addition & 1 deletion ua/create_subscription_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestCreateSubscriptionResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
SubscriptionID: 1,
RevisedPublishingInterval: 1000,
Expand Down
2 changes: 1 addition & 1 deletion ua/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func decode(b []byte, val reflect.Value, name string) (n int, err error) {
}()
}

// fmt.Printf("decode: %s is a %s\n", name, val.Kind())
bitomaxsp marked this conversation as resolved.
Show resolved Hide resolved
buf := NewBuffer(b)
switch {
case isBinaryDecoder(val):
Expand All @@ -51,7 +52,6 @@ func decode(b []byte, val reflect.Value, name string) (n int, err error) {
case isTime(val):
val.Set(reflect.ValueOf(buf.ReadTime()).Convert(val.Type()))
default:
// fmt.Printf("decode: %s is a %s\n", name, val.Kind())
switch val.Kind() {
case reflect.Bool:
val.SetBool(buf.ReadBool())
Expand Down
92 changes: 62 additions & 30 deletions ua/extension_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,33 @@
package ua

import (
"fmt"
"reflect"

"github.com/gopcua/opcua/debug"
"github.com/gopcua/opcua/id"
)

// eotypes contains all known extension objects.
var eotypes = NewTypeRegistry()
var eotypes = NewFuncRegistry()

// 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, v); err != nil {
RegisterExtensionObjectFunc(typeID, DefaultEncodeExtensionObject, DefaultDecodeExtensionObject(v))
}

// RegisterExtensionObjectFunc registers a new extension object type using encode and decode functions
// It panics if the type or the id is already registered.
func RegisterExtensionObjectFunc(typeID *NodeID, ef encodefunc, df decodefunc) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this method is public the encodefunc and decodefunc should also be public imo

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Its essentially an interface that you can still adhere to from outside of the package (see read_unknow_node_id_test.go for example). I don't think anyone should even know about the type name, just the function handle

if err := eotypes.Register(typeID, ef, df); err != nil {
panic("Extension object " + err.Error())
}
}

func Deregister(typeID *NodeID) {
eotypes.Deregister(typeID)
}

// These flags define the value type of an ExtensionObject.
// They cannot be combined.
const (
Expand All @@ -28,6 +40,9 @@ const (
ExtensionObjectXML = 2
)

type encodefunc func(v interface{}) ([]byte, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encodefunc and decodefunc should also be public imo

type decodefunc func(b []byte, v interface{}) error

// ExtensionObject is encoded as sequence of bytes prefixed by the NodeId of its DataTypeEncoding
// and the number of bytes encoded.
//
Expand All @@ -38,15 +53,16 @@ type ExtensionObject struct {
Value interface{}
}

func NewExtensionObject(value interface{}) *ExtensionObject {
func NewExtensionObject(value interface{}, typeID *ExpandedNodeID) *ExtensionObject {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It worth making a comment why and what typeID need to be provided i think

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No one should be using this function imo

e := &ExtensionObject{
TypeID: ExtensionObjectTypeID(value),
TypeID: typeID,
Value: value,
}
e.UpdateMask()
return e
}

// Decode fails if there is no decode func registered for e
func (e *ExtensionObject) Decode(b []byte) (int, error) {
buf := NewBuffer(b)
e.TypeID = new(ExpandedNodeID)
Expand Down Expand Up @@ -74,21 +90,57 @@ func (e *ExtensionObject) Decode(b []byte) (int, error) {
}

typeID := e.TypeID.NodeID
e.Value = eotypes.New(typeID)
if e.Value == nil {
decode := eotypes.DecodeFunc(typeID)
if decode == nil {
debug.Printf("ua: unknown extension object %s", typeID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this message is a bit misleading after refactoring. What we now register is funcs iiuc

return buf.Pos(), buf.Error()
}

body.ReadStruct(e.Value)
err := decode(body.Bytes(), &e.Value)
if err != nil {
// TODO: we are losing Pos by creating new buf in decode?
return buf.Pos(), err
}
// TODO: we are losing Pos by creating new buf in decode?
return buf.Pos(), body.Error()
}

// Encode falls back to defaultencode if there is no encode func registered for e
func (e *ExtensionObject) Encode() ([]byte, error) {
buf := NewBuffer(nil)
if e == nil {
e = &ExtensionObject{TypeID: NewTwoByteExpandedNodeID(0), EncodingMask: ExtensionObjectEmpty}
}

typeID := e.TypeID.NodeID
encode := eotypes.EncodeFunc(typeID)
if encode == nil {
debug.Printf("ua: unknown extension object %s", typeID)
return DefaultEncodeExtensionObject(e)
}
return encode(e)
}

// DefaultDecode creates a new instance of v and decodes into it
func DefaultDecodeExtensionObject(v interface{}) func([]byte, interface{}) error {
return func(b []byte, vv interface{}) error {
rv := reflect.ValueOf(vv)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return fmt.Errorf("incorrect type to decode into")
}
r := reflect.New(reflect.TypeOf(v).Elem()).Interface()
buf := NewBuffer(b)
buf.ReadStruct(r)
reflect.Indirect(rv).Set(reflect.ValueOf(r))
return nil
}
}

// DefaultEncode encodes into bytes based on the go struct
func DefaultEncodeExtensionObject(v interface{}) ([]byte, error) {
e, ok := v.(*ExtensionObject)
if !ok {
return nil, fmt.Errorf("expected ExtensionObject")
}
buf := NewBuffer(nil)
buf.WriteStruct(e.TypeID)
buf.WriteByte(e.EncodingMask)
if e.EncodingMask == ExtensionObjectEmpty {
Expand All @@ -114,23 +166,3 @@ func (e *ExtensionObject) UpdateMask() {
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:
if id := eotypes.Lookup(v); id != nil {
return &ExpandedNodeID{NodeID: id}
}
return NewTwoByteExpandedNodeID(0)
}
}
4 changes: 3 additions & 1 deletion ua/extension_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ package ua

import (
"testing"

"github.com/gopcua/opcua/id"
)

func TestExtensionObject(t *testing.T) {
cases := []CodecTestCase{
{
Name: "anonymous-user-identity-token",
Struct: NewExtensionObject(&AnonymousIdentityToken{PolicyID: "anonymous"}),
Struct: NewExtensionObject(&AnonymousIdentityToken{PolicyID: "anonymous"}, NewFourByteExpandedNodeID(0, id.AnonymousIdentityToken_Encoding_DefaultBinary)),
Bytes: []byte{
// TypeID
0x01, 0x00, 0x41, 0x01,
Expand Down
4 changes: 2 additions & 2 deletions ua/find_servers_on_network_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestFindServersOnNetworkRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
StartingRecordID: 1000,
MaxRecordsToReturn: 0,
Expand All @@ -34,7 +34,7 @@ func TestFindServersOnNetworkRequest(t *testing.T) {
// 0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
// }),
// time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
// 1, 0, 0, "", NewExtensionObject(nil),
// 1, 0, 0, "", NewExtensionObject(nil,nil),
// ),
// 1000,
// 0,
Expand Down
4 changes: 2 additions & 2 deletions ua/find_servers_on_network_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestFindServersOnNetworkResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
LastCounterResetTime: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
Servers: []*ServerOnNetwork{
Expand Down Expand Up @@ -72,7 +72,7 @@ func TestFindServersOnNetworkResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
LastCounterResetTime: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
Servers: []*ServerOnNetwork{
Expand Down
2 changes: 1 addition & 1 deletion ua/find_servers_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestFindServersRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
EndpointURL: "opc.tcp://wow.its.easy:11111/UA/Server",
},
Expand Down
2 changes: 1 addition & 1 deletion ua/find_servers_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestFindServersResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
Servers: []*ApplicationDescription{
{
Expand Down
2 changes: 1 addition & 1 deletion ua/get_endpoints_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestGetEndpointsRequest(t *testing.T) {
}),
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
EndpointURL: "opc.tcp://wow.its.easy:11111/UA/Server",
},
Expand Down
2 changes: 1 addition & 1 deletion ua/get_endpoints_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestGetEndpointsResponse(t *testing.T) {
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
AdditionalHeader: NewExtensionObject(nil, NewTwoByteExpandedNodeID(0)),
},
Endpoints: []*EndpointDescription{
{
Expand Down
Loading