This is an automated email from the ASF dual-hosted git repository. chrisdutz pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 1e0eaaf2c3bcacd976658a87ed4b3497d687226d Author: Christofer Dutz <[email protected]> AuthorDate: Tue Jun 23 19:14:10 2026 +0200 feat: Updated the plc4x server and driver to operate via TLS and to support authentication. --- RELEASE_NOTES | 16 +++ plc4j/drivers/plc4x/pom.xml | 5 + .../java/plc4x/readwrite/Plc4xAuthRequest.java | 149 +++++++++++++++++++++ .../java/plc4x/readwrite/Plc4xAuthResponse.java | 106 +++++++++++++++ .../plc4x/java/plc4x/readwrite/Plc4xMessage.java | 6 +- .../java/plc4x/readwrite/Plc4xRequestType.java | 6 +- .../apache/plc4x/java/plc4x/Plc4xConnection.java | 45 +++++++ .../org/apache/plc4x/java/plc4x/Plc4xDriver.java | 11 +- .../apache/plc4x/java/plc4x/Plc4xMessageCodec.java | 29 ++++ .../java/plc4x/config/Plc4xConfiguration.java | 24 ++++ .../config/Plc4xTlsTransportConfiguration.java | 31 +++++ .../main/resources/protocols/plc4x/v0/plc4x.mspec | 11 ++ 12 files changed, 435 insertions(+), 4 deletions(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 704970b275..8742246cdd 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -12,12 +12,24 @@ New Features notifications. - Added a new PlcCertificateAuthentication to the API module. - Initial version of a new Java UMAS driver. +- The 'plc4x' proxy driver now supports TLS as a transport and + requires mandatory username/password authentication + (configured via the new "username" and "password" connection + parameters). Incompatible changes -------------------- - Dropped support for Java 11, new baseline Java version is Java 21. +- The 'plc4x' proxy driver now defaults to the TLS transport + instead of plaintext TCP. Existing plaintext connections must + switch to an explicit transport prefix (e.g. "plc4x:tcp://..."). + When using TLS against a server with a self-signed certificate, + set "tls.verify-ssl=false" (or pin the certificate). +- The 'plc4x' proxy driver now requires username/password + authentication on connect; connecting without credentials, or + with invalid ones, is rejected with an ACCESS_DENIED handshake. - The PLC4J-API module however is intentionally held at Java 17 to allow alternate driver implementations to support Java 17. @@ -34,6 +46,10 @@ Incompatible changes Bug Fixes --------- +- Fixed serialization in the 'plc4x' proxy driver's message + codec, which did not configure the buffer integer/string + encodings under SPI3 and failed to serialize any message. + ============================================================== Apache PLC4X 0.13.1 ============================================================== diff --git a/plc4j/drivers/plc4x/pom.xml b/plc4j/drivers/plc4x/pom.xml index 645aa7f014..cd88da10e5 100644 --- a/plc4j/drivers/plc4x/pom.xml +++ b/plc4j/drivers/plc4x/pom.xml @@ -130,6 +130,11 @@ <artifactId>plc4j-transports-tcp</artifactId> <version>0.14.0-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-transports-tls</artifactId> + <version>0.14.0-SNAPSHOT</version> + </dependency> <dependency> <groupId>org.apache.plc4x</groupId> <artifactId>plc4j-utils-audit-log-api</artifactId> diff --git a/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xAuthRequest.java b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xAuthRequest.java new file mode 100644 index 0000000000..14c578b093 --- /dev/null +++ b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xAuthRequest.java @@ -0,0 +1,149 @@ +// 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 org.apache.plc4x.java.plc4x.readwrite; + +import org.apache.plc4x.java.spi.buffers.api.Message; +import org.apache.plc4x.java.spi.buffers.api.ReadBuffer; +import org.apache.plc4x.java.spi.buffers.api.WithOption; +import org.apache.plc4x.java.spi.buffers.api.WriteBuffer; +import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException; +import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption; +import org.apache.plc4x.java.spi.fields.data.reader.DataReaderFactory; +import org.apache.plc4x.java.spi.fields.data.writer.DataWriterFactory; +import org.apache.plc4x.java.spi.fields.fields.reader.FieldReaderFactory; +import org.apache.plc4x.java.spi.fields.fields.writer.FieldWriterFactory; +import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper; +import org.apache.plc4x.java.spi.utils.StaticHelper; + +/** + * Code generated by code-generation. DO NOT EDIT. + */ +public class Plc4xAuthRequest extends Plc4xMessage implements Message { + protected final String username; + + protected final String password; + + public Plc4xAuthRequest(Integer requestId, String username, String password) { + super(requestId); + this.username = username; + this.password = password; + } + + /** + * Discriminator field requestType + */ + @Override + public Plc4xRequestType getRequestType() { + return Plc4xRequestType.AUTH_REQUEST; + } + + /** + * Property field username + */ + public String getUsername() { + return username; + } + + /** + * Property field password + */ + public String getPassword() { + return password; + } + + public static Plc4xMessageBuilder staticParsePlc4xMessageBuilder(ReadBuffer readBuffer) throws + BufferException { + readBuffer.pushContext(WithOption.WithName("Plc4xAuthRequest"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + int startPos = readBuffer.getPositionInBits(); + boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get(); + // Implicit Field: usernameLen + short usernameLen = FieldReaderFactory.readImplicitField(DataReaderFactory.readUnsignedShort(readBuffer, 8), WithOption.WithName("usernameLen"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + // Simple Field: username + String username = FieldReaderFactory.readSimpleField(DataReaderFactory.readString(readBuffer, (usernameLen) * (8)), WithOption.WithName("username"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + // Implicit Field: passwordLen + short passwordLen = FieldReaderFactory.readImplicitField(DataReaderFactory.readUnsignedShort(readBuffer, 8), WithOption.WithName("passwordLen"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + // Simple Field: password + String password = FieldReaderFactory.readSimpleField(DataReaderFactory.readString(readBuffer, (passwordLen) * (8)), WithOption.WithName("password"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + readBuffer.popContext(); + return new Plc4xMessageBuilderImpl(username, password); + } + + protected void serializePlc4xMessageChild(WriteBuffer writeBuffer) throws BufferException { + writeBuffer.pushContext(WithOption.WithName("Plc4xAuthRequest"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + int startPos = writeBuffer.getPositionInBits(); + boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get(); + // Implicit Field: usernameLen + short usernameLen = (short) (StaticHelper.STR_LEN(username)); + FieldWriterFactory.writeImplicitField((short) usernameLen, DataWriterFactory.writeUnsignedShort(writeBuffer, 8), WithOption.WithName("usernameLen"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + // Simple Field: username + FieldWriterFactory.writeSimpleField((String) username, DataWriterFactory.writeString(writeBuffer, (usernameLen) * (8)), WithOption.WithName("username"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + // Implicit Field: passwordLen + short passwordLen = (short) (StaticHelper.STR_LEN(password)); + FieldWriterFactory.writeImplicitField((short) passwordLen, DataWriterFactory.writeUnsignedShort(writeBuffer, 8), WithOption.WithName("passwordLen"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + // Simple Field: password + FieldWriterFactory.writeSimpleField((String) password, DataWriterFactory.writeString(writeBuffer, (passwordLen) * (8)), WithOption.WithName("password"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + writeBuffer.popContext(); + } + + @Override + public int getLengthInBytes() { + return (int) Math.ceil((float) getLengthInBits() / 8.0); + } + + @Override + public int getLengthInBits() { + int lengthInBits = super.getLengthInBits(); + Plc4xAuthRequest _value = this; + boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get(); + // Implicit Field: usernameLen + lengthInBits += 8; + + // Simple Field: username + lengthInBits += (StaticHelper.STR_LEN(username)) * (8); + + // Implicit Field: passwordLen + lengthInBits += 8; + + // Simple Field: password + lengthInBits += (StaticHelper.STR_LEN(password)) * (8); + + return lengthInBits; + } + + public static class Plc4xMessageBuilderImpl implements Plc4xMessage.Plc4xMessageBuilder { + private final String username; + + private final String password; + + public Plc4xMessageBuilderImpl(String username, String password) { + this.username = username; + this.password = password; + } + + public Plc4xMessage build(int requestId) { + return new Plc4xAuthRequest(requestId, username, password); + } + } +} diff --git a/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xAuthResponse.java b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xAuthResponse.java new file mode 100644 index 0000000000..44d2049b31 --- /dev/null +++ b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xAuthResponse.java @@ -0,0 +1,106 @@ +// 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 org.apache.plc4x.java.plc4x.readwrite; + +import org.apache.plc4x.java.spi.buffers.api.Message; +import org.apache.plc4x.java.spi.buffers.api.ReadBuffer; +import org.apache.plc4x.java.spi.buffers.api.WithOption; +import org.apache.plc4x.java.spi.buffers.api.WriteBuffer; +import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException; +import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption; +import org.apache.plc4x.java.spi.fields.data.reader.DataReaderFactory; +import org.apache.plc4x.java.spi.fields.data.writer.DataWriterFactory; +import org.apache.plc4x.java.spi.fields.fields.reader.FieldReaderFactory; +import org.apache.plc4x.java.spi.fields.fields.writer.FieldWriterFactory; +import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper; + +/** + * Code generated by code-generation. DO NOT EDIT. + */ +public class Plc4xAuthResponse extends Plc4xMessage implements Message { + protected final Plc4xResponseCode responseCode; + + public Plc4xAuthResponse(Integer requestId, Plc4xResponseCode responseCode) { + super(requestId); + this.responseCode = responseCode; + } + + /** + * Discriminator field requestType + */ + @Override + public Plc4xRequestType getRequestType() { + return Plc4xRequestType.AUTH_RESPONSE; + } + + /** + * Property field responseCode + */ + public Plc4xResponseCode getResponseCode() { + return responseCode; + } + + public static Plc4xMessageBuilder staticParsePlc4xMessageBuilder(ReadBuffer readBuffer) throws + BufferException { + readBuffer.pushContext(WithOption.WithName("Plc4xAuthResponse"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + int startPos = readBuffer.getPositionInBits(); + boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get(); + // Simple Field (enum): responseCode + Plc4xResponseCode responseCode = FieldReaderFactory.readEnumField(DataReaderFactory.readEnum(Plc4xResponseCode::enumForValue, DataReaderFactory.readUnsignedShort(readBuffer, 8)), WithOption.WithName("responseCode"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + readBuffer.popContext(); + return new Plc4xMessageBuilderImpl(responseCode); + } + + protected void serializePlc4xMessageChild(WriteBuffer writeBuffer) throws BufferException { + writeBuffer.pushContext(WithOption.WithName("Plc4xAuthResponse"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + int startPos = writeBuffer.getPositionInBits(); + boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get(); + // Simple Field (enum): responseCode + FieldWriterFactory.writeSimpleEnumField((Plc4xResponseCode) responseCode, DataWriterFactory.writeEnum(Plc4xResponseCode::getValue, Plc4xResponseCode::name, DataWriterFactory.writeUnsignedShort(writeBuffer, 8)), WithOption.WithName("responseCode"), WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + + writeBuffer.popContext(); + } + + @Override + public int getLengthInBytes() { + return (int) Math.ceil((float) getLengthInBits() / 8.0); + } + + @Override + public int getLengthInBits() { + int lengthInBits = super.getLengthInBits(); + Plc4xAuthResponse _value = this; + boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get(); + // Simple Field: responseCode + lengthInBits += 8; + + return lengthInBits; + } + + public static class Plc4xMessageBuilderImpl implements Plc4xMessage.Plc4xMessageBuilder { + private final Plc4xResponseCode responseCode; + + public Plc4xMessageBuilderImpl(Plc4xResponseCode responseCode) { + this.responseCode = responseCode; + } + + public Plc4xMessage build(int requestId) { + return new Plc4xAuthResponse(requestId, responseCode); + } + } +} diff --git a/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xMessage.java b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xMessage.java index c83f078ac2..8318fcb663 100644 --- a/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xMessage.java +++ b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xMessage.java @@ -81,7 +81,11 @@ public abstract class Plc4xMessage implements Message { // Switch Field Plc4xMessageBuilder builder = null; - if (EvaluationHelper.equals(requestType, (Plc4xRequestType) (Plc4xRequestType.CONNECT_REQUEST))) { + if (EvaluationHelper.equals(requestType, (Plc4xRequestType) (Plc4xRequestType.AUTH_REQUEST))) { + builder = Plc4xAuthRequest.staticParsePlc4xMessageBuilder(readBuffer); + } else if (EvaluationHelper.equals(requestType, (Plc4xRequestType) (Plc4xRequestType.AUTH_RESPONSE))) { + builder = Plc4xAuthResponse.staticParsePlc4xMessageBuilder(readBuffer); + } else if (EvaluationHelper.equals(requestType, (Plc4xRequestType) (Plc4xRequestType.CONNECT_REQUEST))) { builder = Plc4xConnectRequest.staticParsePlc4xMessageBuilder(readBuffer); } else if (EvaluationHelper.equals(requestType, (Plc4xRequestType) (Plc4xRequestType.CONNECT_RESPONSE))) { builder = Plc4xConnectResponse.staticParsePlc4xMessageBuilder(readBuffer); diff --git a/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xRequestType.java b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xRequestType.java index a19acef92b..8945109cb4 100644 --- a/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xRequestType.java +++ b/plc4j/drivers/plc4x/src/main/generated/org/apache/plc4x/java/plc4x/readwrite/Plc4xRequestType.java @@ -45,7 +45,11 @@ public enum Plc4xRequestType { UNSUBSCRIPTION_REQUEST((short) 0x0B), - UNSUBSCRIPTION_RESPONSE((short) 0x0C); + UNSUBSCRIPTION_RESPONSE((short) 0x0C), + + AUTH_REQUEST((short) 0x0D), + + AUTH_RESPONSE((short) 0x0E); private static final Map<Short, Plc4xRequestType> map; diff --git a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xConnection.java b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xConnection.java index 089b88cb04..597cb0b51d 100644 --- a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xConnection.java +++ b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xConnection.java @@ -27,6 +27,8 @@ 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.plc4x.config.Plc4xConfiguration; +import org.apache.plc4x.java.plc4x.readwrite.Plc4xAuthRequest; +import org.apache.plc4x.java.plc4x.readwrite.Plc4xAuthResponse; import org.apache.plc4x.java.plc4x.readwrite.Plc4xConnectRequest; import org.apache.plc4x.java.plc4x.readwrite.Plc4xConnectResponse; import org.apache.plc4x.java.plc4x.readwrite.Plc4xMessage; @@ -121,6 +123,10 @@ public class Plc4xConnection extends ConnectionBase<Plc4xConfiguration> { } }); + // Authenticate first. The proxy mandates username/password auth; no operation is + // permitted until this exchange succeeds. We never log the credentials. + authenticate(); + // Open the underlying proxied connection. int requestId = txIdGenerator.getAndIncrement(); CompletableFuture<Plc4xMessage> future = registerPending(requestId); @@ -147,6 +153,45 @@ public class Plc4xConnection extends ConnectionBase<Plc4xConfiguration> { } } + /** + * Performs the mandatory username/password handshake with the proxy. Throws if the + * server rejects the credentials or the exchange does not complete in time. Credentials + * are taken from the connection configuration and are never logged. + */ + private void authenticate() throws PlcConnectionException { + int requestId = txIdGenerator.getAndIncrement(); + CompletableFuture<Plc4xMessage> future = registerPending(requestId); + String username = configuration.getUsername(); + String password = configuration.getPassword(); + if (username == null || password == null) { + pendingResponses.remove(requestId); + throw new PlcConnectionException( + "Username and password are required to connect to a PLC4X proxy server"); + } + try { + messageCodec.send(new Plc4xAuthRequest(requestId, username, password)); + } catch (MessageCodecException e) { + pendingResponses.remove(requestId); + throw new PlcConnectionException("Failed to send proxy authentication request", e); + } + try { + Plc4xMessage response = future + .orTimeout(configuration.getRequestTimeout(), TimeUnit.MILLISECONDS) + .get(); + if (!(response instanceof Plc4xAuthResponse authResponse)) { + throw new PlcConnectionException("Unexpected response to proxy authentication: " + response); + } + if (authResponse.getResponseCode() != Plc4xResponseCode.OK) { + throw new PlcConnectionException( + "Authentication against PLC4X proxy server failed: " + authResponse.getResponseCode()); + } + } catch (PlcConnectionException e) { + throw e; + } catch (Exception e) { + throw new PlcConnectionException("Error during proxy authentication", e); + } + } + @Override public void close() throws Exception { handshakeComplete = false; diff --git a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xDriver.java b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xDriver.java index 0a0c7b7d3b..9ad492fbfc 100644 --- a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xDriver.java +++ b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xDriver.java @@ -20,6 +20,7 @@ package org.apache.plc4x.java.plc4x; import org.apache.plc4x.java.plc4x.config.Plc4xConfiguration; import org.apache.plc4x.java.plc4x.config.Plc4xTcpTransportConfiguration; +import org.apache.plc4x.java.plc4x.config.Plc4xTlsTransportConfiguration; import org.apache.plc4x.java.spi.config.Configuration; import org.apache.plc4x.java.spi.drivers.ConnectionBase; import org.apache.plc4x.java.spi.drivers.DriverBase; @@ -53,17 +54,23 @@ public class Plc4xDriver extends DriverBase { if ("tcp".equals(transport.getTransportCode())) { return Plc4xTcpTransportConfiguration.class; } + if ("tls".equals(transport.getTransportCode())) { + return Plc4xTlsTransportConfiguration.class; + } return super.getTransportConfigurationClass(transport); } @Override public Optional<String> getDefaultTransportCode() { - return Optional.of("tcp"); + // TLS is the default transport - the proxy carries credentials, so the channel should + // be encrypted by default. Plaintext TCP remains available as an explicit opt-in + // (e.g. "plc4x:tcp://...") for trusted networks or testing. + return Optional.of("tls"); } @Override public List<String> getSupportedTransportCodes() { - return List.of("tcp", "test"); + return List.of("tls", "tcp", "test"); } @Override diff --git a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xMessageCodec.java b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xMessageCodec.java index a28283954a..19283f8038 100644 --- a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xMessageCodec.java +++ b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/Plc4xMessageCodec.java @@ -19,8 +19,11 @@ package org.apache.plc4x.java.plc4x; import org.apache.plc4x.java.plc4x.readwrite.Plc4xMessage; +import org.apache.plc4x.java.spi.buffers.api.WithOption; import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException; import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased; +import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption; +import org.apache.plc4x.java.spi.buffers.bytebased.WriteBufferByteBased; import org.apache.plc4x.java.spi.drivers.MessageCodecBase; import org.apache.plc4x.java.spi.drivers.exceptions.MessageCodecException; import org.apache.plc4x.java.spi.transports.api.TransportInstance; @@ -67,4 +70,30 @@ public class Plc4xMessageCodec extends MessageCodecBase<Plc4xMessage> { return Plc4xMessage.staticParse(readBuffer); } + /** + * The generated serializers/parsers ask the buffer for an unsigned-binary integer encoding + * (and signed/float encodings) for fields that don't pass an explicit option. The default + * {@link MessageCodecBase} buffers do not pre-configure those, which trips field writers like + * {@code writeUnsignedShort}, so we supply the big-endian defaults the protocol expects. + */ + @Override + protected WriteBufferByteBased createWriteBuffer(int size) { + return new WriteBufferByteBased(new byte[size], + WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), + WithOption.WithSignedIntegerEncoding("twos-complement"), + WithOption.WithFloatEncoding("IEEE754"), + WithOption.WithStringEncoding("UTF8"), + WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + } + + @Override + protected ReadBufferByteBased createReadBuffer(byte[] data) { + return new ReadBufferByteBased(data, + WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), + WithOption.WithSignedIntegerEncoding("twos-complement"), + WithOption.WithFloatEncoding("IEEE754"), + WithOption.WithStringEncoding("UTF8"), + WithByteBasedOption.WithByteOrder("BIG_ENDIAN")); + } + } diff --git a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xConfiguration.java b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xConfiguration.java index 6a9feb0ae7..115bd78d77 100644 --- a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xConfiguration.java +++ b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xConfiguration.java @@ -34,6 +34,14 @@ public class Plc4xConfiguration implements Configuration { @Description("Default timeout for all types of requests.") private int requestTimeout; + @ConfigurationParameter("username") + @Description("Username for authenticating against the PLC4X proxy server. Authentication is mandatory.") + private String username; + + @ConfigurationParameter("password") + @Description("Password for authenticating against the PLC4X proxy server. Authentication is mandatory.") + private String password; + public String getRemoteConnectionString() { return remoteConnectionString; } @@ -42,6 +50,22 @@ public class Plc4xConfiguration implements Configuration { this.remoteConnectionString = remoteConnectionString; } + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + public int getRequestTimeout() { return requestTimeout; } diff --git a/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xTlsTransportConfiguration.java b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xTlsTransportConfiguration.java new file mode 100644 index 0000000000..1545ee950c --- /dev/null +++ b/plc4j/drivers/plc4x/src/main/java/org/apache/plc4x/java/plc4x/config/Plc4xTlsTransportConfiguration.java @@ -0,0 +1,31 @@ +/* + * 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 org.apache.plc4x.java.plc4x.config; + +import org.apache.plc4x.java.plc4x.readwrite.Constants; +import org.apache.plc4x.java.transport.tls.config.TlsTransportConfiguration; + +public class Plc4xTlsTransportConfiguration extends TlsTransportConfiguration { + + @Override + public int getDefaultPort() { + return Constants.PLC4XTCPDEFAULTPORT; + } + +} diff --git a/protocols/plc4x/src/main/resources/protocols/plc4x/v0/plc4x.mspec b/protocols/plc4x/src/main/resources/protocols/plc4x/v0/plc4x.mspec index 3d88d6f6ad..ad75f472b4 100644 --- a/protocols/plc4x/src/main/resources/protocols/plc4x/v0/plc4x.mspec +++ b/protocols/plc4x/src/main/resources/protocols/plc4x/v0/plc4x.mspec @@ -27,6 +27,15 @@ [simple uint 16 requestId ] [discriminator Plc4xRequestType requestType ] [typeSwitch requestType + ['AUTH_REQUEST' Plc4xAuthRequest + [implicit uint 8 usernameLen 'STR_LEN(username)'] + [simple vstring 'usernameLen * 8' username ] + [implicit uint 8 passwordLen 'STR_LEN(password)'] + [simple vstring 'passwordLen * 8' password ] + ] + ['AUTH_RESPONSE' Plc4xAuthResponse + [simple Plc4xResponseCode responseCode ] + ] ['CONNECT_REQUEST' Plc4xConnectRequest [implicit uint 8 connectionStringLen 'STR_LEN(connectionString)'] [simple vstring 'connectionStringLen * 8' connectionString ] @@ -229,6 +238,8 @@ ['0x0A' SUBSCRIPTION_RESPONSE ] ['0x0B' UNSUBSCRIPTION_REQUEST ] ['0x0C' UNSUBSCRIPTION_RESPONSE] + ['0x0D' AUTH_REQUEST ] + ['0x0E' AUTH_RESPONSE ] ] [enum uint 8 Plc4xResponseCode
