gopcua / opcua Goto Github PK
View Code? Open in Web Editor NEWNative Go OPC-UA library
License: MIT License
Native Go OPC-UA library
License: MIT License
Creating UserIdentityToken
manually is a burden.
Add some template to create one easily.
When handling each type of NodeID, the main concern of the user is to retrieve values in Identifier
field.
So, I'm considering to implement Value()
or Payload()
method on each NodeID, which makes the NodeID interface to return the value without asserting.
type NodeID interface {
Serialize() ([]byte, error)
SerializeTo([]byte) error
DecodeFromBytes([]byte) error
Len() int
String() string
EncodingMaskValue() uint8
SetURIFlag()
SetIndexFlag()
Value() ([]byte, error) // Add this line
}
func (n *NumericNodeID) Value() ([]byte, error) {
if n == nil {
return nil, errors.NewErrReceiverNil(n)
}
// return identifier as it is
return n.Identifier, nil
}
func (g *GUIDNodeID) Value() ([]byte, error) {
if n == nil {
return nil, errors.NewErrReceiverNil(g)
}
// return serialized GUID instead of GUID structure
return n.GUID.Serialize()
}
Session creation and activation should be done automatically with smallest user inputs.
Session
which implements net.Conn
interfaceIn specification parameters like UserIdentityToken is described as ExtensibleParameter
, not ExtensionParameter
, while Wireshark calls it ExtensionObject
.
With current implementation, ArraySize
will be encoded as 0xffffffff
when the array contains nothing.
This should be corrected as 0x00000000
.
Example: Array of String (should be applied to some other constructors)
// NewStringArray creates a new StringArray from multiple strings.
func NewStringArray(strs []string) *StringArray {
if strs == nil {
s := &StringArray{
ArraySize: -1, // <= This should be 0.
}
return s
}
s := &StringArray{
ArraySize: int32(len(strs)),
}
for _, ss := range strs {
s.Strings = append(s.Strings, NewString(ss))
}
return s
}
I want to enable two-factor authentication for this org. @wmnsk could you please enable this for your account in https://github.com/settings/security?
Go 1.12 has been released, and we should be compliant with Go Module mechanism.
The OPC/UA binary protocol has no mechanism to express a nil
struct. This means that all struct fields at least represent a Go zero value for that struct. Additionally, the current code generator creates struct fields as pointers which has the effect that all struct fields need to be initialized with at least an empty value.
Here is an example from the ua/write_response_test.go
.
Struct: &WriteResponse{
ResponseHeader: &ResponseHeader{
Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
RequestHandle: 1,
ServiceDiagnostics: &DiagnosticInfo{},
StringTable: []string{},
AdditionalHeader: NewExtensionObject(nil),
},
Results: []StatusCode{StatusOK},
},
This behavior was also the cause for a discrepancy in #162 where a field of the session configuration had not been initialized.
One fix is to make the encoder smart about nil
values. That has the effect that tests can no longer use the same structs for encoding and decoding since a nil
silently turns into an empty struct.
A better approach might be not to generate struct fields as pointers since they cannot be nil
anyway. Then the codec just works.
Hey,
I'm currently trying to implement the ReadRequest
service. I found your service type definitions in services/service.go
.
Where did you find the numbers? I couldn't find them in the spec. I found them here https://open62541.org/doc/open62541-0.3.pdf (page 170) and saw them in Wireshark. How did you know about them?
Best regards,
Mirco
For those who is interested in contribution, we need to provide guideline.
CONTRIBUTING.md
ISSUE_TEMPLATE.md
PULL_REQUEST_TEMPLATE.md
Creating this issue for a place for discussion outside of a PR.
While trying to implement the crypto functions for the server path, I noticed a few functions which didn't work for receiving messages. Primarily, the recv()
and SendAsync()
functions within the uasc package were tailored for sending client messages and not capable of receiving messages. The majority of the code is fine and the problems weren't major, but they do need some adjustment. For example, the SendAsync method would panic while trying to copy the reqHdr
field into a ResponseHeader and the recv method could only handle messages with existing handlers (which are tied to client-generated requestIDs).
I'm preparing a PR for three things:
Within the server logic I'm working on a concept of a 'Channel Broker' to manage multiple secure channels on one listener. With this, I'm hoping to have a method of accepting connections, creating secure channels, and handling requests from multiple connections as well as slightly decoupling the secure channel from the uacp.Conn
. This is because clients are allowed to reconnect to a server provided the secure channel hasn't expired so this should allow for a means to 'reattach' a new Conn
to an existing secure channel. Having a system monitoring all active secure channels will also allow for a means to prune expired connections.
I anticipate client logic remaining the same as it is now; i.e. the application controlling the creation and use of SecureChannels. One thing I'm aware of, but unsure exactly how to implement it at this time, is that clients will also need a way to reconnect to a server utilizing an existing SecureChannel if the uacp.Conn is broken. I don't anticipate this being too difficult (try to send, fail, close the uacp.Conn, open a new one, replace the old conn reference with the new one, continue on). Also need to check the spec to see if the full HEL/ACK sequence needs to be repeated in this case.
#156 was a proof of concept that the crypo functions themselves are functional, but will need more work to allow for both sending and receiving messages, decent secret management, etc. Now that I've looked closer at what it'll take to implement the server component of the logic, I should be able to clean up that sequencing.
Comments are welcome; when I have things cleaned up a bit I'll push a WIP branch for review.
Hi @wmnsk, I had started working on a pure go opcua implementation in April but didn't make much headway until recently but you've beaten me to the punch :)
My code is roughly in the same state (encoder, decoder, uacp, session, ...) but I think having a larger and active community is more important and your project has gained some good traction. I have some experience from that with my own open-source projects.
The one big thing I can contribute is a code generator for all OPC/UA service objects and array types and I'm also thinking of writing a reflection based Encoder/Decoder to reduce the boilerplate even further.
The generated code looks like the one below and also generates all length encoded Array variants of objects like []*ReadValueId
.
Would you be interested in this? If yes, then we can discuss what the interfaces should look like since the generated code isn't a drop-in replacement for the current structures. I'll attach a PR with the code generator so that you can have a look.
package service
import (
"bytes"
ua "github.com/northvolt/go-opcua/datatypes"
)
type CreateSubscriptionRequest struct {
RequestHeader *RequestHeader
RequestedPublishingInterval float64
RequestedLifetimeCount uint32
RequestedMaxKeepAliveCount uint32
MaxNotificationsPerPublish uint32
PublishingEnabled bool
Priority uint8
}
func NewCreateSubscriptionRequest() *CreateSubscriptionRequest {
return &CreateSubscriptionRequest{
RequestHeader: NewRequestHeader(),
}
}
func (m *CreateSubscriptionRequest) ID() uint16 {
return 787
}
func (m *CreateSubscriptionRequest) GetName() string {
return "CreateSubscriptionRequest"
}
func (m *CreateSubscriptionRequest) Read(b *bytes.Buffer) error {
var err error
if m.RequestHeader, err = ReadRequestHeader(b); err != nil {
return err
}
if m.RequestedPublishingInterval, err = ua.ReadDouble(b); err != nil {
return err
}
if m.RequestedLifetimeCount, err = ua.ReadUInt32(b); err != nil {
return err
}
if m.RequestedMaxKeepAliveCount, err = ua.ReadUInt32(b); err != nil {
return err
}
if m.MaxNotificationsPerPublish, err = ua.ReadUInt32(b); err != nil {
return err
}
if m.PublishingEnabled, err = ua.ReadBoolean(b); err != nil {
return err
}
if m.Priority, err = ua.ReadByte(b); err != nil {
return err
}
return nil
}
func (m *CreateSubscriptionRequest) Write(b *bytes.Buffer) error {
if err := WriteRequestHeader(b, m.RequestHeader); err != nil {
return err
}
if err := ua.WriteDouble(b, m.RequestedPublishingInterval); err != nil {
return err
}
if err := ua.WriteUInt32(b, m.RequestedLifetimeCount); err != nil {
return err
}
if err := ua.WriteUInt32(b, m.RequestedMaxKeepAliveCount); err != nil {
return err
}
if err := ua.WriteUInt32(b, m.MaxNotificationsPerPublish); err != nil {
return err
}
if err := ua.WriteBoolean(b, m.PublishingEnabled); err != nil {
return err
}
if err := ua.WriteByte(b, m.Priority); err != nil {
return err
}
return nil
}
func ReadCreateSubscriptionRequest(b *bytes.Buffer) (*CreateSubscriptionRequest, error) {
m := new(CreateSubscriptionRequest)
if err := m.Read(b); err != nil {
return nil, err
}
return m, nil
}
func WriteCreateSubscriptionRequest(b *bytes.Buffer, m *CreateSubscriptionRequest) error {
return m.Write(b)
}
package service
import (
"bytes"
ua "github.com/northvolt/go-opcua/datatypes"
)
type ReadValueIdArray []*ReadValueId
func (m ReadValueIdArray) GetName() string {
return "ReadValueIdArray"
}
func ReadReadValueIdArray(b *bytes.Buffer) ([]*ReadValueId, error) {
n, err := ua.ReadUInt32(b)
if err != nil {
return nil, err
}
if n == ua.NilValue {
return nil, nil
}
m := make([]*ReadValueId, n)
for i := uint32(0); i < n; i++ {
if m[i], err = ReadReadValueId(b); err != nil {
return nil, err
}
}
return m, nil
}
func WriteReadValueIdArray(b *bytes.Buffer, m []*ReadValueId) error {
if m == nil {
return ua.WriteUInt32(b, ua.NilValue)
}
if err := ua.WriteUInt32(b, uint32(len(m))); err != nil {
return err
}
for i := range m {
if err := WriteReadValueId(b, m[i]); err != nil {
return err
}
}
return nil
}
To keep better quality of code without much effort, checker tools are so useful.
This issue is to determine which tool's instruction to follow.
cf. #15
I cannot understand - is x509 Authentication supported or not?
It seems to be presented in code
https://github.com/wmnsk/gopcua/blob/72c87ce6fabe6993089b948be572e56fe94670c5/datatypes/user-identity-token.go#L253
and even tested, but supported features do not mention it as supported.
So I'm confused - was it supported not fully or it's just not working code or it's just a documentation issue?
If it's the last one I think other features should be checked too (maybe more of them supported already).
Thanks!
Currently some of the (non-primitive) datatypes are in services
directory but they should actually be in datatypes
.
This issue is to track the problem. At some point of implementation before v1.0 release, I'll work on moving them and revise some codes.
Hello,
question regards to #130 (comment)
Please could you help me with clarifying "Retrieve values in ReadResponse from server"
Did you mean, getting data from services.DecodeReadResponse(buf) and then getting from the struct of ReadResponse.Result?
I did session.Read() and variant EncodingMask shows the right type from server. But other parameters are &{10 <nil> <nil> <nil> []}
Hi gopcua team,
I really like your implementation in this package.
I would also like to use the browse services and I also need support for some more datatypes of the variant.
I regularly run into this error while decoding the ReadResponse message: cannot decode as uint8: got undefined type
Can you share your timeline? Are there any plans to complete the list of services and available datatypes?
Thanks
Felix
This is just a reminder to use https://github.com/dvyukov/go-fuzz to test our parsers before releasing version 1.0.
I've pushed a WIP branch for the first kick at a server implementation. The focus on this initial code is to show the uacp
package changes that need to occur to support listening for unsolicited messages and how that integrates into both the client and the server code. I anticipate these changes being the first to be merged, then the server code can follow once it's fleshed out.
The primary difference is that I moved the goroutine read loop out of the uasc package and into each of the client and server packages. The uasc package still deals with decoding/decrypting the messages, but returns the service up to the application layer. The exception to this are the Open and Close SecureChannel requests; those are dealt with directly in the uasc package without notifying the client/server.
The server implementation has preliminary support for opening and closing secure channels, auto-builds endpoints, and opening/closing/activating sessions. Authorization has config for the purpose of endpoint generation, but is otherwise not implemented yet. The session management is still in a very early state and should be normalized with the client implementation (they're currently totally separate). Other services are just placeholders for now. To properly implement further services, the server needs to implement an address space, which is what I've started looking into next.
Parameters in constructors in each Service should be given as a defined types instead of respective values in the types.
e.g., Pass SignatureData
instead of alg
and sig
in CreateSession.
Now the tests for each service in service_test.go
is becoming too big. Need to split tests for each service and write more efficient tests.
I've run across an issue when trying to close and create another SecureChannel inside a client. After I call Close() on the first instance of a SecureChannel, the program quickly panics due to a nil pointer deference inside func (s *SecureChannel) monitor(ctx context.Context)
.
I think the issue is this goroutine stays blocked on
n, err := s.lowerConn.Read(s.rcvBuf)
so it never sees that the context finishes and returns cleanly. Inside the call to close, s.lowerConn gets assigned to nil, resulting in the panic.
Simply commenting out the lowerConn nil assignment in SecureChannel.close() stops this, but I haven't looked to see if there are other impacts of doing so.
Currently no tests implemented for networking feature which communicates with other OPC UA entity.
Those funcs should not be left untested, so I need to find the way to test it in simple and CI-friendly manner.
Currently certificates are handled as []byte, but it should actually be ApplicationInstanceCertificate.
Need to add ApplicationInstanceCertificate as described in Part4, 7.2.
Conn
-related tests sometimes fail because new test case starts before the resources(like binded address) in previous test is not freed yet.
https://circleci.com/gh/wmnsk/gopcua/482
This is a great talk https://www.youtube.com/watch?v=pUaFW98V1Sc. The topic is very similar to what we're building. He even talks about go-fuzz.
One thing that came to mind. Our interface names are Decode
and Serialize
. He's using Marshal
and Unmarshal
like the json package https://golang.org/pkg/encoding/json/ and the proto package https://godoc.org/github.com/golang/protobuf/proto.
Therefore I'd like to propose changing our interface before releasing version 1.
Hey,
I tried adding some stuff to the wiki. Looks like it's not activated or I'm not allowed to use it. Could you change that?
Today I experimented with gopcua in the browser. I have a working example compiling it to webassembly and decoding raw messages in the browser.
The idea is to use gopcua in the browser and communicate with OPC UA servers that support websockets.
In the current implementation, params inside RequestHeader
in each service is given respectively, but it should be given with *RequestHeader
to be more flexible and consice in handling services with constructor.
For example,
func NewActivateSessionRequest(ts time.Time, authToken datatypes.NodeID, handle, diag, timeout uint32, auditID string, sig *SignatureData, certs []*SignedSoftwareCertificate, locales []string, userToken *datatypes.ExtensionObject, tokenSig *SignatureData) *ActivateSessionRequest {
should be;
func NewActivateSessionRequest(reqHeader *RequestHeader, sig *SignatureData, certs []*SignedSoftwareCertificate, locales []string, userToken *datatypes.ExtensionObject, tokenSig *SignatureData) *ActivateSessionRequest {
In OPC UA Session, client should set AuthenticationToken told by server, and the type of data varies within NodeId.
In current implementation it is hard-coded in some fixed types like FourByteNodeID. It should be NodeID interface to be able to set any type of NodeId given from server.
I was tring to connect to an Siemens S7, using the datetime example I was unable to create a session..
the error returned was StatusBadSessionIDInvalid
.
so logging the tcp packets with wireshark and matching the results with a working client I noticed that the message CreateSessionRequest
has a
opcua.RequestedSessionTimeout = 0
so the S7 replied with
aopcua.RevisedSessionTimeout=1
when at next step we call ActivateSession
the session is already expired.
in the source code client.go
I noticed that in
func (c *Client) CreateSession
the RequestedSessionTimeout
of the struct ua.CreateSessionRequest
is not set so it has 0 value.
for me worked adding RequestedSessionTimeout: cfg.SessionTimeout
req := &ua.CreateSessionRequest{
ClientDescription: cfg.ClientDescription,
EndpointURL: c.endpointURL,
SessionName: fmt.Sprintf("gopcua-%d", time.Now().UnixNano()),
ClientNonce: nonce,
ClientCertificate: c.cfg.Certificate,
RequestedSessionTimeout: cfg.SessionTimeout,
}
other algorithms are to be in another issue.
Hi!
I'm trying to connect from prosys opcua client (osx) to your example/server (with my changes of course).
Have an issue while connect:
2018/10/01 00:30:44 main.go:79: D Successfully established connection with 172.21.0.1:60570
2018/10/01 00:30:44 main.go:89: D Successfully opened secure channel with 172.21.0.1:60570
2018/10/01 00:30:44 main.go:95: E ReadService error: unsupported uint16: unsupported or not implemented yet.
Any Ideas?
Ready to collaborate.
Before first release, we need to make README.md more friendly for users.
Work in progress in PR #30.
In #170 @maurocordioli referenced a test case from the OPC/UA compliance test suite. I think it would be useful to have a framework for writing tests for this suite. Since we don't have a server yet we can pick a reference implementation and run it in a Docker container or as a subprocess.
I think having a framework for writing tests is more important than test coverage at this point. If we have a framework then others can help filling in the gaps. It would also be useful if we can swap out the server implementation essentially to run integration tests but I don't know how the whole OPC/UA certification process works and whether it matters.
@dwhutchison since you wanted to start working on a server as well we could use this as a testbed for these tests.
What do you all think? Who has experience with this?
By default from sources all would be installed
https://opcuastack.readthedocs.io/en/latest/getting_started/installation.html
to
~/.ASNeG
, which would require from user to do something like
export PATH=$HOME/.ASNeG/usr/bin:$PATH
export LD_LIBRARY_PATH=$HOME/.ASNeG/usr/lib:$LD_LIBRARY_PATH
and it still would not really help, because when doing
https://opcuastack.readthedocs.io/en/latest/getting_started/hello_world.html
user would get the
CMake Error at CMakeLists.txt:82 (include):
include could not find load file:
/usr/share/cmake/OpcUaStack3/CMakeOpenSSL.txt
So either detailed explanation how to not get rid of these problems should be included in documentation (what to add to .bashrc, how to configure currently opened shell, etc) or default path should be /
instead ~/.ASNeG
. It seems that application is designed to be installed to root...
Hi,
I tried to implement the new version you released in my current project here
https://github.com/felix-lessoer/Machinebeat/blob/v2/beater/client.go
The problem is:
After changing the implementation to use your new client implementation I got no value from ReadResponse anymore.
Trying to get ReadResponse.Results[].Value always leads into nil.
I'm using this public server to test:
endpoint: "opc.tcp://opcuaserver.com:48010"
Node:
id: "AirConditioner_1.Temperature"
namespace: 3
How do you expect to build the ReadRequest?
`
node = client.Node(nodeID)
rh := ua.RequestHeader{}
rv := ua.ReadValueID{}
rv.NodeID = nodeID
rv.AttributeID = 0
rv.IndexRange = ""
qn, err := node.BrowseName()
if err != nil {
return nil, err
}
rv.DataEncoding = qn
rr := ua.ReadRequest{}
rr.MaxAge = 2000
rr.NodesToRead = []*ua.ReadValueID{&rv}
rr.TimestampsToReturn = ua.TimestampsToReturnBoth
rr.RequestHeader = &rh
m, err := client.Read(&rr)
if err != nil {
return nil, err
}
value, status := handleReadResponse(m)
`
Custom errors in errors
should not be too many.
e.g. implement ErrDecoding instead of ErrTooShortToDecode and make the message more flexible.
when we config our opcua server and run examples as default config, we should change fllow func, we should delete :
opt = AuthPolicyID("Anonymous")
opt(c.cfg, c.sessionCfg)
if not, the server will return : The user identity token is not valid.
func NewClient(endpoint string, opts ...Option) *Client {
c := &Client{
endpointURL: endpoint,
cfg: DefaultClientConfig(),
sessionCfg: DefaultSessionConfig(),
}
for _, opt := range opts {
opt(c.cfg, c.sessionCfg)
}
// UserIdentityToken was removed from DefaultSessionConfig() so ensure a default still is set
if c.sessionCfg.UserIdentityToken == nil {
opt := AuthAnonymous()
opt(c.cfg, c.sessionCfg)
opt = AuthPolicyID("Anonymous")
opt(c.cfg, c.sessionCfg)
}
return c
}
When crosscompiling for a 32 bit environment, I get the following two issues:
.../src/github.com/gopcua/opcua/ua/node_id.go:133:22: constant 4294967295 overflows int
.../src/github.com/gopcua/opcua/ua/node_id.go:250:10: constant 4294967295 overflows int
The first issue arises since strconv.Atoi
a few lines earlier returns an int
which in my system is 32 bit and thus cannot be compared to an uint32
. Using ParseUint
instead solved the problem:
id, err := strconv.ParseUint(idval[2:], 10, 32)
The second issue is similar, but for that I made an uglier solution to get it to work by changing the input argument of SetIntID to an uint32
and fiddling a bit with casting etc in the code below.
This relates to commit b9119dc of ua/node_id.go
@wmnsk can we drop these stale branches:
In SecureChannel and Session handling, large part of the required parameters depends on each Config, which is to be created and passed by users.
It causes crash if the parameters in each Config are not properly set. Thus the parameters should be validated when opening SecureChannel/Creating Session and we should notify users of the invalid parameters.
Hi I've been looking at your code recently, but I'm not sure how you read server data with client, and I haven't found the location to set the identifier, identifier type, and Namespace Index parameters, can you tell me?
After transferred the repo CCI has not been working.
Unfollow/Stop Building => Setup again did not resolve the issue. Maybe pushing something refleshes it?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.