This is an automated email from the ASF dual-hosted git repository.

sruehl pushed a commit to branch testing/adsLibPort
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 113a226c3df84e7c7882ba6bc5cc80c6586975e4
Author: Sebastian Rühl <sru...@apache.org>
AuthorDate: Fri Apr 13 13:34:09 2018 +0200

    initial port of AdsLib to test compatibility of to c++ implementation
---
 .../plc4x/java/ads/api/commands/types/Result.java  |   4 +-
 .../plc4x/java/ads/api/generic/types/AmsError.java |   8 +-
 .../java/ads/connection/AdsTcpPlcConnection.java   |   4 +
 .../ads/adslib/ADSClientNotificationExample.java   | 281 +++++++++++
 .../org/apache/plc4x/java/ads/adslib/AdsLib.java   | 545 +++++++++++++++++++++
 .../apache/plc4x/java/ads/adslib/AmsRequest.java   |  50 ++
 .../apache/plc4x/java/ads/adslib/AmsRouter.java    | 368 ++++++++++++++
 .../org/apache/plc4x/java/ads/adslib/Output.java   |  38 ++
 .../apache/plc4x/java/ads/adslib/package-info.java |  24 +
 plc4j/protocols/ads/src/test/resources/logback.xml |   4 +-
 .../base/connection/TcpSocketChannelFactory.java   |   3 +
 11 files changed, 1326 insertions(+), 3 deletions(-)

diff --git 
a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
 
b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
index ed6ca78..993ad86 100644
--- 
a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
+++ 
b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
@@ -21,6 +21,8 @@ package org.apache.plc4x.java.ads.api.commands.types;
 import io.netty.buffer.ByteBuf;
 import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
 
+import static java.util.Objects.requireNonNull;
+
 public class Result extends UnsignedIntLEByteValue {
 
     public static final int NUM_BYTES = 
UnsignedIntLEByteValue.UNSIGNED_INT_LE_NUM_BYTES;
@@ -58,7 +60,7 @@ public class Result extends UnsignedIntLEByteValue {
     }
 
     public static Result of(AdsReturnCode adsReturnCode) {
-        return of(adsReturnCode.getHex());
+        return of(requireNonNull(adsReturnCode).getHex());
     }
 
     public AdsReturnCode toAdsReturnCode() {
diff --git 
a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
 
b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
index 9a41994..0a78b8d 100644
--- 
a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
+++ 
b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
@@ -22,6 +22,8 @@ import io.netty.buffer.ByteBuf;
 import org.apache.plc4x.java.ads.api.commands.types.AdsReturnCode;
 import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
 
+import static java.util.Objects.requireNonNull;
+
 public class AmsError extends UnsignedIntLEByteValue {
 
     public static final int NUM_BYTES = 
UnsignedIntLEByteValue.UNSIGNED_INT_LE_NUM_BYTES;
@@ -48,6 +50,10 @@ public class AmsError extends UnsignedIntLEByteValue {
         return new AmsError(errorCode);
     }
 
+    public static AmsError of(AdsReturnCode errorCode) {
+        return new AmsError(requireNonNull(errorCode).getHex());
+    }
+
     public static AmsError of(String errorCode) {
         return of(Long.parseLong(errorCode));
     }
@@ -56,7 +62,7 @@ public class AmsError extends UnsignedIntLEByteValue {
         return new AmsError(byteBuf);
     }
 
-    private AdsReturnCode toAdsReturnCode() {
+    public AdsReturnCode toAdsReturnCode() {
         return AdsReturnCode.of(getAsLong());
     }
 
diff --git 
a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
 
b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
index ce96675..505ab63 100644
--- 
a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
+++ 
b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
@@ -85,6 +85,10 @@ public class AdsTcpPlcConnection extends 
AdsAbstractPlcConnection {
         };
     }
 
+    public InetAddress getRemoteAddress() {
+        return ((TcpSocketChannelFactory)channelFactory).getAddress();
+    }
+
     protected static AmsNetId generateAMSNetId() {
         try {
             return AmsNetId.of(Inet4Address.getLocalHost().getHostAddress() + 
".1.1");
diff --git 
a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/ADSClientNotificationExample.java
 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/ADSClientNotificationExample.java
new file mode 100644
index 0000000..edf0c01
--- /dev/null
+++ 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/ADSClientNotificationExample.java
@@ -0,0 +1,281 @@
+/*
+ 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.ads.adslib;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.generic.types.AmsNetId;
+import org.apache.plc4x.java.ads.api.generic.types.AmsPort;
+import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
+
+import java.io.Console;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.plc4x.java.ads.adslib.AdsLib.*;
+
+/**
+ * Test ported from <a 
href="https://github.com/Beckhoff/ADS/blob/master/example/example.cpp";>github</a>
+ * <p>
+ * On github there is a test project which can be used to test the protocol. 
The example there uses a c++ implementation.
+ */
+@SuppressWarnings("all")
+public class ADSClientNotificationExample {
+
+    private static void NotifyCallback(ImmutablePair<AmsNetId, AmsPort> pAddr, 
TimeStamp timeStamp, AdsNotificationSample notificationSample, int hUser) {
+        Data notificationSampleData = notificationSample.getData();
+        byte[] data = notificationSampleData.getBytes();
+        System.out.print("NetId: " + pAddr.left +
+            " hUser 0x" + Integer.toHexString(hUser) +
+            " sample time: " + timeStamp.getAsDate() +
+            " sample size: " + notificationSample.getSampleSize().getAsLong() +
+            " value: ");
+        assert data.length == notificationSample.getSampleSize().getAsLong();
+        System.out.println(" 0x" + Hex.encodeHexString(data));
+    }
+
+    private static SymbolHandle getHandleByNameExample(PrintStream out, long 
port, ImmutablePair<AmsNetId, AmsPort> server, String handleName) {
+        Output<byte[]> handle = new Output<>();
+        Result handleStatus = AdsSyncReadWriteReqEx2(port,
+            server,
+            IndexGroup.ReservedGroups.ADSIGRP_SYM_HNDBYNAME,
+            IndexOffset.of(0),
+            ReadLength.of(4),
+            handle,
+            WriteLength.of(handleName.length()),
+            Data.of(handleName),
+            new Output<>());
+        if (handleStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Create handle for '" + handleName + "' failed with: " 
+ handleStatus);
+            return null;
+        }
+        return SymbolHandle.of(handle.value);
+    }
+
+    private static void releaseHandleExample(PrintStream out, long port, 
ImmutablePair<AmsNetId, AmsPort> server, SymbolHandle handle) {
+        Result releaseHandle = AdsSyncWriteReqEx(port, server, 
IndexGroup.ReservedGroups.ADSIGRP_SYM_RELEASEHND, IndexOffset.of(0), 
WriteLength.of(4), Data.of(handle.getBytes()));
+        if (releaseHandle.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Release handle " + handle + "' failed with: " + 
releaseHandle);
+        }
+    }
+
+
+    private static void notificationExample(PrintStream out, long port, 
ImmutablePair<AmsNetId, AmsPort> server) {
+        AdsNotificationAttribute adsNotificationAttribute = 
AdsNotificationAttribute.of(
+            Length.of(1),
+            AdsNotificationAttribute.ADSTRANS_SERVERCYCLE,
+            MaxDelay.of(0),
+            CycleTime.of(4000000)
+        );
+
+        Output<Long> notificationHandle = new Output<>();
+        int hUser = 0;
+
+        Result addStatus = AdsSyncAddDeviceNotificationReqEx(port,
+            server,
+            IndexGroup.of(0x4020),
+            IndexOffset.of(4),
+            adsNotificationAttribute,
+            ADSClientNotificationExample::NotifyCallback,
+            hUser,
+            notificationHandle);
+
+        if (addStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Add device notification failed with: " + addStatus);
+            return;
+        }
+
+        out.println("Hit ENTER to stop notifications");
+        tryInteractiveWait();
+
+        Result delStatus = AdsSyncDelDeviceNotificationReqEx(port, server, 
NotificationHandle.of(notificationHandle.value));
+        if (delStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Delete device notification failed with: " + 
delStatus);
+            return;
+        }
+    }
+
+    private static void notificationByNameExample(PrintStream out, long port, 
ImmutablePair<AmsNetId, AmsPort> server) {
+        AdsNotificationAttribute adsNotificationAttribute = 
AdsNotificationAttribute.of(
+            Length.of(1),
+            AdsNotificationAttribute.ADSTRANS_SERVERCYCLE,
+            MaxDelay.of(0),
+            CycleTime.of(4000000)
+        );
+        Output<Long> notificationHandle = new Output<>();
+        int hUser = 0;
+
+        SymbolHandle handle;
+
+        out.println("notificationByNameExample():");
+        handle = getHandleByNameExample(out, port, server, "MAIN.byByte[4]");
+        if (handle == null) {
+            return;
+        }
+
+        Result addStatus = AdsSyncAddDeviceNotificationReqEx(port,
+            server,
+            IndexGroup.ReservedGroups.ADSIGRP_SYM_VALBYHND,
+            IndexOffset.of(handle.getAsLong()),
+            adsNotificationAttribute,
+            ADSClientNotificationExample::NotifyCallback,
+            hUser,
+            notificationHandle);
+        if (addStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Add device notification failed with: " + addStatus);
+            return;
+        }
+
+        out.println("Hit ENTER to stop by name notifications");
+        tryInteractiveWait();
+
+        Result delStatus = AdsSyncDelDeviceNotificationReqEx(port, server, 
NotificationHandle.of(notificationHandle.value));
+        if (delStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Delete device notification failed with: " + 
delStatus);
+            return;
+        }
+        releaseHandleExample(out, port, server, handle);
+    }
+
+    private static void readExample(PrintStream out, long port, 
ImmutablePair<AmsNetId, AmsPort> server) {
+        Output<Integer> bytesRead = new Output<>();
+        Output<byte[]> buffer = new Output<>();
+
+        out.println("readExample():");
+        for (int i = 0; i < 8; ++i) {
+            Result status = AdsSyncReadReqEx2(port, server, 
IndexGroup.of(0x4020), IndexOffset.of(0), Length.of(4), buffer, bytesRead);
+            if (status.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+                out.println("ADS read failed with: " + status);
+                return;
+            }
+            out.println("ADS read " + bytesRead.value + " bytes, value: 0x" + 
Hex.encodeHexString(buffer.value));
+        }
+    }
+
+    private static void readByNameExample(PrintStream out, long port, 
ImmutablePair<AmsNetId, AmsPort> server) {
+        Output<Integer> bytesRead = new Output<>();
+        Output<byte[]> buffer = new Output<>();
+        SymbolHandle handle;
+
+        out.println("readByNameExample():");
+        handle = getHandleByNameExample(out, port, server, "MAIN.byByte[4]");
+        if (handle == null) {
+            return;
+        }
+
+        for (int i = 0; i < 8; ++i) {
+            Result status = AdsSyncReadReqEx2(port, server, 
IndexGroup.ReservedGroups.ADSIGRP_SYM_VALBYHND, 
IndexOffset.of(handle.getAsLong()), Length.of(4), buffer, bytesRead);
+            if (status.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+                out.println("ADS read failed with: " + status);
+                return;
+            }
+            out.println("ADS read " + bytesRead.value + " bytes, value: 0x" + 
Hex.encodeHexString(buffer.value));
+        }
+        releaseHandleExample(out, port, server, handle);
+    }
+
+    private static void readStateExample(PrintStream out, long port, 
ImmutablePair<AmsNetId, AmsPort> server) {
+        Output<Integer> adsState = new Output<>();
+        Output<Integer> devState = new Output<>();
+
+        Result status = AdsSyncReadStateReqEx(port, server, adsState, 
devState);
+        if (status.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("ADS read failed with: " + status);
+            return;
+        }
+        out.println("ADS state: " + adsState.value + " devState: " + 
devState.value);
+    }
+
+    private static void runExample(String remoteNetIdString, String 
remoteIpV4, PrintStream out) {
+        AmsNetId remoteNetId = AmsNetId.of(remoteNetIdString);
+
+        // uncomment and adjust if automatic AmsNetId deduction is not working 
as expected
+        AdsSetLocalAddress(AmsNetId.of("10.10.56.23.1.1"));
+
+        // add local route to your EtherCAT Master
+        if (AdsAddRoute(remoteNetId, remoteIpV4).toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            out.println("Adding ADS route failed, did you specified valid 
addresses?");
+            return;
+        }
+
+        // open a new ADS port
+        long port = AdsPortOpenEx();
+        if (port < 0) {
+            out.println("Open ADS port failed");
+            return;
+        }
+
+
+        ImmutablePair<AmsNetId, AmsPort> remote = 
ImmutablePair.of(remoteNetId, AmsPort.of(851));
+
+        notificationExample(out, port, remote);
+        notificationByNameExample(out, port, remote);
+        readExample(out, port, remote);
+        readByNameExample(out, port, remote);
+        readStateExample(out, port, remote);
+
+        Result closeStatus = AdsPortCloseEx(port);
+        if (closeStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            out.println("Close ADS port failed with: " + closeStatus);
+        }
+
+        AdsDelRoute(remoteNetId);
+    }
+
+    public static void main(String... args) {
+        String remoteNetIdString = "10.10.64.40.1.1";
+        String remoteIpV4 = "10.10.64.40";
+        if (args.length == 2) {
+            remoteIpV4 = args[0];
+            remoteNetIdString = args[1];
+        }
+        runExample(remoteNetIdString, remoteIpV4, System.out);
+        System.exit(0);
+    }
+
+    /////
+    // Utils
+    private static void tryInteractiveWait() {
+        Console console = System.console();
+        if (console != null) {
+            console.readLine();
+        } else {
+            try {
+                int timeout = 
Integer.valueOf(System.getProperty("input.timeout", "3"));
+                System.out.println("Using timeout of " + timeout + "Seconds as 
System.console() is not available. Override with -Dinput.timeout=3");
+                TimeUnit.SECONDS.sleep(timeout);
+                System.out.println("Timeout reached enter pressed");
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    static class SymbolHandle extends UnsignedIntLEByteValue {
+
+        public SymbolHandle(byte... bytes) {
+            super(bytes);
+        }
+
+        public static SymbolHandle of(byte... bytes) {
+            return new SymbolHandle(bytes);
+        }
+    }
+}
diff --git 
a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AdsLib.java
 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AdsLib.java
new file mode 100644
index 0000000..8fef263
--- /dev/null
+++ 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AdsLib.java
@@ -0,0 +1,545 @@
+/*
+ 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.ads.adslib;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.plc4x.java.ads.api.commands.*;
+import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.generic.types.AmsError;
+import org.apache.plc4x.java.ads.api.generic.types.AmsNetId;
+import org.apache.plc4x.java.ads.api.generic.types.AmsPort;
+import org.apache.plc4x.java.ads.api.generic.types.Invoke;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS";>github AdsLib</a>
+ */
+@SuppressWarnings("all")
+public class AdsLib {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AdsLib.class);
+
+    private static final AmsRouter amsRouter = new AmsRouter();
+
+    private static AmsRouter GetRouter() {
+        return amsRouter;
+    }
+
+    /**
+     * Add new ams route to target system
+     *
+     * @param ams address of the target system
+     * @param ip  address of the target system
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsAddRoute(AmsNetId ams, String ip) {
+        try {
+            return GetRouter().AddRoute(ams, (Inet4Address) 
Inet4Address.getByName(ip));
+        } catch (UnknownHostException e) {
+            LOGGER.error("Error adding route", e);
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Delete ams route that had previously been added with AdsAddRoute().
+     *
+     * @param ams address of the target system
+     */
+    public static void AdsDelRoute(AmsNetId ams) {
+        GetRouter().DelRoute(ams);
+    }
+
+    /**
+     * The connection (communication port) to the message router is
+     * closed. The port to be closed must previously have been opened via
+     * an AdsPortOpenEx() call.
+     *
+     * @param port port number of an Ads port that had previously been opened 
with AdsPortOpenEx().
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsPortCloseEx(long port) {
+        return GetRouter().ClosePort((int) port);
+    }
+
+    /**
+     * Establishes a connection (communication port) to the message
+     * router. The port number returned by AdsPortOpenEx() is required as
+     * parameter for further AdsLib function calls.
+     *
+     * @return port number of a new Ads port or 0 if no more ports available
+     */
+    public static long AdsPortOpenEx() {
+        return GetRouter().OpenPort();
+    }
+
+    /**
+     * Returns the local NetId and port number.
+     *
+     * @param port  port number of an Ads port that had previously been opened 
with AdsPortOpenEx().
+     * @param pAddr Pointer to the structure of type ImmutablePair<AmsNetId, 
AmsPort>.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsGetLocalAddressEx(long port, 
Output<ImmutablePair<AmsNetId, AmsPort>> pAddr) {
+        return GetRouter().GetLocalAddress((int) port, pAddr);
+    }
+
+    /**
+     * Change local NetId
+     *
+     * @param ams local AmsNetId
+     */
+    public static void AdsSetLocalAddress(AmsNetId ams) {
+        GetRouter().SetLocalAddress(ams);
+    }
+
+    /**
+     * Reads data synchronously from an ADS server.
+     *
+     * @param port         port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param pAddr        Structure with NetId and port number of the ADS 
server.
+     * @param indexGroup   Index Group.
+     * @param indexOffset  Index Offset.
+     * @param bufferLength Length of the data in bytes.
+     * @param buffer       Pointer to a data buffer that will receive the data.
+     * @param bytesRead    pointer to a variable. If successful, this variable 
will return the number of actually read data bytes.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncReadReqEx2(long port,
+                                           ImmutablePair<AmsNetId, AmsPort> 
pAddr,
+                                           IndexGroup indexGroup,
+                                           IndexOffset indexOffset,
+                                           Length bufferLength,
+                                           Output<byte[]> buffer,
+                                           Output<Integer> bytesRead) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsReadRequest adsReadRequest = AdsReadRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE,
+            indexGroup,
+            indexOffset,
+            bufferLength
+        );
+
+        AmsRequest<AdsReadRequest, AdsReadResponse> request = 
AmsRequest.of(adsReadRequest);
+        AmsError adsRequestResult = GetRouter().AdsRequest(request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsReadResponse response = request.getResponseFuture().get(3, 
TimeUnit.SECONDS);
+            byte[] bytes = response.getData().getBytes();
+            buffer.value = bytes;
+            bytesRead.value = bytes.length;
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Reads the identification and version number of an ADS server.
+     *
+     * @param port    port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param pAddr   Structure with NetId and port number of the ADS server.
+     * @param devName Pointer to a character string of at least 16 bytes, that 
will receive the name of the ADS device.
+     * @param version Address of a variable of type AdsVersion, which will 
receive the version number, revision number and the build number.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncReadDeviceInfoReqEx(long port, 
ImmutablePair<AmsNetId, AmsPort> pAddr, Output<String> devName, 
Output<Triple<Byte, Byte, Integer>> version) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsReadDeviceInfoRequest adsReadDeviceInfoRequest = 
AdsReadDeviceInfoRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE
+        );
+        AmsRequest<AdsReadDeviceInfoRequest, AdsReadDeviceInfoResponse> 
request = AmsRequest.of(adsReadDeviceInfoRequest);
+        AmsError adsRequestResult = GetRouter().AdsRequest(request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsReadDeviceInfoResponse response = 
request.getResponseFuture().get(3, TimeUnit.SECONDS);
+            devName.value = new String(response.getDevice().getBytes());
+            version.value = Triple.of(response.getMajorVersion().getAsByte(), 
response.getMinorVersion().getAsByte(), response.getVersion().getAsInt());
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Reads the ADS status and the device status from an ADS server.
+     *
+     * @param port     port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param pAddr    Structure with NetId and port number of the ADS server.
+     * @param adsState Address of a variable that will receive the ADS status 
(see data type 
[ADSSTATE](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/tcadsdll_enumadsstate.htm?id=17630)).
+     * @param devState Address of a variable that will receive the device 
status.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncReadStateReqEx(long port, 
ImmutablePair<AmsNetId, AmsPort> pAddr, Output<Integer> adsState, 
Output<Integer> devState) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsReadStateRequest adsReadStateRequest = AdsReadStateRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE
+        );
+        AmsRequest<AdsReadStateRequest, AdsReadStateResponse> request = 
AmsRequest.of(adsReadStateRequest);
+        AmsError adsRequestResult = GetRouter().AdsRequest(request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsReadStateResponse response = request.getResponseFuture().get(3, 
TimeUnit.SECONDS);
+            adsState.value = response.getAdsState().getAsInt();
+            devState.value = response.getDeviceState().getAsInt();
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Writes data synchronously into an ADS server and receives data back 
from the ADS server.
+     *
+     * @param port        port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param pAddr       Structure with NetId and port number of the ADS 
server.
+     * @param indexGroup  Index Group.
+     * @param indexOffset Index Offset.
+     * @param readLength  Length, in bytes, of the read buffer readData.
+     * @param readData    Buffer for data read from the ADS server.
+     * @param writeLength Length of the data, in bytes, send to the ADS server.
+     * @param writeData   Buffer with data send to the ADS server.
+     * @param bytesRead   pointer to a variable. If successful, this variable 
will return the number of actually read data bytes.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncReadWriteReqEx2(long port,
+                                                ImmutablePair<AmsNetId, 
AmsPort> pAddr,
+                                                IndexGroup indexGroup,
+                                                IndexOffset indexOffset,
+                                                ReadLength readLength,
+                                                Output<byte[]> readData,
+                                                WriteLength writeLength,
+                                                Data writeData,
+                                                Output<Integer> bytesRead) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsReadWriteRequest adsReadWriteRequest = AdsReadWriteRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE,
+            indexGroup,
+            indexOffset,
+            readLength,
+            writeData
+        );
+        AmsRequest<AdsReadWriteRequest, AdsReadWriteResponse> request = 
AmsRequest.of(adsReadWriteRequest);
+        AmsError adsRequestResult = GetRouter().AdsRequest(request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsReadWriteResponse response = request.getResponseFuture().get(3, 
TimeUnit.SECONDS);
+            byte[] bytes = response.getData().getBytes();
+            readData.value = bytes;
+            bytesRead.value = bytes.length;
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Writes data synchronously to an ADS server.
+     *
+     * @param port         port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param pAddr        Structure with NetId and port number of the ADS 
server.
+     * @param indexGroup   Index Group.
+     * @param indexOffset  Index Offset.
+     * @param bufferLength Length of the data, in bytes, send to the ADS 
server.
+     * @param buffer       Buffer with data send to the ADS server.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncWriteReqEx(long port,
+                                           ImmutablePair<AmsNetId, AmsPort> 
pAddr,
+                                           IndexGroup indexGroup,
+                                           IndexOffset indexOffset,
+                                           WriteLength bufferLength,
+                                           Data buffer) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsWriteRequest adsWriteRequest = AdsWriteRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE,
+            indexGroup,
+            indexOffset,
+            buffer
+        );
+        AmsRequest<AdsWriteRequest, AdsWriteResponse> request = 
AmsRequest.of(adsWriteRequest);
+        AmsError adsRequestResult = GetRouter().AdsRequest(request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsWriteResponse response = request.getResponseFuture().get(3, 
TimeUnit.SECONDS);
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Changes the ADS status and the device status of an ADS server.
+     *
+     * @param port         port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param pAddr        Structure with NetId and port number of the ADS 
server.
+     * @param adsState     New ADS status.
+     * @param devState     New device status.
+     * @param bufferLength Length of the additional data, in bytes, send to 
the ADS server.
+     * @param buffer       Buffer with additional data send to the ADS server.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncWriteControlReqEx(long port,
+                                                  ImmutablePair<AmsNetId, 
AmsPort> pAddr,
+                                                  short adsState,
+                                                  short devState,
+                                                  int bufferLength,
+                                                  byte[] buffer) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsWriteControlRequest adsWriteControlRequest = 
AdsWriteControlRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE,
+            AdsState.of(adsState),
+            DeviceState.of(devState),
+            Data.of(buffer)
+        );
+        AmsRequest<AdsWriteControlRequest, AdsWriteControlResponse> request = 
AmsRequest.of(adsWriteControlRequest);
+        AmsError adsRequestResult = GetRouter().AdsRequest(request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsWriteControlResponse response = 
request.getResponseFuture().get(3, TimeUnit.SECONDS);
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * A notification is defined within an ADS server (e.g. PLC). When a
+     * certain event occurs a function (the callback function) is invoked in
+     * the ADS client (C program).
+     *
+     * @param port          port number of an Ads port that had previously 
been opened with AdsPortOpenEx().
+     * @param pAddr         Structure with NetId and port number of the ADS 
server.
+     * @param indexGroup    Index Group.
+     * @param indexOffset   Index Offset.
+     * @param attribute     Pointer to the structure that contains further 
information.
+     * @param pFunc         Pointer to the structure describing the callback 
function.
+     * @param hUser         32-bit value that is passed to the callback 
function.
+     * @param pNotification Address of the variable that will receive the 
handle of the notification.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncAddDeviceNotificationReqEx(long port,
+                                                           
ImmutablePair<AmsNetId, AmsPort> pAddr,
+                                                           IndexGroup 
indexGroup,
+                                                           IndexOffset 
indexOffset,
+                                                           
AdsNotificationAttribute attribute,
+                                                           
PAdsNotificationFuncEx pFunc,
+                                                           int hUser,
+                                                           Output<Long> 
pNotification) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsAddDeviceNotificationRequest adsAddDeviceNotificationRequest = 
AdsAddDeviceNotificationRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE,
+            indexGroup,
+            indexOffset,
+            attribute.length,
+            attribute.transmissionMode,
+            attribute.maxDelay,
+            attribute.cycleTime
+        );
+        AmsRouter.Notification notify = AmsRouter.Notification.of(pFunc, 
hUser, attribute.length, pAddr, port);
+        AmsRequest<AdsAddDeviceNotificationRequest, 
AdsAddDeviceNotificationResponse> request = 
AmsRequest.of(adsAddDeviceNotificationRequest);
+        AmsError adsRequestResult = GetRouter().AddNotification(request, 
pNotification, notify);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsAddDeviceNotificationResponse response = 
request.getResponseFuture().get(3, TimeUnit.SECONDS);
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * A notification defined previously is deleted from an ADS server.
+     *
+     * @param port               port number of an Ads port that had 
previously been opened with AdsPortOpenEx().
+     * @param pAddr              Structure with NetId and port number of the 
ADS server.
+     * @param notificationHandle Address of the variable that contains the 
handle of the notification.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncDelDeviceNotificationReqEx(long port, 
ImmutablePair<AmsNetId, AmsPort> pAddr, NotificationHandle notificationHandle) {
+        Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+        Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, 
localAmsNet);
+        if (getLocalAddressResult.toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+            return getLocalAddressResult;
+        }
+        AdsDeleteDeviceNotificationRequest adsDeleteDeviceNotificationRequest 
= AdsDeleteDeviceNotificationRequest.of(
+            pAddr.left,
+            pAddr.right,
+            localAmsNet.value.left,
+            localAmsNet.value.right,
+            Invoke.NONE,
+            notificationHandle
+        );
+        AmsRequest<AdsDeleteDeviceNotificationRequest, 
AdsDeleteDeviceNotificationResponse> request = 
AmsRequest.of(adsDeleteDeviceNotificationRequest);
+        AmsError adsRequestResult = GetRouter().DelNotification((int) port, 
pAddr, request);
+        if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+            return Result.of(adsRequestResult.getAsLong());
+        }
+        try {
+            AdsDeleteDeviceNotificationResponse response = 
request.getResponseFuture().get(3, TimeUnit.SECONDS);
+            return response.getResult();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return Result.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    /**
+     * Read the configured timeout for the ADS functions. The standard value 
is 5000 ms.
+     *
+     * @param port    port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param timeout Buffer to store timeout value in ms.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncGetTimeoutEx(long port, Output<Integer> 
timeout) {
+        return GetRouter().GetTimeout((int) port, timeout);
+    }
+
+    /**
+     * Alters the timeout for the ADS functions. The standard value is 5000 ms.
+     *
+     * @param port    port number of an Ads port that had previously been 
opened with AdsPortOpenEx().
+     * @param timeout Timeout in ms.
+     * @return [ADS Return 
Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+     */
+    public static Result AdsSyncSetTimeoutEx(long port, int timeout) {
+        return GetRouter().SetTimeout((int) port, timeout);
+    }
+
+    ////
+    // Utils
+
+    /**
+     * Ported from <a href="https://github.com/Beckhoff/ADS";>github AdsLib</a>
+     */
+    @FunctionalInterface
+    public interface PAdsNotificationFuncEx {
+        void notifyCallback(ImmutablePair<AmsNetId, AmsPort> pAddr, TimeStamp 
timeStamp, AdsNotificationSample notificationSample, int hUser);
+    }
+
+    /**
+     * Ported from <a href="https://github.com/Beckhoff/ADS";>github AdsLib</a>
+     */
+    public static class AdsNotificationAttribute {
+
+        public static TransmissionMode ADSTRANS_SERVERCYCLE = 
TransmissionMode.of(3);
+
+        public final Length length;
+        public final TransmissionMode transmissionMode;
+        public final MaxDelay maxDelay;
+        public final CycleTime cycleTime;
+
+        public AdsNotificationAttribute(Length length, TransmissionMode 
transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
+            this.length = length;
+            this.transmissionMode = transmissionMode;
+            this.maxDelay = maxDelay;
+            this.cycleTime = cycleTime;
+        }
+
+        public static AdsNotificationAttribute of(Length length, 
TransmissionMode transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
+            return new AdsNotificationAttribute(length, transmissionMode, 
maxDelay, cycleTime);
+        }
+    }
+
+}
diff --git 
a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRequest.java
 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRequest.java
new file mode 100644
index 0000000..c31e978
--- /dev/null
+++ 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRequest.java
@@ -0,0 +1,50 @@
+/*
+ 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.ads.adslib;
+
+import org.apache.plc4x.java.ads.api.generic.AmsPacket;
+import org.apache.plc4x.java.api.messages.PlcProprietaryRequest;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS";>github AdsLib</a>
+ */
+public class AmsRequest<REQUEST extends AmsPacket, RESPONSE extends AmsPacket> 
{
+
+    private final PlcProprietaryRequest<REQUEST> request;
+    private final CompletableFuture<RESPONSE> responseFuture;
+
+    private AmsRequest(REQUEST amsPacket, CompletableFuture<RESPONSE> 
responseFuture) {
+        this.request = new PlcProprietaryRequest<>(amsPacket);
+        this.responseFuture = responseFuture;
+    }
+
+    public static <REQUEST extends AmsPacket, RESPONSE extends AmsPacket> 
AmsRequest<REQUEST, RESPONSE> of(REQUEST amsPacket) {
+        return new AmsRequest<>(amsPacket, new CompletableFuture<>());
+    }
+
+    public PlcProprietaryRequest<REQUEST> getRequest() {
+        return request;
+    }
+
+    public CompletableFuture<RESPONSE> getResponseFuture() {
+        return responseFuture;
+    }
+}
diff --git 
a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRouter.java
 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRouter.java
new file mode 100644
index 0000000..5e1d768
--- /dev/null
+++ 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRouter.java
@@ -0,0 +1,368 @@
+/*
+ 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.ads.adslib;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.plc4x.java.ads.api.commands.*;
+import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.generic.AmsPacket;
+import org.apache.plc4x.java.ads.api.generic.types.AmsError;
+import org.apache.plc4x.java.ads.api.generic.types.AmsNetId;
+import org.apache.plc4x.java.ads.api.generic.types.AmsPort;
+import org.apache.plc4x.java.ads.connection.AdsTcpPlcConnection;
+import org.apache.plc4x.java.ads.protocol.Plc4x2AdsProtocol;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.PlcProprietaryRequest;
+import org.apache.plc4x.java.api.messages.PlcProprietaryResponse;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.function.Consumer;
+
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS";>github AdsLib</a>
+ */
+@SuppressWarnings("all")
+public class AmsRouter {
+
+    private static final int NUM_PORTS_MAX = 128;
+    private static final int PORT_BASE = 30000;
+
+    private AmsNetId localAddr;
+
+    private Map<InetAddress, AdsTcpPlcConnection> connections = new 
HashMap<>();
+    private Map<AmsNetId, AdsTcpPlcConnection> mapping = new HashMap<>();
+    private Map<AdsTcpPlcConnection, MutableInt> refCounts = new HashMap<>();
+
+    private Map<Integer, AdsLibPort> ports = new HashMap<>();
+
+    public AmsRouter() {
+        try {
+            // TODO: ensure we have ipv4
+            this.localAddr = 
AmsNetId.of(Inet4Address.getLocalHost().getHostAddress() + ".1.1");
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public AmsRouter(AmsNetId localAddr) {
+        this.localAddr = localAddr;
+    }
+
+    synchronized Result AddRoute(AmsNetId ams, Inet4Address ip) {
+        AdsTcpPlcConnection oldConnection = GetConnection(ams);
+        if (oldConnection != null && !(ip.equals(((InetSocketAddress) 
oldConnection.getChannel().remoteAddress()).getAddress()))) {
+            /*
+             There is already a route for this AmsNetId, but with
+             a different IP. The old route has to be deleted, first!
+             */
+            return Result.of(AdsReturnCode.ADS_CODE_1286);
+        }
+
+        AdsTcpPlcConnection conn = connections.get(ip);
+        if (conn == null) {
+            AdsTcpPlcConnection newConnection = AdsTcpPlcConnection.of(ip, 
null, null);
+            // TODO: add listener here:
+            try {
+                newConnection.connect();
+            } catch (PlcConnectionException e) {
+                throw new RuntimeException(e);
+            }
+            connections.put(ip, newConnection);
+            conn = newConnection;
+
+            /* in case no local AmsNetId was set previously, we derive one */
+            if (localAddr == null) {
+                localAddr = conn.getSourceAmsNetId();
+            }
+        }
+
+        MutableInt refCounter = refCounts.getOrDefault(conn, new MutableInt());
+        refCounter.increment();
+        mapping.put(ams, conn);
+        return localAddr == null ? Result.of(AdsReturnCode.ADS_CODE_1) : 
Result.of(AdsReturnCode.ADS_CODE_0);
+    }
+
+    synchronized void DelRoute(AmsNetId ams) {
+        AdsTcpPlcConnection route = mapping.get(ams);
+        if (route != null) {
+            AdsTcpPlcConnection conn = route;
+            conn.close();
+            MutableInt refCounter = refCounts.getOrDefault(conn, new 
MutableInt());
+            if (0 == refCounter.decrementAndGet()) {
+                mapping.remove(ams);
+                refCounts.remove(conn);
+                DeleteIfLastConnection(conn);
+            }
+        }
+    }
+
+    void DeleteIfLastConnection(AdsTcpPlcConnection conn) {
+        if (conn != null) {
+            if (mapping.containsValue(conn)) {
+                return;
+            }
+            connections.remove(conn.getRemoteAddress());
+        }
+    }
+
+    synchronized int OpenPort() {
+        try {
+            AdsLibPort adsLibPort = new AdsLibPort(mapping);
+            int localPort = adsLibPort.getLocalPort();
+            ports.put(localPort, adsLibPort);
+            return localPort;
+        } catch (IOException e) {
+            return 0;
+        }
+    }
+
+    synchronized Result ClosePort(int port) {
+        AdsLibPort serverSocket = ports.get(port);
+        if (serverSocket == null || (serverSocket.getLocalPort() < PORT_BASE) 
|| (serverSocket.getLocalPort() >= PORT_BASE + NUM_PORTS_MAX)) {
+            return Result.of(AdsReturnCode.ADS_CODE_1864);
+        }
+        serverSocket.close();
+        return Result.of(AdsReturnCode.ADS_CODE_0);
+    }
+
+    synchronized Result GetLocalAddress(int port, 
Output<ImmutablePair<AmsNetId, AmsPort>> pAddr) {
+        if ((port < PORT_BASE) || (port >= PORT_BASE + NUM_PORTS_MAX)) {
+            return Result.of(AdsReturnCode.ADS_CODE_1864);
+        }
+
+        AdsLibPort serverSocket = ports.get(port);
+        if (serverSocket == null) {
+            return Result.of(AdsReturnCode.ADS_CODE_1864);
+        }
+
+        AmsPort amsPort = AmsPort.of(serverSocket.getLocalPort());
+        pAddr.value = ImmutablePair.of(localAddr, amsPort);
+        return Result.of(AdsReturnCode.ADS_CODE_0);
+    }
+
+    synchronized void SetLocalAddress(AmsNetId netId) {
+        localAddr = netId;
+    }
+
+    synchronized Result GetTimeout(int port, Output<Integer> timeout) {
+        if ((port < PORT_BASE) || (port >= PORT_BASE + NUM_PORTS_MAX)) {
+            return Result.of(AdsReturnCode.ADS_CODE_1864);
+        }
+        AdsLibPort serverSocket = ports.get(port);
+        timeout.value = serverSocket.getSoTimeout();
+        return Result.of(AdsReturnCode.ADS_CODE_0);
+    }
+
+    synchronized Result SetTimeout(int port, int timeout) {
+        if ((port < PORT_BASE) || (port >= PORT_BASE + NUM_PORTS_MAX)) {
+            return Result.of(AdsReturnCode.ADS_CODE_1864);
+        }
+        AdsLibPort serverSocket = ports.get(port);
+        serverSocket.setSoTimeout(timeout);
+        return Result.of(AdsReturnCode.ADS_CODE_0);
+    }
+
+    synchronized AdsTcpPlcConnection GetConnection(AmsNetId amsDest) {
+        AdsTcpPlcConnection conn = mapping.get(amsDest);
+        if (conn == null) {
+            return null;
+        }
+        return connections.get(conn.getRemoteAddress());
+    }
+
+    <T extends AmsPacket, R extends AmsPacket> AmsError 
AdsRequest(AmsRequest<T, R> request) {
+        PlcProprietaryRequest<T> plcProprietaryRequest = request.getRequest();
+
+        AdsTcpPlcConnection ads = 
GetConnection(plcProprietaryRequest.getRequest().getAmsHeader().getTargetAmsNetId());
+        if (ads == null) {
+            return AmsError.of(AdsReturnCode.ADS_CODE_7);
+        }
+        CompletableFuture<PlcProprietaryResponse<R>> completableFuture = 
ads.send(plcProprietaryRequest);
+        try {
+            PlcProprietaryResponse<R> response = completableFuture.get(3, 
TimeUnit.SECONDS);
+            request.getResponseFuture().complete(response.getResponse());
+            return response.getResponse().getAmsHeader().getCode();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return AmsError.of(AdsReturnCode.ADS_CODE_1864);
+        }
+    }
+
+    AmsError AddNotification(AmsRequest<AdsAddDeviceNotificationRequest, 
AdsAddDeviceNotificationResponse> request, Output<Long> pNotification, 
Notification notify) {
+        PlcProprietaryRequest<AdsAddDeviceNotificationRequest> 
plcProprietaryRequest = request.getRequest();
+        //if (request.bytesRead) {
+        //    request.bytesRead = 0;
+        //}
+
+        AdsTcpPlcConnection ads = 
GetConnection(plcProprietaryRequest.getRequest().getAmsHeader().getTargetAmsNetId());
+        if (ads == null) {
+            return AmsError.of(AdsReturnCode.ADS_CODE_7);
+        }
+
+        AdsLibPort port = 
ports.get(plcProprietaryRequest.getRequest().getAmsHeader().getSourceAmsPort().getAsInt());
+        
CompletableFuture<PlcProprietaryResponse<AdsAddDeviceNotificationResponse>> 
send = ads.send(plcProprietaryRequest);
+        try {
+            PlcProprietaryResponse<AdsAddDeviceNotificationResponse> response 
= send.get(3, TimeUnit.SECONDS);
+            if (response.getResponse().getResult().toAdsReturnCode() != 
AdsReturnCode.ADS_CODE_0) {
+                return 
AmsError.of(response.getResponse().getResult().getAsLong());
+            }
+            pNotification.value = 
response.getResponse().getNotificationHandle().getAsLong();
+            port.AddNotification(pNotification.value, notify);
+            request.getResponseFuture().complete(response.getResponse());
+            return response.getResponse().getAmsHeader().getCode();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return AmsError.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    AmsError DelNotification(int port, ImmutablePair<AmsNetId, AmsPort> pAddr, 
AmsRequest<AdsDeleteDeviceNotificationRequest, 
AdsDeleteDeviceNotificationResponse> request) {
+        PlcProprietaryRequest<AdsDeleteDeviceNotificationRequest> 
plcProprietaryRequest = request.getRequest();
+
+        AdsTcpPlcConnection ads = 
GetConnection(plcProprietaryRequest.getRequest().getAmsHeader().getTargetAmsNetId());
+        if (ads == null) {
+            return AmsError.of(AdsReturnCode.ADS_CODE_7);
+        }
+
+        AdsLibPort adsLibPort = ports.get(port);
+        
CompletableFuture<PlcProprietaryResponse<AdsDeleteDeviceNotificationResponse>> 
send = ads.send(plcProprietaryRequest);
+        try {
+            PlcProprietaryResponse<AdsDeleteDeviceNotificationResponse> 
response = send.get(3, TimeUnit.SECONDS);
+
+            adsLibPort.DelNotification(pAddr, 
plcProprietaryRequest.getRequest().getNotificationHandle());
+            request.getResponseFuture().complete(response.getResponse());
+            return response.getResponse().getAmsHeader().getCode();
+        } catch (InterruptedException | ExecutionException | TimeoutException 
e) {
+            e.printStackTrace();
+            return AmsError.of(AdsReturnCode.ADS_CODE_1);
+        }
+    }
+
+    static class Notification {
+
+        AdsLib.PAdsNotificationFuncEx pFunc;
+        int hUser;
+        Length length;
+        ImmutablePair<AmsNetId, AmsPort> pAddr;
+        long port;
+
+        Notification(AdsLib.PAdsNotificationFuncEx pFunc, int hUser, Length 
length, ImmutablePair<AmsNetId, AmsPort> pAddr, long port) {
+            this.pFunc = pFunc;
+            this.hUser = hUser;
+            this.length = length;
+            this.pAddr = pAddr;
+            this.port = port;
+        }
+
+        static Notification of(AdsLib.PAdsNotificationFuncEx pFunc, int hUser, 
Length length, ImmutablePair<AmsNetId, AmsPort> pAddr, long port) {
+            return new Notification(pFunc, hUser, length, pAddr, port);
+        }
+
+    }
+
+    static class AdsLibPort implements Consumer<AdsDeviceNotificationRequest> {
+
+        private ServerSocket serverSocket;
+        private Map<Long, Notification> notificationMap;
+        private Map<AmsNetId, AdsTcpPlcConnection> mapping;
+
+        public AdsLibPort(Map<AmsNetId, AdsTcpPlcConnection> mapping) throws 
IOException {
+            notificationMap = new ConcurrentHashMap<>();
+            this.mapping = mapping;
+            ServerSocket socket = null;
+            for (int i = PORT_BASE; i < PORT_BASE + NUM_PORTS_MAX; i++) {
+                try {
+                    socket = new ServerSocket(i);
+                } catch (IOException ignore) {
+                }
+            }
+            if (socket == null) {
+                throw new IOException("Unable to open server in port range(" + 
PORT_BASE + '-' + PORT_BASE + NUM_PORTS_MAX + ')');
+            }
+            socket.setReuseAddress(true);
+            serverSocket = socket;
+        }
+
+        public int getLocalPort() {
+            return serverSocket.getLocalPort();
+        }
+
+        public void close() {
+            try {
+                serverSocket.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            for (AdsTcpPlcConnection adsTcpPlcConnection : mapping.values()) {
+                
adsTcpPlcConnection.getChannel().pipeline().get(Plc4x2AdsProtocol.class).removeConsumer(this);
+            }
+        }
+
+        public Integer getSoTimeout() {
+            try {
+                return serverSocket.getSoTimeout();
+            } catch (IOException e) {
+                e.printStackTrace();
+                return -1;
+            }
+        }
+
+        public void setSoTimeout(int soTimeout) {
+            try {
+                serverSocket.setSoTimeout(soTimeout);
+            } catch (SocketException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public void AddNotification(Long notificationHandle, Notification 
notify) {
+            notificationMap.put(notificationHandle, notify);
+            AdsTcpPlcConnection connection = mapping.get(notify.pAddr.left);
+            // TODO: filter for addr
+            
connection.getChannel().pipeline().get(Plc4x2AdsProtocol.class).addConsumer(this);
+        }
+
+        public void DelNotification(ImmutablePair<AmsNetId, AmsPort> pAddr, 
NotificationHandle notificationHandle) {
+            // Note. pAddr is not used for anything.
+            notificationMap.remove(notificationHandle.getAsLong());
+            AdsTcpPlcConnection connection = mapping.get(pAddr.left);
+            // TODO: filter for addr
+            
connection.getChannel().pipeline().get(Plc4x2AdsProtocol.class).addConsumer(this);
+        }
+
+        @Override
+        public void accept(AdsDeviceNotificationRequest 
adsDeviceNotificationRequest) {
+            for (AdsStampHeader adsStampHeader : 
adsDeviceNotificationRequest.getAdsStampHeaders()) {
+                TimeStamp timeStamp = adsStampHeader.getTimeStamp();
+                for (AdsNotificationSample adsNotificationSample : 
adsStampHeader.getAdsNotificationSamples()) {
+                    NotificationHandle notificationHandle = 
adsNotificationSample.getNotificationHandle();
+                    Notification notification = 
notificationMap.get(notificationHandle.getAsLong());
+                    notification.pFunc.notifyCallback(notification.pAddr, 
timeStamp, adsNotificationSample, notification.hUser);
+                }
+            }
+        }
+    }
+
+
+}
diff --git 
a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/Output.java
 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/Output.java
new file mode 100644
index 0000000..a9dd1ec
--- /dev/null
+++ 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/Output.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.ads.adslib;
+
+public final class Output<T> {
+
+    public T value;
+
+    public Output() {
+    }
+
+    public Output(T value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "Output{" +
+            "value=" + value +
+            '}';
+    }
+}
diff --git 
a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/package-info.java
 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/package-info.java
new file mode 100644
index 0000000..fd5b92a
--- /dev/null
+++ 
b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/package-info.java
@@ -0,0 +1,24 @@
+/*
+ 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.
+ */
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS/AdsLib";>github</a>
+ * <p>
+ * The purpose of this is to validate the plc4x implementation against a 
reference test.
+ */
+package org.apache.plc4x.java.ads.adslib;
\ No newline at end of file
diff --git a/plc4j/protocols/ads/src/test/resources/logback.xml 
b/plc4j/protocols/ads/src/test/resources/logback.xml
index 27d40c0..dd243bd 100644
--- a/plc4j/protocols/ads/src/test/resources/logback.xml
+++ b/plc4j/protocols/ads/src/test/resources/logback.xml
@@ -19,7 +19,9 @@
 -->
 <configuration xmlns="http://ch.qos.logback/xml/ns/logback";
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-               xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback 
https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd";>
+               xsi:schemaLocation="
+                  http://ch.qos.logback/xml/ns/logback
+                  
https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd";>
 
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
     <!-- encoders are assigned the type
diff --git 
a/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
 
b/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
index 00b660b..3fbfd2a 100644
--- 
a/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
+++ 
b/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
@@ -57,4 +57,7 @@ public class TcpSocketChannelFactory implements 
ChannelFactory {
         }
     }
 
+    public InetAddress getAddress() {
+        return address;
+    }
 }

-- 
To stop receiving notification emails like this one, please contact
sru...@apache.org.

Reply via email to