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

Initial Server Implementation #737

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ build/
dist/
__pycache__/
.vscode/
*.pem
*.crt
*.csr
*.exe
.idea/
__debug*
14 changes: 9 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# go test -count=1 disables the test cache so that all tests are run every time.

all: test integration examples
all: test integration selfintegration examples

test:
go test -count=1 -race ./...
Expand All @@ -9,22 +9,26 @@ lint:
staticcheck ./...

integration:
go test -count=1 -race -v -tags=integration ./uatest/...
go test -count=1 -race -v -tags=integration ./tests/python...

selfintegration:
go test -count=1 -race -v -tags=integration ./tests/go...

examples:
go build -o build/ ./examples/...

test-race:
go test -count=1 -race ./...
go test -count=1 -race -v -tags=integration ./uatest/...
go test -count=1 -race -v -tags=integration ./tests/python...
go test -count=1 -race -v -tags=integration ./tests/go...

install-py-opcua:
pip3 install opcua

gen:
go install golang.org/x/tools/cmd/stringer@latest
which stringer || go install golang.org/x/tools/cmd/stringer@latest
find . -name '*_gen.go' -delete
go generate ./...
go mod tidy

release:
GITHUB_TOKEN=$$(security find-generic-password -gs GITHUB_TOKEN -w) goreleaser --clean
Expand Down
111 changes: 69 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ We would be happy if you can add your equipment to the list. Just open a PR :)
| [Prosys OPC UA Simulation Server v5.4.6-148](https://prosysopc.com/products/opc-ua-simulation-server/) | v0.3.x | [manual testing](https://github.com/united-manufacturing-hub/benthos-umh?tab=readme-ov-file#testing) | [UMH](https://www.umh.app) |
| InfluxDB Telegraf plugin | v0.3.x | ? | Community |

## Supported Features
## Supported Client Features

The current focus is on the OPC UA Binary protocol over TCP. No other protocols are supported at this point.

Expand All @@ -182,51 +182,78 @@ The current focus is on the OPC UA Binary protocol over TCP. No other protocols
| | User Name Password | Yes | |
| | X509 Certificate | Yes | |

## Supported Server Features

The current focus is on the OPC UA Binary protocol over TCP. No other protocols are supported at this point.

| Categories | Features | Supported | Notes |
|----------------|----------------------------------|-----------|-------------|
| Encoding | OPC UA Binary | Yes | |
| | OPC UA JSON | | not planned |
| | OPC UA XML | | not planned |
| Transport | UA-TCP UA-SC UA Binary | Yes | |
| | OPC UA HTTPS | | not planned |
| | SOAP-HTTP WS-SC UA Binary | | not planned |
| | SOAP-HTTP WS-SC UA XML | | not planned |
| | SOAP-HTTP WS-SC UA XML-UA Binary | | not planned |
| Encryption | None | Yes | |
| | Basic128Rsa15 | Untested | |
| | Basic256 | Untested | |
| | Basic256Sha256 | Untested | |
| Authentication | Anonymous | Yes | |
| | User Name Password | Untested | |
| | X509 Certificate | Untested | |


### Services

Here is the current set of supported services. For low-level access use the client `Send` function directly.

| Service Set | Service | Client | Notes |
|-----------------------------|-------------------------------|--------|--------------|
| Discovery Service Set | FindServers | Yes | |
| | FindServersOnNetwork | Yes | |
| | GetEndpoints | Yes | |
| | RegisterServer | | |
| | RegisterServer2 | | |
| Secure Channel Service Set | OpenSecureChannel | Yes | |
| | CloseSecureChannel | Yes | |
| Session Service Set | CreateSession | Yes | |
| | CloseSession | Yes | |
| | ActivateSession | Yes | |
| | Cancel | | |
| Node Management Service Set | AddNodes | | |
| | AddReferences | | |
| | DeleteNodes | | |
| | DeleteReferences | | |
| View Service Set | Browse | Yes | |
| | BrowseNext | Yes | |
| | TranslateBrowsePathsToNodeIds | | |
| | RegisterNodes | Yes | |
| | UnregisterNodes | Yes | |
| Query Service Set | QueryFirst | | |
| | QueryNext | | |
| Attribute Service Set | Read | Yes | |
| | Write | Yes | |
| | HistoryRead | Yes | |
| | HistoryUpdate | | |
| Method Service Set | Call | Yes | |
| MonitoredItems Service Set | CreateMonitoredItems | Yes | |
| | DeleteMonitoredItems | Yes | |
| | ModifyMonitoredItems | Yes | |
| | SetMonitoringMode | Yes | |
| | SetTriggering | | |
| Subscription Service Set | CreateSubscription | Yes | |
| | ModifySubscription | | |
| | SetPublishingMode | | |
| | Publish | Yes | |
| | Republish | | |
| | DeleteSubscriptions | Yes | |
| | TransferSubscriptions | | |

| Service Set | Service | Client | Server | Notes |
|-----------------------------|-------------------------------|--------|--------|--------------|
| Discovery Service Set | FindServers | Yes | | |
| | FindServersOnNetwork | Yes | | |
| | GetEndpoints | Yes | | |
| | RegisterServer | | | |
| | RegisterServer2 | | | |
| Secure Channel Service Set | OpenSecureChannel | Yes | Yes* | |
| | CloseSecureChannel | Yes | Yes* | |
| Session Service Set | CreateSession | Yes | Yes | |
| | CloseSession | Yes | Yes | |
| | ActivateSession | Yes | Yes | |
| | Cancel | | | |
| Node Management Service Set | AddNodes | | | |
| | AddReferences | | | |
| | DeleteNodes | | | |
| | DeleteReferences | | | |
| View Service Set | Browse | Yes | Yes | |
| | BrowseNext | Yes | | |
| | TranslateBrowsePathsToNodeIds | | | |
| | RegisterNodes | Yes | | |
| | UnregisterNodes | Yes | | |
| Query Service Set | QueryFirst | | | |
| | QueryNext | | | |
| Attribute Service Set | Read | Yes | Yes | |
| | Write | Yes | Yes | |
| | HistoryRead | Yes | | |
| | HistoryUpdate | | | |
| Method Service Set | Call | Yes | | |
| MonitoredItems Service Set | CreateMonitoredItems | Yes | Yes | |
| | DeleteMonitoredItems | Yes | Yes | |
| | ModifyMonitoredItems | Yes | Yes | |
| | SetMonitoringMode | Yes | Yes | |
| | SetTriggering | | | |
| Subscription Service Set | CreateSubscription | Yes | Yes | |
| | ModifySubscription | | | |
| | SetPublishingMode | | | |
| | Publish | Yes | Yes | |
| | Republish | | | |
| | DeleteSubscriptions | Yes | Yes | |
| | TransferSubscriptions | | | |

* not all encryption schemes are fully functional at this time


## Authors

Expand Down
38 changes: 19 additions & 19 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ func (c *Client) CreateSession(ctx context.Context, cfg *uasc.SessionConfig) (*S
var s *Session
// for the CreateSessionRequest the authToken is always nil.
// use c.SecureChannel().SendRequest() to enforce this.
err := c.SecureChannel().SendRequest(ctx, req, nil, func(v interface{}) error {
err := c.SecureChannel().SendRequest(ctx, req, nil, func(v ua.Response) error {
var res *ua.CreateSessionResponse
if err := safeAssign(v, &res); err != nil {
return err
Expand Down Expand Up @@ -876,7 +876,7 @@ func (c *Client) ActivateSession(ctx context.Context, s *Session) error {
UserIdentityToken: ua.NewExtensionObject(s.cfg.UserIdentityToken),
UserTokenSignature: s.cfg.UserTokenSignature,
}
return c.SecureChannel().SendRequest(ctx, req, s.resp.AuthenticationToken, func(v interface{}) error {
return c.SecureChannel().SendRequest(ctx, req, s.resp.AuthenticationToken, func(v ua.Response) error {
var res *ua.ActivateSessionResponse
if err := safeAssign(v, &res); err != nil {
return err
Expand Down Expand Up @@ -918,7 +918,7 @@ func (c *Client) closeSession(ctx context.Context, s *Session) error {
}
req := &ua.CloseSessionRequest{DeleteSubscriptions: true}
var res *ua.CloseSessionResponse
return c.Send(ctx, req, func(v interface{}) error {
return c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
}
Expand All @@ -936,7 +936,7 @@ func (c *Client) DetachSession(ctx context.Context) (*Session, error) {
// Send sends the request via the secure channel and registers a handler for
// the response. If the client has an active session it injects the
// authentication token.
func (c *Client) Send(ctx context.Context, req ua.Request, h func(interface{}) error) error {
func (c *Client) Send(ctx context.Context, req ua.Request, h func(ua.Response) error) error {
stats.Client().Add("Send", 1)

err := c.sendWithTimeout(ctx, req, c.cfg.sechan.RequestTimeout, h)
Expand All @@ -948,7 +948,7 @@ func (c *Client) Send(ctx context.Context, req ua.Request, h func(interface{}) e
// sendWithTimeout sends the request via the secure channel with a custom timeout and registers a handler for
// the response. If the client has an active session it injects the
// authentication token.
func (c *Client) sendWithTimeout(ctx context.Context, req ua.Request, timeout time.Duration, h func(interface{}) error) error {
func (c *Client) sendWithTimeout(ctx context.Context, req ua.Request, timeout time.Duration, h uasc.ResponseHandler) error {
sc := c.SecureChannel()
if sc == nil {
return ua.StatusBadServerNotConnected
Expand Down Expand Up @@ -981,7 +981,7 @@ func (c *Client) FindServers(ctx context.Context) (*ua.FindServersResponse, erro
EndpointURL: c.endpointURL,
}
var res *ua.FindServersResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -993,7 +993,7 @@ func (c *Client) FindServersOnNetwork(ctx context.Context) (*ua.FindServersOnNet

req := &ua.FindServersOnNetworkRequest{}
var res *ua.FindServersOnNetworkResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1007,7 +1007,7 @@ func (c *Client) GetEndpoints(ctx context.Context) (*ua.GetEndpointsResponse, er
EndpointURL: c.endpointURL,
}
var res *ua.GetEndpointsResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand Down Expand Up @@ -1046,7 +1046,7 @@ func (c *Client) Read(ctx context.Context, req *ua.ReadRequest) (*ua.ReadRespons
req = cloneReadRequest(req)

var res *ua.ReadResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
err := safeAssign(v, &res)
if err != nil {
return err
Expand Down Expand Up @@ -1077,7 +1077,7 @@ func (c *Client) Write(ctx context.Context, req *ua.WriteRequest) (*ua.WriteResp
stats.Client().Add("NodesToWrite", int64(len(req.NodesToWrite)))

var res *ua.WriteResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand Down Expand Up @@ -1117,7 +1117,7 @@ func (c *Client) Browse(ctx context.Context, req *ua.BrowseRequest) (*ua.BrowseR
req = cloneBrowseRequest(req)

var res *ua.BrowseResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1131,7 +1131,7 @@ func (c *Client) Call(ctx context.Context, req *ua.CallMethodRequest) (*ua.CallM
MethodsToCall: []*ua.CallMethodRequest{req},
}
var res *ua.CallResponse
err := c.Send(ctx, creq, func(v interface{}) error {
err := c.Send(ctx, creq, func(v ua.Response) error {
return safeAssign(v, &res)
})
if err != nil {
Expand All @@ -1148,7 +1148,7 @@ func (c *Client) BrowseNext(ctx context.Context, req *ua.BrowseNextRequest) (*ua
stats.Client().Add("BrowseNext", 1)

var res *ua.BrowseNextResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1162,7 +1162,7 @@ func (c *Client) RegisterNodes(ctx context.Context, req *ua.RegisterNodesRequest
stats.Client().Add("NodesToRegister", int64(len(req.NodesToRegister)))

var res *ua.RegisterNodesResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1176,7 +1176,7 @@ func (c *Client) UnregisterNodes(ctx context.Context, req *ua.UnregisterNodesReq
stats.Client().Add("NodesToUnregister", int64(len(req.NodesToUnregister)))

var res *ua.UnregisterNodesResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1199,7 +1199,7 @@ func (c *Client) HistoryReadEvent(ctx context.Context, nodes []*ua.HistoryReadVa
}

var res *ua.HistoryReadResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1222,7 +1222,7 @@ func (c *Client) HistoryReadRawModified(ctx context.Context, nodes []*ua.History
}

var res *ua.HistoryReadResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1245,7 +1245,7 @@ func (c *Client) HistoryReadProcessed(ctx context.Context, nodes []*ua.HistoryRe
}

var res *ua.HistoryReadResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand All @@ -1268,7 +1268,7 @@ func (c *Client) HistoryReadAtTime(ctx context.Context, nodes []*ua.HistoryReadV
}

var res *ua.HistoryReadResponse
err := c.Send(ctx, req, func(v interface{}) error {
err := c.Send(ctx, req, func(v ua.Response) error {
return safeAssign(v, &res)
})
return res, err
Expand Down
Loading
Loading