From 8f6b26b2d3927ca91093d28eb2c7f159318de202 Mon Sep 17 00:00:00 2001 From: Isak Styf Date: Wed, 10 Dec 2025 22:46:15 +0100 Subject: [PATCH 1/3] allow Node to work on a client interface instead of a concrete implementation --- client.go | 10 ++++++++++ node.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 4dee9d49e..58e49c745 100644 --- a/client.go +++ b/client.go @@ -122,6 +122,16 @@ func (a bySecurityLevel) Len() int { return len(a) } func (a bySecurityLevel) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a bySecurityLevel) Less(i, j int) bool { return a[i].SecurityLevel < a[j].SecurityLevel } +type ClientInterface interface { + Browse(context.Context, *ua.BrowseRequest) (*ua.BrowseResponse, error) + BrowseNext(context.Context, *ua.BrowseNextRequest) (*ua.BrowseNextResponse, error) + + NodeFromExpandedNodeID(*ua.ExpandedNodeID) *Node + + Read(context.Context, *ua.ReadRequest) (*ua.ReadResponse, error) + Send(context.Context, ua.Request, func(ua.Response) error) error +} + // Client is a high-level client for an OPC/UA server. // It establishes a secure channel and a session. type Client struct { diff --git a/node.go b/node.go index fb4189991..6d60afcac 100644 --- a/node.go +++ b/node.go @@ -19,7 +19,7 @@ type Node struct { // ID is the node id of the node. ID *ua.NodeID - c *Client + c ClientInterface } func (n *Node) String() string { From f4c30c6d465c2cd07d42567f9b98dac256c92cbd Mon Sep 17 00:00:00 2001 From: Isak Styf Date: Wed, 10 Dec 2025 23:13:27 +0100 Subject: [PATCH 2/3] allow Subscription to work on a client interface instead of a concrete implementation --- client.go | 10 ++++++++++ client_sub.go | 6 +++--- subscription.go | 10 +++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index 58e49c745..e2d1aeb74 100644 --- a/client.go +++ b/client.go @@ -126,10 +126,16 @@ type ClientInterface interface { Browse(context.Context, *ua.BrowseRequest) (*ua.BrowseResponse, error) BrowseNext(context.Context, *ua.BrowseNextRequest) (*ua.BrowseNextResponse, error) + Node(*ua.NodeID) *Node NodeFromExpandedNodeID(*ua.ExpandedNodeID) *Node Read(context.Context, *ua.ReadRequest) (*ua.ReadResponse, error) Send(context.Context, ua.Request, func(ua.Response) error) error + + ForgetSubscription(context.Context, uint32) + RegisterSubscription_NeedsSubMuxLock(*Subscription) error + + RequestTimeout() time.Duration } // Client is a high-level client for an OPC/UA server. @@ -1352,6 +1358,10 @@ func (c *Client) UpdateNamespaces(ctx context.Context) error { return nil } +func (c *Client) RequestTimeout() time.Duration { + return c.cfg.sechan.RequestTimeout +} + // safeAssign implements a type-safe assign from T to *T. func safeAssign(t, ptrT interface{}) error { if reflect.TypeOf(t) != reflect.TypeOf(ptrT).Elem() { diff --git a/client_sub.go b/client_sub.go index 11816d2a6..5e51118c7 100644 --- a/client_sub.go +++ b/client_sub.go @@ -211,8 +211,8 @@ func (c *Client) sendRepublishRequests(ctx context.Context, sub *Subscription, a } } -// registerSubscription_NeedsSubMuxLock registers a subscription -func (c *Client) registerSubscription_NeedsSubMuxLock(sub *Subscription) error { +// RegisterSubscription_NeedsSubMuxLock registers a subscription +func (c *Client) RegisterSubscription_NeedsSubMuxLock(sub *Subscription) error { if sub.SubscriptionID == 0 { return ua.StatusBadSubscriptionIDInvalid } @@ -225,7 +225,7 @@ func (c *Client) registerSubscription_NeedsSubMuxLock(sub *Subscription) error { return nil } -func (c *Client) forgetSubscription(ctx context.Context, id uint32) { +func (c *Client) ForgetSubscription(ctx context.Context, id uint32) { c.subMux.Lock() c.forgetSubscription_NeedsSubMuxLock(ctx, id) c.subMux.Unlock() diff --git a/subscription.go b/subscription.go index 5b5a3d274..9d5bd1e99 100644 --- a/subscription.go +++ b/subscription.go @@ -34,7 +34,7 @@ type Subscription struct { itemsMu sync.Mutex lastSeq uint32 nextSeq uint32 - c *Client + c ClientInterface } type SubscriptionParameters struct { @@ -82,7 +82,7 @@ type PublishNotificationData struct { // from the client and the server. func (s *Subscription) Cancel(ctx context.Context) error { stats.Subscription().Add("Cancel", 1) - s.c.forgetSubscription(ctx, s.SubscriptionID) + s.c.ForgetSubscription(ctx, s.SubscriptionID) return s.delete(ctx) } @@ -321,8 +321,8 @@ func (s *Subscription) publishTimeout() time.Duration { if timeout > uasc.MaxTimeout { return uasc.MaxTimeout } - if timeout < s.c.cfg.sechan.RequestTimeout { - return s.c.cfg.sechan.RequestTimeout + if requestTimeout := s.c.RequestTimeout(); timeout < requestTimeout { + return requestTimeout } return timeout } @@ -447,7 +447,7 @@ func (s *Subscription) recreate_create(ctx context.Context) error { s.lastSeq = 0 s.nextSeq = 1 - if err := s.c.registerSubscription_NeedsSubMuxLock(s); err != nil { + if err := s.c.RegisterSubscription_NeedsSubMuxLock(s); err != nil { return err } dlog.Printf("subscription registered") From ebe6b4e990cd4de7f929feba1bb1331b215534f2 Mon Sep 17 00:00:00 2001 From: Isak Styf Date: Thu, 11 Dec 2025 08:06:10 +0100 Subject: [PATCH 3/3] create a node constructor function to improve testability --- client.go | 4 ++-- node.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index e2d1aeb74..a08ebba54 100644 --- a/client.go +++ b/client.go @@ -1005,14 +1005,14 @@ func (c *Client) sendWithTimeout(ctx context.Context, req ua.Request, timeout ti // Node returns a node object which accesses its attributes // through this client connection. func (c *Client) Node(id *ua.NodeID) *Node { - return &Node{ID: id, c: c} + return NewNode(id, c) } // NodeFromExpandedNodeID returns a node object which accesses its attributes // through this client connection. This is usually needed when working with node ids returned // from browse responses by the server. func (c *Client) NodeFromExpandedNodeID(id *ua.ExpandedNodeID) *Node { - return &Node{ID: ua.NewNodeIDFromExpandedNodeID(id), c: c} + return NewNode(ua.NewNodeIDFromExpandedNodeID(id), c) } // FindServers finds the servers available at an endpoint diff --git a/node.go b/node.go index 6d60afcac..fc1b713bb 100644 --- a/node.go +++ b/node.go @@ -22,6 +22,10 @@ type Node struct { c ClientInterface } +func NewNode(id *ua.NodeID, c ClientInterface) *Node { + return &Node{ID: id, c: c} +} + func (n *Node) String() string { return n.ID.String() }