diff --git a/LICENSE b/LICENSE index 564d7622ee8..a168ddb2caf 100644 --- a/LICENSE +++ b/LICENSE @@ -221,8 +221,3 @@ build-utils/protocol-base-mspec/src/main/antlr4/org/apache/plc4x/plugins/codegen is licensed under the Category A: "UNLICENSE" which is available here: UNLICENSE file in the licenses directory. -The files: -plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java -plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java -plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java -are derivative works of the Eclipse Milo project and are licensed under the EPL 2.0 license. diff --git a/NOTICE b/NOTICE index 91529ec3249..fae53f7315b 100644 --- a/NOTICE +++ b/NOTICE @@ -11,5 +11,3 @@ The Netty project (https://netty.io/). ---------------------------------------------- -This product includes software developed at -The Eclipse Milo project (https://projects.eclipse.org/projects/iot.milo/) diff --git a/plc4j/integrations/apache-kafka/pom.xml b/plc4j/integrations/apache-kafka/pom.xml index 7dc6405ab50..d836c4979fc 100644 --- a/plc4j/integrations/apache-kafka/pom.xml +++ b/plc4j/integrations/apache-kafka/pom.xml @@ -266,6 +266,11 @@ runtime + + org.apache.commons + commons-lang3 + + + org.bouncycastle bcmail-jdk15on @@ -208,7 +203,6 @@ org.slf4j slf4j-simple - commons-io commons-io @@ -219,6 +213,11 @@ vavr + + org.apache.commons + commons-lang3 + + @@ -278,7 +277,6 @@ org.slf4j:slf4j-simple - org.bouncycastle:bcpkix-jdk15on org.bouncycastle:bcmail-jdk15on org.apache.plc4x:plc4j-driver-ab-eth diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java deleted file mode 100644 index 4e4d4a7688d..00000000000 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2019 the Eclipse Milo Authors - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ - -package org.apache.plc4x.java.opcuaserver; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Set; -import java.util.UUID; -import java.util.regex.Pattern; - -import com.google.common.collect.Sets; -import org.apache.plc4x.java.opcuaserver.configuration.Configuration; -import org.apache.plc4x.java.opcuaserver.configuration.PasswordConfiguration; -import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class KeyStoreLoader { - - private static final Pattern IP_ADDR_PATTERN = Pattern.compile( - "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final String certificateFileName = "plc4x-opcuaserver.pfx"; - - private X509Certificate[] serverCertificateChain; - private X509Certificate serverCertificate; - private KeyPair serverKeyPair; - - private Configuration config; - private PasswordConfiguration passwordConfig; - - public KeyStoreLoader(Configuration config, PasswordConfiguration passwordConfig, Boolean interactive) { - this.config = config; - this.passwordConfig = passwordConfig; - - File securityTempDir = new File(config.getDir(), "security"); - if (!securityTempDir.exists() && !securityTempDir.mkdirs()) { - logger.error("Unable to create directory please confirm folder permissions on " + securityTempDir.toString()); - System.exit(1); - } - logger.info("security dir: {}", securityTempDir.getAbsolutePath()); - - try { - load(securityTempDir, interactive); - } catch (Exception e) { - logger.error("Error loading the key store " + e); - System.exit(1); - } - } - - public KeyStoreLoader load(File baseDir, boolean interactive) throws Exception { - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - - File serverKeyStore = baseDir.toPath().resolve(certificateFileName).toFile(); - - if (!serverKeyStore.exists()) { - if (!interactive) { - logger.info("Please re-run with the -i switch to setup the security certificate key store"); - System.exit(1); - } - - logger.info("Creating keystore at {}", serverKeyStore); - keyStore.load(null, passwordConfig.getSecurityPassword().toCharArray()); - - logger.info("Creating self signed certiciate {}", serverKeyStore); - KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); - - String applicationUri = "urn:eclipse:milo:plc4x:server" + UUID.randomUUID(); - - SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair) - .setCommonName(applicationUri) - .setOrganization("org.apache") - .setOrganizationalUnit("plc4x") - .setLocalityName("Wakefield") - .setStateName("MA") - .setCountryCode("US") - .setApplicationUri(applicationUri); - - // Get as many hostnames and IP addresses as we can listed in the certificate. - Set hostnames = Sets.union( - Sets.newHashSet(HostnameUtil.getHostname()), - HostnameUtil.getHostnames("0.0.0.0", false) - ); - - logger.info("using IP address/hostnames {}", hostnames.toString()); - - for (String hostname : hostnames) { - if (IP_ADDR_PATTERN.matcher(hostname).matches()) { - builder.addIpAddress(hostname); - } else { - builder.addDnsName(hostname); - } - } - - X509Certificate certificate = builder.build(); - - keyStore.setKeyEntry(config.getName(), keyPair.getPrivate(), passwordConfig.getSecurityPassword().toCharArray(), new X509Certificate[]{certificate}); - keyStore.store(new FileOutputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); - - logger.info("Self signed certificate created. Replace {} and update config file passwords if not using a signed certificate.", serverKeyStore); - - } else { - logger.info("Loading KeyStore at {}", serverKeyStore); - keyStore.load(new FileInputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); - } - - Key serverPrivateKey = keyStore.getKey(config.getName(), passwordConfig.getSecurityPassword().toCharArray()); - if (serverPrivateKey instanceof PrivateKey) { - serverCertificate = (X509Certificate) keyStore.getCertificate(config.getName()); - - serverCertificateChain = Arrays.stream(keyStore.getCertificateChain(config.getName())) - .map(X509Certificate.class::cast) - .toArray(X509Certificate[]::new); - - PublicKey serverPublicKey = serverCertificate.getPublicKey(); - serverKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey); - } - - return this; - } - - X509Certificate getServerCertificate() { - return serverCertificate; - } - - public X509Certificate[] getServerCertificateChain() { - return serverCertificateChain; - } - - KeyPair getServerKeyPair() { - return serverKeyPair; - } - -} diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java index e8c66d1f241..e3cc2b08e89 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java @@ -1,27 +1,33 @@ /* - * Copyright (c) 2019 the Eclipse Milo Authors + * 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 * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ + * http://www.apache.org/licenses/LICENSE-2.0 * - * SPDX-License-Identifier: EPL-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 org.apache.plc4x.java.opcuaserver; -import java.io.File; -import java.security.KeyPair; -import java.security.Security; +import java.io.*; +import java.net.InetAddress; +import java.security.*; import java.security.cert.X509Certificate; -import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.io.IOException; -import java.io.Console; import java.nio.file.Path; import java.nio.file.FileSystems; @@ -29,13 +35,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.apache.plc4x.java.opcuaserver.context.CertificateKeyPair; +import org.apache.plc4x.java.opcuaserver.context.CertificateGenerator; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig; import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator; import org.eclipse.milo.opcua.sdk.server.identity.UsernameIdentityValidator; import org.eclipse.milo.opcua.sdk.server.identity.X509IdentityValidator; -import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; import org.eclipse.milo.opcua.stack.core.StatusCodes; import org.eclipse.milo.opcua.stack.core.UaRuntimeException; import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager; @@ -47,15 +54,11 @@ import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode; import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo; import org.eclipse.milo.opcua.stack.core.util.CertificateUtil; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedHttpsCertificateBuilder; import org.eclipse.milo.opcua.stack.server.EndpointConfiguration; import org.eclipse.milo.opcua.stack.server.security.DefaultServerCertificateValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.google.common.collect.Lists.newArrayList; -import org.apache.commons.lang3.RandomStringUtils; import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS; import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME; import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_X509; @@ -194,40 +197,32 @@ private void readCommandLineArgs(String[] args) { } public static void main(String[] args) throws Exception { - OPCUAServer server = new OPCUAServer(args); - server.startup().get(); - final CompletableFuture future = new CompletableFuture<>(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> future.complete(null))); + OPCUAServer serverInit = new OPCUAServer(args); + serverInit.getServer().startup().get(); + CompletableFuture future = new CompletableFuture<>(); future.get(); } private final OpcUaServer server; private final Plc4xNamespace plc4xNamespace; + private final String certificateFileName = "plc4x-opcuaserver.pfx"; public OPCUAServer(String[] args) throws Exception { readCommandLineArgs(args); - KeyStoreLoader loader = new KeyStoreLoader(config, passwordConfig, cmd.hasOption("interactive")); - - DefaultCertificateManager certificateManager = new DefaultCertificateManager( - loader.getServerKeyPair(), - loader.getServerCertificateChain() - ); + File securityTempDir = new File(config.getDir(), "security"); + if (!securityTempDir.exists() && !securityTempDir.mkdirs()) { + logger.error("Unable to create directory please confirm folder permissions on " + securityTempDir.toString()); + System.exit(1); + } + logger.info("Security Directory is: {}", securityTempDir.getAbsolutePath()); // File pkiDir = FileSystems.getDefault().getPath(config.getDir()).resolve("pki").toFile(); DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); - logger.info("pki dir: {}", pkiDir.getAbsolutePath()); - - DefaultServerCertificateValidator certificateValidator = - new DefaultServerCertificateValidator(trustListManager); - - KeyPair httpsKeyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); + logger.info("Certificate directory is: {}, Please move certificates from the reject dir to the trusted directory to allow encrypted access", pkiDir.getAbsolutePath()); - SelfSignedHttpsCertificateBuilder httpsCertificateBuilder = new SelfSignedHttpsCertificateBuilder(httpsKeyPair); - httpsCertificateBuilder.setCommonName(HostnameUtil.getHostname()); - HostnameUtil.getHostnames("0.0.0.0").forEach(httpsCertificateBuilder::addDnsName); - X509Certificate httpsCertificate = httpsCertificateBuilder.build(); + DefaultServerCertificateValidator certificateValidator = new DefaultServerCertificateValidator(trustListManager); UsernameIdentityValidator identityValidator = new UsernameIdentityValidator( true, @@ -240,22 +235,113 @@ public OPCUAServer(String[] args) throws Exception { } ); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + + File serverKeyStore = securityTempDir.toPath().resolve(certificateFileName).toFile(); + X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true); - // If you need to use multiple certificates you'll have to be smarter than this. - X509Certificate certificate = certificateManager.getCertificates() - .stream() - .findFirst() - .orElseThrow(() -> new UaRuntimeException(StatusCodes.Bad_ConfigurationError, "no certificate found")); + CertificateKeyPair certificate = null; + if (!serverKeyStore.exists()) { + if (!cmd.hasOption("interactive")) { + logger.info("Please re-run with the -i switch to setup the security certificate key store"); + System.exit(1); + } + certificate = CertificateGenerator.generateCertificate(); + logger.info("Creating new KeyStore at {}", serverKeyStore); + keyStore.load(null, passwordConfig.getSecurityPassword().toCharArray()); + keyStore.setKeyEntry("plc4x-certificate-alias", certificate.getKeyPair().getPrivate(), passwordConfig.getSecurityPassword().toCharArray(), new X509Certificate[] { certificate.getCertificate() }); + keyStore.store(new FileOutputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); + } else { + logger.info("Loading KeyStore at {}", serverKeyStore); + keyStore.load(new FileInputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); + String alias = keyStore.aliases().nextElement(); + KeyPair kp = new KeyPair(keyStore.getCertificate(alias).getPublicKey(), + (PrivateKey) keyStore.getKey(alias, passwordConfig.getSecurityPassword().toCharArray())); + certificate = new CertificateKeyPair(kp,(X509Certificate) keyStore.getCertificate(alias)); + } - // The configured application URI must match the one in the certificate(s) String applicationUri = CertificateUtil - .getSanUri(certificate) + .getSanUri(certificate.getCertificate()) .orElseThrow(() -> new UaRuntimeException( StatusCodes.Bad_ConfigurationError, "certificate is missing the application URI")); - Set endpointConfigurations = createEndpointConfigurations(certificate); + Set endpointConfigurations = new LinkedHashSet<>(); + + String hostname = InetAddress.getLocalHost().getHostName(); + + EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() + .setBindAddress("0.0.0.0") + .setHostname(hostname) + .setPath("/plc4x") + .setCertificate(certificate.getCertificate()) + .setBindPort(config.getTcpPort()) + .setSecurityMode(MessageSecurityMode.None) + .addTokenPolicies( + USER_TOKEN_POLICY_ANONYMOUS, + USER_TOKEN_POLICY_USERNAME, + USER_TOKEN_POLICY_X509); + + endpointConfigurations.add( + builder.copy() + .setSecurityPolicy(SecurityPolicy.Basic256Sha256) + .setSecurityMode(MessageSecurityMode.SignAndEncrypt) + .build() + ); + + endpointConfigurations.add( + builder.copy() + .setHostname("127.0.0.1") + .setSecurityPolicy(SecurityPolicy.Basic256Sha256) + .setSecurityMode(MessageSecurityMode.SignAndEncrypt) + .build() + ); + + EndpointConfiguration.Builder discoveryBuilder = builder.copy() + .setPath("/discovery") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(discoveryBuilder.build()); + + EndpointConfiguration.Builder discoveryLocalBuilder = builder.copy() + .setPath("/discovery") + .setHostname("127.0.0.1") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(discoveryLocalBuilder.build()); + + EndpointConfiguration.Builder discoveryLocalPlc4xBuilder = builder.copy() + .setPath("/plc4x/discovery") + .setHostname("127.0.0.1") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(discoveryLocalPlc4xBuilder.build()); + + if (!config.getDisableInsecureEndpoint()) { + EndpointConfiguration.Builder noSecurityBuilder = builder.copy() + .setSecurityPolicy(SecurityPolicy.None) + .setTransportProfile(TransportProfile.TCP_UASC_UABINARY); + endpointConfigurations.add(noSecurityBuilder.build()); + } + + //Always add an unsecured endpoint to localhost, this is a work around for Milo throwing an exception if it isn't here. + EndpointConfiguration.Builder noSecurityBuilder = builder.copy() + .setSecurityPolicy(SecurityPolicy.None) + .setHostname("127.0.0.1") + .setTransportProfile(TransportProfile.TCP_UASC_UABINARY) + .setSecurityMode(MessageSecurityMode.None); + endpointConfigurations.add(noSecurityBuilder.build()); + + DefaultCertificateManager certificateManager = new DefaultCertificateManager( + certificate.getKeyPair(), + Arrays.stream(keyStore.getCertificateChain(keyStore.getCertificateAlias(certificate.getCertificate())))// Added so that existing certificates are loaded on startup + .map(X509Certificate.class::cast) + .toArray(X509Certificate[]::new) + ); OpcUaServerConfig serverConfig = OpcUaServerConfig.builder() .setApplicationUri(applicationUri) @@ -271,8 +357,6 @@ public OPCUAServer(String[] args) throws Exception { .setCertificateManager(certificateManager) .setTrustListManager(trustListManager) .setCertificateValidator(certificateValidator) - .setHttpsKeyPair(httpsKeyPair) - .setHttpsCertificate(httpsCertificate) .setIdentityValidator(new CompositeValidator(identityValidator, x509IdentityValidator)) .setProductUri("urn:eclipse:milo:plc4x:server") .build(); @@ -283,90 +367,6 @@ public OPCUAServer(String[] args) throws Exception { plc4xNamespace.startup(); } - private Set createEndpointConfigurations(X509Certificate certificate) { - Set endpointConfigurations = new LinkedHashSet<>(); - - List bindAddresses = newArrayList(); - bindAddresses.add("0.0.0.0"); - - List localAddresses = new ArrayList<>(bindAddresses); - - Set hostnames = new LinkedHashSet<>(); - hostnames.add(HostnameUtil.getHostname()); - hostnames.addAll(HostnameUtil.getHostnames("0.0.0.0")); - - for (String bindAddress : bindAddresses) { - for (String hostname : hostnames) { - EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() - .setBindAddress(bindAddress) - .setHostname(hostname) - .setPath("/plc4x") - .setCertificate(certificate) - .addTokenPolicies( - USER_TOKEN_POLICY_ANONYMOUS, - USER_TOKEN_POLICY_USERNAME, - USER_TOKEN_POLICY_X509); - - - if (!config.getDisableInsecureEndpoint()) { - EndpointConfiguration.Builder noSecurityBuilder = builder.copy() - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); - endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder)); - } else { - //Always add an unsecured endpoint to localhost, this is a work around for Milo throughing an exception if it isn't here. - if (hostname.equals("127.0.0.1")) { - EndpointConfiguration.Builder noSecurityBuilder = builder.copy() - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); - endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder)); - } - } - - // TCP Basic256Sha256 / SignAndEncrypt - endpointConfigurations.add(buildTcpEndpoint( - builder.copy() - .setSecurityPolicy(SecurityPolicy.Basic256Sha256) - .setSecurityMode(MessageSecurityMode.SignAndEncrypt)) - ); - - // HTTPS Basic256Sha256 / Sign (SignAndEncrypt not allowed for HTTPS) - endpointConfigurations.add(buildHttpsEndpoint( - builder.copy() - .setSecurityPolicy(SecurityPolicy.Basic256Sha256) - .setSecurityMode(MessageSecurityMode.Sign)) - ); - - EndpointConfiguration.Builder discoveryBuilder = builder.copy() - .setPath("/discovery") - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - - - endpointConfigurations.add(buildTcpEndpoint(discoveryBuilder)); - endpointConfigurations.add(buildHttpsEndpoint(discoveryBuilder)); - } - } - - return endpointConfigurations; - } - - private EndpointConfiguration buildTcpEndpoint(EndpointConfiguration.Builder base) { - return base.copy() - .setTransportProfile(TransportProfile.TCP_UASC_UABINARY) - .setBindPort(config.getTcpPort()) - .build(); - } - - private EndpointConfiguration buildHttpsEndpoint(EndpointConfiguration.Builder base) { - return base.copy() - .setTransportProfile(TransportProfile.HTTPS_UABINARY) - .setBindPort(config.getHttpPort()) - .build(); - } - public OpcUaServer getServer() { return server; } @@ -377,7 +377,6 @@ public CompletableFuture startup() { public CompletableFuture shutdown() { plc4xNamespace.shutdown(); - return server.shutdown(); } diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java index 9e32867f49e..6740d45e3ee 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java @@ -19,60 +19,19 @@ package org.apache.plc4x.java.opcuaserver.backend; - import java.lang.reflect.Array; -import java.util.List; import java.util.Arrays; -import java.util.Random; -import java.util.UUID; - -import org.eclipse.milo.opcua.sdk.core.AccessLevel; -import org.eclipse.milo.opcua.sdk.core.Reference; -import org.eclipse.milo.opcua.sdk.core.ValueRank; -import org.eclipse.milo.opcua.sdk.core.ValueRanks; -import org.eclipse.milo.opcua.sdk.server.Lifecycle; -import org.eclipse.milo.opcua.sdk.server.OpcUaServer; + +import org.eclipse.milo.opcua.sdk.server.AbstractLifecycle; import org.eclipse.milo.opcua.sdk.server.api.DataItem; -import org.eclipse.milo.opcua.sdk.server.api.DataTypeDictionaryManager; -import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle; -import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.BaseEventTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemTypeNode; import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilterContext; -import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectTypeNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode; -import org.eclipse.milo.opcua.sdk.server.nodes.factories.NodeFactory; -import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilters; -import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel; -import org.eclipse.milo.opcua.stack.core.AttributeId; -import org.eclipse.milo.opcua.stack.core.BuiltinDataType; import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.UaException; -import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; -import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; -import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; -import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; -import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName; import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; -import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement; -import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; -import org.eclipse.milo.opcua.stack.core.types.enumerated.StructureType; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumField; -import org.eclipse.milo.opcua.stack.core.types.structured.Range; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureField; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.ULong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,15 +40,14 @@ import org.apache.plc4x.java.api.messages.PlcReadRequest; import org.apache.plc4x.java.api.messages.PlcReadResponse; import org.apache.plc4x.java.api.messages.PlcWriteRequest; -import org.apache.plc4x.java.api.messages.PlcWriteResponse; + import org.apache.plc4x.java.api.types.PlcResponseCode; -import org.apache.plc4x.java.api.value.PlcValue; + import org.apache.plc4x.java.utils.connectionpool.*; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; -import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; + import org.apache.plc4x.java.api.model.PlcField; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; @@ -98,26 +56,43 @@ import java.math.BigInteger; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ulong; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort; -public class Plc4xCommunication { + +public class Plc4xCommunication extends AbstractLifecycle { private PlcDriverManager driverManager; private final Logger logger = LoggerFactory.getLogger(getClass()); private final Integer DEFAULT_TIMEOUT = 1000000; private final Integer DEFAULT_RETRY_BACKOFF = 5000; + private final DataValue BAD_RESPONSE = new DataValue(new Variant(null), StatusCode.BAD); private Map failedConnectionList = new HashMap<>(); Map monitoredList = new HashMap<>(); public Plc4xCommunication () { + + } + + @Override + protected void onStartup() { driverManager = new PooledPlcDriverManager(); } + @Override + protected void onShutdown() { + //Do Nothing + } + + public PlcDriverManager getDriverManager() { + return driverManager; + } + + public void setDriverManager(PlcDriverManager driverManager) { + this.driverManager = driverManager; + } + public PlcField getField(String tag, String connectionString) throws PlcConnectionException { return driverManager.getDriver(connectionString).prepareField(tag); } @@ -196,92 +171,109 @@ public static NodeId getNodeId(String plcValue) { public DataValue getValue(AttributeFilterContext.GetAttributeContext ctx, String tag, String connectionString) { PlcConnection connection = null; - DataValue resp = new DataValue(new Variant(null), StatusCode.BAD); - - //Check if we just polled the connection and it failed. Wait for the backoff counter to expire before we try again. - if (failedConnectionList.containsKey(connectionString)) { - if (System.currentTimeMillis() > failedConnectionList.get(connectionString) + DEFAULT_RETRY_BACKOFF) { - failedConnectionList.remove(connectionString); - } else { - logger.debug("Waiting for back off timer - " + ((failedConnectionList.get(connectionString) + DEFAULT_RETRY_BACKOFF) - System.currentTimeMillis()) + " ms left"); - return resp; - } - } - - //Try to connect to PLC try { - connection = driverManager.getConnection(connectionString); - logger.debug(connectionString + " Connected"); - } catch (PlcConnectionException e) { - logger.error("Failed to connect to device, error raised - " + e); - failedConnectionList.put(connectionString, System.currentTimeMillis()); - return resp; - } - if (!connection.getMetadata().canRead()) { - logger.error("This connection doesn't support reading."); + //Check if we just polled the connection and it failed. Wait for the backoff counter to expire before we try again. + if (failedConnectionList.containsKey(connectionString)) { + if (System.currentTimeMillis() > failedConnectionList.get(connectionString) + DEFAULT_RETRY_BACKOFF) { + failedConnectionList.remove(connectionString); + } else { + logger.debug("Waiting for back off timer - " + ((failedConnectionList.get(connectionString) + DEFAULT_RETRY_BACKOFF) - System.currentTimeMillis()) + " ms left"); + return BAD_RESPONSE; + } + } + + //Try to connect to PLC try { - connection.close(); - } catch (Exception exception) { - logger.warn("Closing connection failed with error - " + exception); + connection = driverManager.getConnection(connectionString); + logger.debug(connectionString + " Connected"); + } catch (PlcConnectionException e) { + logger.error("Failed to connect to device, error raised - " + e); + failedConnectionList.put(connectionString, System.currentTimeMillis()); + return BAD_RESPONSE; } - return resp; - } - long timeout = DEFAULT_TIMEOUT; - if (monitoredList.containsKey(ctx.getNode().getNodeId())) { - timeout = (long) monitoredList.get(ctx.getNode().getNodeId()).getSamplingInterval()*1000; - } + if (!connection.getMetadata().canRead()) { + logger.error("This connection doesn't support reading."); + try { + connection.close(); + } catch (Exception exception) { + logger.warn("Closing connection failed with error - " + exception); + } + return BAD_RESPONSE; + } - // Create a new read request: - // - Give the single item requested an alias name - PlcReadRequest.Builder builder = connection.readRequestBuilder(); - builder.addItem("value-1", tag); - PlcReadRequest readRequest = builder.build(); + long timeout = DEFAULT_TIMEOUT; + if (monitoredList.containsKey(ctx.getNode().getNodeId())) { + timeout = (long) monitoredList.get(ctx.getNode().getNodeId()).getSamplingInterval() * 1000; + } - PlcReadResponse response = null; - try { - response = readRequest.execute().get(timeout, TimeUnit.MICROSECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.warn(e + " Occurred while reading value, using timeout of " + timeout/1000 + "ms"); + // Create a new read request: + // - Give the single item requested an alias name + PlcReadRequest.Builder builder = connection.readRequestBuilder(); + builder.addItem("value-1", tag); + PlcReadRequest readRequest = builder.build(); + + PlcReadResponse response = null; try { - connection.close(); - } catch (Exception exception) { - logger.warn("Closing connection failed with error - " + exception); + response = readRequest.execute().get(timeout, TimeUnit.MICROSECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + logger.warn(e + " Occurred while reading value, using timeout of " + timeout / 1000 + "ms"); + try { + connection.close(); + } catch (Exception exception) { + logger.warn("Closing connection failed with error - " + exception); + } + return BAD_RESPONSE; } - return resp; - } - - for (String fieldName : response.getFieldNames()) { - if(response.getResponseCode(fieldName) == PlcResponseCode.OK) { - int numValues = response.getNumberOfValues(fieldName); - if(numValues == 1) { - if (response.getObject(fieldName) instanceof BigInteger) { - resp = new DataValue(new Variant(ulong((BigInteger) response.getObject(fieldName))), StatusCode.GOOD); - } else { - resp = new DataValue(new Variant(response.getObject(fieldName)), StatusCode.GOOD); - } - } else { - Object array = Array.newInstance(response.getObject(fieldName, 0).getClass(), numValues); - for (int i = 0; i < numValues; i++) { - if (response.getObject(fieldName, i) instanceof BigInteger) { - Array.set(array, i, ulong((BigInteger) response.getObject(fieldName, i))); + DataValue resp = BAD_RESPONSE; + for (String fieldName : response.getFieldNames()) { + if (response.getResponseCode(fieldName) == PlcResponseCode.OK) { + int numValues = response.getNumberOfValues(fieldName); + if (numValues == 1) { + if (response.getObject(fieldName) instanceof BigInteger) { + resp = new DataValue(new Variant(ulong((BigInteger) response.getObject(fieldName))), StatusCode.GOOD); + } else { + resp = new DataValue(new Variant(response.getObject(fieldName)), StatusCode.GOOD); + } } else { - Array.set(array, i, response.getObject(fieldName, i)); + Object array = null; + if (response.getObject(fieldName, 0) instanceof BigInteger) { + array = Array.newInstance(ULong.class, numValues); + } else { + array = Array.newInstance(response.getObject(fieldName, 0).getClass(), numValues); + } + for (int i = 0; i < numValues; i++) { + if (response.getObject(fieldName, i) instanceof BigInteger) { + Array.set(array, i, ulong((BigInteger) response.getObject(fieldName, i))); + } else { + Array.set(array, i, response.getObject(fieldName, i)); + } + } + resp = new DataValue(new Variant(array), StatusCode.GOOD); } } - resp = new DataValue(new Variant(array), StatusCode.GOOD); - } - } - } + } - try { - connection.close(); + try { + connection.close(); + } catch (Exception e) { + failedConnectionList.put(connectionString, System.currentTimeMillis()); + logger.warn("Closing connection failed with error " + e); + } + + return resp; } catch (Exception e) { - failedConnectionList.put(connectionString, System.currentTimeMillis()); - logger.warn("Closing connection failed with error " + e); + logger.warn("General error reading value " + e.getStackTrace()[0].toString()); + if (connection != null) { + try { + connection.close(); + } catch (Exception ex) { + //Do Nothing + } + } + return BAD_RESPONSE; } - return resp; } public void setValue(String tag, String value, String connectionString) { diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java index 536022905cc..53c3be98d92 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java @@ -1,150 +1,104 @@ /* - * Copyright (c) 2019 the Eclipse Milo Authors + * 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 * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ + * http://www.apache.org/licenses/LICENSE-2.0 * - * SPDX-License-Identifier: EPL-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 org.apache.plc4x.java.opcuaserver.backend; -import org.apache.plc4x.java.opcuaserver.*; - import java.lang.reflect.Array; import java.util.Arrays; import java.util.List; -import java.util.Random; -import java.util.UUID; import org.apache.plc4x.java.opcuaserver.configuration.Configuration; import org.apache.plc4x.java.opcuaserver.configuration.DeviceConfiguration; import org.apache.plc4x.java.opcuaserver.configuration.Tag; +import org.apache.plc4x.java.utils.connectionpool.PooledPlcDriverManager; import org.eclipse.milo.opcua.sdk.core.AccessLevel; import org.eclipse.milo.opcua.sdk.core.Reference; import org.eclipse.milo.opcua.sdk.core.ValueRank; -import org.eclipse.milo.opcua.sdk.core.ValueRanks; -import org.eclipse.milo.opcua.sdk.server.Lifecycle; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; import org.eclipse.milo.opcua.sdk.server.api.DataItem; import org.eclipse.milo.opcua.sdk.server.api.DataTypeDictionaryManager; import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle; import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.BaseEventTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemTypeNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectTypeNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode; -import org.eclipse.milo.opcua.sdk.server.nodes.factories.NodeFactory; import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilters; import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel; -import org.eclipse.milo.opcua.stack.core.AttributeId; -import org.eclipse.milo.opcua.stack.core.BuiltinDataType; import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.UaException; -import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; -import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; -import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; -import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName; -import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; -import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement; import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; -import org.eclipse.milo.opcua.stack.core.types.enumerated.StructureType; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumField; -import org.eclipse.milo.opcua.stack.core.types.structured.Range; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.plc4x.java.api.model.PlcField; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; - -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte; import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ulong; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort; + public class Plc4xNamespace extends ManagedNamespaceWithLifecycle { - public static final String NAMESPACE_URI = "urn:eclipse:milo:plc4x:server"; + public static final String APPLICATIONID = "urn:eclipse:milo:plc4x:server"; private Configuration config; - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private volatile Thread eventThread; - private volatile boolean keepPostingEvents = true; - - private final Random random = new Random(); - private final DataTypeDictionaryManager dictionaryManager; - private final SubscriptionModel subscriptionModel; - private Plc4xCommunication plc4xServer; public Plc4xNamespace(OpcUaServer server, Configuration c) { - super(server, NAMESPACE_URI); - + super(server, APPLICATIONID); this.config = c; subscriptionModel = new SubscriptionModel(server, this); - dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), NAMESPACE_URI); - + dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), APPLICATIONID); plc4xServer = new Plc4xCommunication(); - getLifecycleManager().addLifecycle(dictionaryManager); getLifecycleManager().addLifecycle(subscriptionModel); - + getLifecycleManager().addLifecycle(plc4xServer); getLifecycleManager().addStartupTask(this::addNodes); } private void addNodes() { for (DeviceConfiguration c: config.getDevices()) { - createAndAddNodes(c); - } - } + NodeId folderNodeId = newNodeId(c.getName()); - private void createAndAddNodes(DeviceConfiguration c) { + UaFolderNode folderNode = new UaFolderNode( + getNodeContext(), + folderNodeId, + newQualifiedName(c.getName()), + LocalizedText.english(c.getName()) + ); - NodeId folderNodeId = newNodeId(c.getName()); + getNodeManager().addNode(folderNode); - UaFolderNode folderNode = new UaFolderNode( - getNodeContext(), - folderNodeId, - newQualifiedName(c.getName()), - LocalizedText.english(c.getName()) - ); + folderNode.addReference(new Reference( + folderNode.getNodeId(), + Identifiers.Organizes, + Identifiers.ObjectsFolder.expanded(), + false + )); - getNodeManager().addNode(folderNode); - - // Make sure our new folder shows up under the server's Objects folder. - folderNode.addReference(new Reference( - folderNode.getNodeId(), - Identifiers.Organizes, - Identifiers.ObjectsFolder.expanded(), - false - )); - - addDynamicNodes(folderNode, c); + addConfiguredNodes(folderNode, c); + } } - private void addDynamicNodes(UaFolderNode rootNode, DeviceConfiguration c) { + private void addConfiguredNodes(UaFolderNode rootNode, DeviceConfiguration c) { final List tags = c.getTags(); final String connectionString = c.getConnectionString(); for (int i = 0; i < tags.size(); i++) { @@ -229,6 +183,11 @@ private void addDynamicNodes(UaFolderNode rootNode, DeviceConfiguration c) { public void onDataItemsCreated(List dataItems) { for (DataItem item : dataItems) { plc4xServer.addField(item); + + if (plc4xServer.getDriverManager() == null) { + plc4xServer.removeField(item); + plc4xServer.setDriverManager(new PooledPlcDriverManager()); + } } subscriptionModel.onDataItemsCreated(dataItems); diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java index 854e8c02ef1..88efb2d64a2 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java @@ -47,9 +47,6 @@ public class Configuration { @JsonProperty private Integer tcpPort = 12686; - @JsonProperty - private Integer httpPort = 8443; - public Configuration() { } @@ -73,10 +70,6 @@ public Integer getTcpPort() { return tcpPort; } - public Integer getHttpPort() { - return httpPort; - } - public List getDevices() { return devices; } diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateGenerator.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateGenerator.java new file mode 100644 index 00000000000..56151fb8c6b --- /dev/null +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateGenerator.java @@ -0,0 +1,122 @@ +/* + * 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 + * + * http://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 org.apache.plc4x.java.opcuaserver.context; + +import org.apache.commons.lang3.RandomUtils; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.security.*; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +public class CertificateGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(CertificateGenerator.class); + private static final String APPURI = "urn:eclipse:milo:plc4x:server"; + + public static CertificateKeyPair generateCertificate() { + KeyPairGenerator kpg = null; + try { + kpg = KeyPairGenerator.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("Security Algorithim is unsupported for certificate"); + } + kpg.initialize(2048); + KeyPair caKeys = kpg.generateKeyPair(); + KeyPair userKeys = kpg.generateKeyPair(); + + X500NameBuilder nameBuilder = new X500NameBuilder(); + + nameBuilder.addRDN(BCStyle.CN, "Apache PLC4X Driver Client"); + nameBuilder.addRDN(BCStyle.O, "Apache Software Foundation"); + nameBuilder.addRDN(BCStyle.OU, "dev"); + nameBuilder.addRDN(BCStyle.L, ""); + nameBuilder.addRDN(BCStyle.ST, "DE"); + nameBuilder.addRDN(BCStyle.C, "US"); + + BigInteger serial = new BigInteger(RandomUtils.nextBytes(40)); + + final Calendar calender = Calendar.getInstance(); + calender.add(Calendar.DATE, -1); + Date startDate = calender.getTime(); + calender.add(Calendar.DATE, 365*25); + Date expiryDate = calender.getTime(); + + KeyPairGenerator generator = null; + try { + generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048, new SecureRandom()); + KeyPair keyPair = generator.generateKeyPair(); + + SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance( + keyPair.getPublic().getEncoded() + ); + + X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder( + nameBuilder.build(), + serial, + startDate, + expiryDate, + Locale.ENGLISH, + nameBuilder.build(), + subjectPublicKeyInfo + ); + + GeneralName[] gnArray = new GeneralName[] {new GeneralName(GeneralName.dNSName, InetAddress.getLocalHost().getHostName()), new GeneralName(GeneralName.uniformResourceIdentifier, APPURI)}; + + + GeneralNames subjectAltNames = GeneralNames.getInstance(new DERSequence(gnArray)); + certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); + + ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(keyPair.getPrivate()); + + X509CertificateHolder certificateHolder = certificateBuilder.build(sigGen); + + JcaX509CertificateConverter certificateConvertor = new JcaX509CertificateConverter(); + certificateConvertor.setProvider(new BouncyCastleProvider()); + + CertificateKeyPair ckp = new CertificateKeyPair(keyPair, certificateConvertor.getCertificate(certificateHolder)); + + return ckp; + + } catch (Exception e) { + LOGGER.error("Security Algorithim is unsupported for certificate"); + return null; + } + } +} diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java new file mode 100644 index 00000000000..0aa3a7065e9 --- /dev/null +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java @@ -0,0 +1,38 @@ +/* + * 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 + * + * http://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 org.apache.plc4x.java.opcuaserver.context; + +import java.security.KeyPair; +import java.security.cert.X509Certificate; + +public class CertificateKeyPair { + + private final KeyPair keyPair; + private final X509Certificate certificate; + + public CertificateKeyPair(KeyPair keyPair, X509Certificate certificate) { + this.keyPair = keyPair; + this.certificate = certificate; + } + + public KeyPair getKeyPair() { return keyPair; } + + public X509Certificate getCertificate() { return certificate; } +} diff --git a/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java b/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java index 256f35f7ad9..71f0f05eed9 100644 --- a/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java +++ b/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java @@ -27,51 +27,46 @@ Licensed to the Apache Software Foundation (ASF) under one import org.apache.plc4x.java.api.messages.PlcWriteRequest; import org.apache.plc4x.java.api.messages.PlcWriteResponse; import org.apache.plc4x.java.api.types.PlcResponseCode; -import org.apache.plc4x.java.opcua.connection.OpcuaTcpPlcConnection; import org.junit.jupiter.api.*; import java.math.BigInteger; -import java.util.HashSet; -import java.util.Set; + import org.apache.commons.io.FileUtils; import java.io.File; -import static org.apache.plc4x.java.opcua.OpcuaPlcDriver.INET_ADDRESS_PATTERN; -import static org.apache.plc4x.java.opcua.OpcuaPlcDriver.OPCUA_URI_PATTERN; -import static org.apache.plc4x.java.opcuaserver.UtilsTest.assertMatching; import static org.assertj.core.api.Assertions.fail; /** */ public class OpcuaPlcDriverTest { // Read only variables of milo example server of version 3.6 - private static final String BOOL_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_BOOL"; - private static final String BYTE_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_BYTE"; - private static final String DOUBLE_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_LREAL"; - private static final String FLOAT_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_REAL"; - private static final String INT16_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_INT"; - private static final String INT32_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_DINT"; - private static final String INT64_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_LINT"; - private static final String INTEGER_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_DINT"; - private static final String SBYTE_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_USINT"; - private static final String STRING_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_STRING"; - private static final String UINT16_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_UINT"; - private static final String UINT32_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_UDINT"; - private static final String UINT64_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_ULINT"; - private static final String UINTEGER_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_UDINT"; - private static final String DOES_NOT_EXIST_IDENTIFIER_READ_WRITE = "ns=2;i=12512623"; + private static final String BOOL_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_BOOL"; + private static final String BYTE_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_BYTE"; + private static final String DOUBLE_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_LREAL"; + private static final String FLOAT_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_REAL"; + private static final String INT16_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_INT"; + private static final String INT32_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_DINT"; + private static final String INT64_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_LINT"; + private static final String INTEGER_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_DINT"; + private static final String SBYTE_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_USINT"; + private static final String STRING_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_STRING"; + private static final String UINT16_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_UINT"; + private static final String UINT32_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_UDINT"; + private static final String UINT64_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_ULINT"; + private static final String UINTEGER_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_UDINT"; + private static final String DOES_NOT_EXIST_IDENTIFIER_READ_WRITE = "ns=1;i=12512623"; // At the moment not used in PLC4X or in the OPC UA driver - private static final String BYTE_STRING_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/ByteString"; - private static final String DATE_TIME_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/DateTime"; - private static final String DURATION_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Duration"; - private static final String GUID_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Guid"; - private static final String LOCALISED_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/LocalizedText"; - private static final String NODE_ID_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/NodeId"; - private static final String QUALIFIED_NAM_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/QualifiedName"; - private static final String UTC_TIME_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/UtcTime"; - private static final String VARIANT_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Variant"; - private static final String XML_ELEMENT_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/XmlElement"; + private static final String BYTE_STRING_IDENTIFIER_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/ByteString"; + private static final String DATE_TIME_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/DateTime"; + private static final String DURATION_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/Duration"; + private static final String GUID_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/Guid"; + private static final String LOCALISED_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/LocalizedText"; + private static final String NODE_ID_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/NodeId"; + private static final String QUALIFIED_NAM_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/QualifiedName"; + private static final String UTC_TIME_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/UtcTime"; + private static final String VARIANT_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/Variant"; + private static final String XML_ELEMENT_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/XmlElement"; // Address of local milo server private String miloLocalAddress = "127.0.0.1:12673/plc4x"; //Tcp pattern of OPC UA @@ -113,7 +108,6 @@ public static void tearDown() { @Test public void connectionNoParams(){ - connectionStringValidSet.forEach(connectionAddress -> { String connectionString = connectionAddress; try { @@ -153,8 +147,8 @@ public void connectionWithDiscoveryParam(){ } @Test - public void readVariables() { - try { + public void readVariables() throws Exception{ + PlcConnection opcuaConnection = new PlcDriverManager().getConnection(tcpConnectionAddress); assert opcuaConnection.isConnected(); @@ -198,14 +192,12 @@ public void readVariables() { opcuaConnection.close(); assert !opcuaConnection.isConnected(); - } catch (Exception e) { - fail("Exception during readVariables Test EXCEPTION: " + e.getMessage()); - } + } @Test - public void writeVariables() { - try { + public void writeVariables() throws Exception { + PlcConnection opcuaConnection = new PlcDriverManager().getConnection(tcpConnectionAddress); assert opcuaConnection.isConnected(); @@ -218,7 +210,7 @@ public void writeVariables() { builder.addItem("Int32", INT32_IDENTIFIER_READ_WRITE, 42); builder.addItem("Int64", INT64_IDENTIFIER_READ_WRITE, 42L); builder.addItem("Integer", INTEGER_IDENTIFIER_READ_WRITE, 42); - builder.addItem("SByte", SBYTE_IDENTIFIER_READ_WRITE + ":SINT", -100); + builder.addItem("SByte", SBYTE_IDENTIFIER_READ_WRITE + ":USINT", 100); builder.addItem("String", STRING_IDENTIFIER_READ_WRITE, "Helllo Toddy!"); builder.addItem("UInt16", UINT16_IDENTIFIER_READ_WRITE + ":UINT", 65535); builder.addItem("UInt32", UINT32_IDENTIFIER_READ_WRITE + ":UDINT", 100); @@ -248,9 +240,6 @@ public void writeVariables() { opcuaConnection.close(); assert !opcuaConnection.isConnected(); - } catch (Exception e) { - fail("Exception during writeVariables Test EXCEPTION: " + e.getMessage()); - } } } diff --git a/plc4j/integrations/opcua-server/src/test/resources/config.yml b/plc4j/integrations/opcua-server/src/test/resources/config.yml index d0b0cd983de..c60002e7e96 100644 --- a/plc4j/integrations/opcua-server/src/test/resources/config.yml +++ b/plc4j/integrations/opcua-server/src/test/resources/config.yml @@ -21,7 +21,6 @@ dir: "target/test-tmp/" name: Plc4x.OPC.UA.Server disableInsecureEndpoint: true tcpPort: 12673 -httpPort: 8445 devices: - name: "Simulated Device" connectionString: "simulated://127.0.0.1"