| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package opcua |
| |
| import ( |
| "context" |
| "net/url" |
| "regexp" |
| "strconv" |
| |
| "github.com/pkg/errors" |
| "github.com/rs/zerolog" |
| |
| plc4go "github.com/apache/plc4x/plc4go/pkg/api" |
| _default "github.com/apache/plc4x/plc4go/spi/default" |
| "github.com/apache/plc4x/plc4go/spi/options" |
| "github.com/apache/plc4x/plc4go/spi/transports" |
| "github.com/apache/plc4x/plc4go/spi/utils" |
| ) |
| |
| type Driver struct { |
| _default.DefaultDriver |
| awaitSetupComplete bool |
| awaitDisconnectComplete bool |
| |
| uriPattern *regexp.Regexp |
| |
| log zerolog.Logger |
| _options []options.WithOption // Used to pass them downstream |
| } |
| |
| func NewDriver(_options ...options.WithOption) plc4go.PlcDriver { |
| customLogger := options.ExtractCustomLoggerOrDefaultToGlobal(_options...) |
| driver := &Driver{ |
| awaitSetupComplete: true, |
| awaitDisconnectComplete: true, |
| |
| uriPattern: regexp.MustCompile(`^((?P<protocolCode>opcua):)?(?P<transportCode>[a-z0-9]*)?:(//)?(?P<transportHost>[\w.-]+)(:(?P<transportPort>\d*))?(?P<transportEndpoint>[\w/=]*)[\\?]?(?P<paramString>([^\\=]+=[^\\=&]+&?)*)`), |
| |
| log: customLogger, |
| _options: _options, |
| } |
| driver.DefaultDriver = _default.NewDefaultDriver(driver, "opcua", "Opcua", "tcp", NewTagHandler()) |
| return driver |
| } |
| |
| func (d *Driver) GetConnectionWithContext(ctx context.Context, transportUrl url.URL, transports map[string]transports.Transport, driverOptions map[string][]string) <-chan plc4go.PlcConnectionConnectResult { |
| d.log.Debug(). |
| Stringer("transportUrl", &transportUrl). |
| Int("numberTransports", len(transports)). |
| Int("numberDriverOptions", len(driverOptions)). |
| Msg("Get connection for transport url") |
| |
| // Get the transport specified in the url |
| transport, ok := transports[transportUrl.Scheme] |
| if !ok { |
| d.log.Error(). |
| Stringer("transportUrl", &transportUrl). |
| Str("scheme", transportUrl.Scheme). |
| Msg("We couldn't find a transport for scheme") |
| return d.reportError(errors.Errorf("couldn't find transport for given transport url %v", transportUrl)) |
| } |
| |
| // Provide a default-port to the transport, which is used, if the user doesn't provide on in the connection string. |
| driverOptions["defaultTcpPort"] = []string{strconv.FormatUint(uint64(80), 10)} |
| // Have the transport create a new transport-instance. |
| transportInstance, err := transport.CreateTransportInstance( |
| transportUrl, |
| driverOptions, |
| append(d._options, options.WithCustomLogger(d.log))..., |
| ) |
| if err != nil { |
| d.log.Error(). |
| Stringer("transportUrl", &transportUrl). |
| Strs("defaultTcpPort", driverOptions["defaultTcpPort"]). |
| Msg("We couldn't create a transport instance for port") |
| return d.reportError(errors.Wrapf(err, "couldn't initialize transport configuration for given transport url %s", transportUrl.String())) |
| } |
| |
| // Split up the connection string into its individual segments. |
| var protocolCode, transportCode, transportHost, transportPort, transportEndpoint, paramString string |
| if match := utils.GetSubgroupMatches(d.uriPattern, transportUrl.String()); match != nil { |
| protocolCode = match["protocolCode"] |
| if protocolCode == "" { |
| protocolCode = d.GetProtocolCode() |
| } |
| transportCode = match["transportCode"] |
| if transportCode == "" { |
| transportCode = d.GetDefaultTransport() |
| } |
| transportHost = match["transportHost"] |
| transportPort = match["transportPort"] |
| transportEndpoint = match["transportEndpoint"] |
| paramString = match["paramString"] |
| _ = paramString // TODO: not sure if we need that |
| } else { |
| return d.reportError(errors.Errorf("Connection string %s doesn't match the format %s", &transportUrl, d.uriPattern)) |
| } |
| |
| // Check if the protocol code matches this driver. |
| if protocolCode != d.GetProtocolCode() { |
| // Actually this shouldn't happen as the DriverManager should have not used this driver in the first place. |
| return d.reportError(errors.New("This driver is not suited to handle this connection string")) |
| } |
| |
| // Create the configuration object. |
| configuration, err := ParseFromOptions(d.log, driverOptions) |
| if err != nil { |
| return d.reportError(errors.Wrap(err, "can't parse options")) |
| } |
| configuration.Host = transportHost |
| configuration.Port = transportPort |
| configuration.TransportEndpoint = transportEndpoint |
| portAddition := "" |
| if transportPort != "" { |
| portAddition += ":" + transportPort |
| } |
| configuration.Endpoint = "opc." + transportCode + "://" + transportHost + portAddition + "" + transportEndpoint |
| d.log.Debug().Stringer("configuration", &configuration).Msg("working with configuration") |
| |
| if securityPolicy := configuration.SecurityPolicy; securityPolicy != "" && securityPolicy != "None" { |
| d.log.Trace().Str("securityPolicy", securityPolicy).Msg("working with security policy") |
| if err := configuration.openKeyStore(); err != nil { |
| return d.reportError(errors.Wrap(err, "error opening key store")) |
| } |
| } else { |
| d.log.Trace().Msg("no security policy") |
| } |
| |
| driverContext := NewDriverContext(configuration) |
| driverContext.awaitSetupComplete = d.awaitSetupComplete |
| driverContext.awaitDisconnectComplete = d.awaitDisconnectComplete |
| |
| codec := NewMessageCodec( |
| transportInstance, |
| append(d._options, options.WithCustomLogger(d.log))..., |
| ) |
| d.log.Debug().Stringer("codec", codec).Msg("working with codec") |
| |
| // Create the new connection |
| connection := NewConnection( |
| codec, |
| configuration, |
| driverContext, |
| d.GetPlcTagHandler(), |
| driverOptions, |
| append(d._options, options.WithCustomLogger(d.log))..., |
| ) |
| d.log.Debug().Msg("created connection, connecting now") |
| return connection.ConnectWithContext(ctx) |
| } |
| |
| func (d *Driver) SetAwaitSetupComplete(awaitComplete bool) { |
| d.awaitSetupComplete = awaitComplete |
| } |
| |
| func (d *Driver) SetAwaitDisconnectComplete(awaitComplete bool) { |
| d.awaitDisconnectComplete = awaitComplete |
| } |
| |
| func (d *Driver) reportError(err error) <-chan plc4go.PlcConnectionConnectResult { |
| ch := make(chan plc4go.PlcConnectionConnectResult, 1) |
| ch <- _default.NewDefaultPlcConnectionConnectResult(nil, err) |
| return ch |
| } |