You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2019/11/06 19:26:00 UTC

[plc4x] branch feature/knxnet-ip created (now e6557fe)

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

cdutz pushed a change to branch feature/knxnet-ip
in repository https://gitbox.apache.org/repos/asf/plc4x.git.


      at e6557fe  - Implemented more of the KNXNet/IP protocol (Fixed some issue with CEMIMPropReadCon) - Implemented the skeleton of the PLC4X driver - Implemented a manual test, that replays a PCAP recording to test the driver

This branch includes the following new commits:

     new e6557fe  - Implemented more of the KNXNet/IP protocol (Fixed some issue with CEMIMPropReadCon) - Implemented the skeleton of the PLC4X driver - Implemented a manual test, that replays a PCAP recording to test the driver

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[plc4x] 01/01: - Implemented more of the KNXNet/IP protocol (Fixed some issue with CEMIMPropReadCon) - Implemented the skeleton of the PLC4X driver - Implemented a manual test, that replays a PCAP recording to test the driver

Posted by cd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch feature/knxnet-ip
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit e6557fe2bc0244c37aff1237ef71b6a9c4f7f6cd
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Wed Nov 6 20:25:47 2019 +0100

    - Implemented more of the KNXNet/IP protocol (Fixed some issue with CEMIMPropReadCon)
    - Implemented the skeleton of the PLC4X driver
    - Implemented a manual test, that replays a PCAP recording to test the driver
---
 .../resources/protocols/knxnetip/knxnetip.mspec    |   1 +
 .../plc4x/java/bacnetip/PassiveBacNetIpDriver.java |   6 +-
 sandbox/test-java-knxnetip-driver/pom.xml          |  40 +++----
 .../java/knxnetip/PassiveKnxNetIpDriver.java}      |  28 ++---
 .../knxnetip/connection/KnxNetIpFieldHandler.java  |  36 +++++++
 .../connection/PassiveKnxNetIpPlcConnection.java   | 116 +++++++++++++++++++++
 .../plc4x/java/knxnetip/model/KnxNetIpField.java   |  47 +++++++++
 .../java/knxnetip/protocol/HelloWorldProtocol.java |  72 +++++++++++++
 .../java/knxnetip/protocol/KnxNetIpProtocol.java   |  59 +++++++++++
 .../services/org.apache.plc4x.java.spi.PlcDriver   |  19 ++++
 .../apache/plc4x/java/knxnetip/ManualKnxNetIp.java |  69 ++++++++++++
 11 files changed, 457 insertions(+), 36 deletions(-)

diff --git a/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec b/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
index 23b5f0e..998f8ed 100644
--- a/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
+++ b/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
@@ -257,6 +257,7 @@
             [simple uint  8 'propertyId']
             [simple uint  4 'numberOfElements']
             [simple uint 12 'startIndex']
+            [simple uint 16 'unknown']
         ]
     ]
 ]
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java
index 4041640..028972c 100644
--- a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java
+++ b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java
@@ -40,12 +40,12 @@ public class PassiveBacNetIpDriver implements PlcDriver {
 
     @Override
     public String getProtocolCode() {
-        return "bacnet-ip";
+        return "bacnet-ip-passive";
     }
 
     @Override
     public String getProtocolName() {
-        return "BACnet/IP";
+        return "BACnet/IP (Passive)";
     }
 
     @Override
@@ -53,7 +53,7 @@ public class PassiveBacNetIpDriver implements PlcDriver {
         Matcher matcher = PASSIVE_BACNET_IP_URI_PATTERN.matcher(url);
         if (!matcher.matches()) {
             throw new PlcConnectionException(
-                "Connection url doesn't match the format 'bacnet-ip://{host|ip}'");
+                "Connection url doesn't match the format 'bacnet-ip-passive://{host|ip}'");
         }
         String networkDevice = matcher.group("networkDevice");
 
diff --git a/sandbox/test-java-knxnetip-driver/pom.xml b/sandbox/test-java-knxnetip-driver/pom.xml
index f25c33e..3b3bec4 100644
--- a/sandbox/test-java-knxnetip-driver/pom.xml
+++ b/sandbox/test-java-knxnetip-driver/pom.xml
@@ -56,43 +56,45 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-utils-driver-base-java</artifactId>
+      <artifactId>plc4j-api</artifactId>
       <version>0.6.0-SNAPSHOT</version>
     </dependency>
     <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-annotations</artifactId>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-driver-base-java</artifactId>
+      <version>0.6.0-SNAPSHOT</version>
     </dependency>
-
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-utils-protocol-test-utils</artifactId>
+      <artifactId>plc4j-protocol-driver-base</artifactId>
       <version>0.6.0-SNAPSHOT</version>
-      <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>commons-codec</groupId>
-      <artifactId>commons-codec</artifactId>
-      <scope>test</scope>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-protocol-driver-base-raw-socket</artifactId>
+      <version>0.6.0-SNAPSHOT</version>
     </dependency>
+
     <dependency>
-      <groupId>com.fasterxml.jackson.dataformat</groupId>
-      <artifactId>jackson-dataformat-xml</artifactId>
-      <scope>test</scope>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
     </dependency>
+
     <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-api</artifactId>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-protocol-test-utils</artifactId>
+      <version>0.6.0-SNAPSHOT</version>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-engine</artifactId>
-      <scope>test</scope>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-protocol-driver-base-pcap-socket</artifactId>
+      <version>0.6.0-SNAPSHOT</version>
+      <!--scope>test</scope-->
     </dependency>
     <dependency>
-      <groupId>org.junit.platform</groupId>
-      <artifactId>junit-platform-launcher</artifactId>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
       <scope>test</scope>
     </dependency>
 
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/PassiveKnxNetIpDriver.java
similarity index 71%
copy from sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java
copy to sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/PassiveKnxNetIpDriver.java
index 4041640..1f17192 100644
--- a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriver.java
+++ b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/PassiveKnxNetIpDriver.java
@@ -16,13 +16,13 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 */
-package org.apache.plc4x.java.bacnetip;
+package org.apache.plc4x.java.knxnetip;
 
 import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.authentication.PlcAuthentication;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
-import org.apache.plc4x.java.bacnetip.connection.PassiveBacNetIpPlcConnection;
-import org.apache.plc4x.java.bacnetip.protocol.HelloWorldProtocol;
+import org.apache.plc4x.java.knxnetip.connection.PassiveKnxNetIpPlcConnection;
+import org.apache.plc4x.java.knxnetip.protocol.HelloWorldProtocol;
 import org.apache.plc4x.java.spi.PlcDriver;
 import org.apache.plc4x.java.utils.rawsockets.netty.RawSocketIpAddress;
 
@@ -31,29 +31,29 @@ import java.util.regex.Pattern;
 
 import static org.apache.plc4x.java.utils.rawsockets.netty.RawSocketAddress.ALL_PROTOCOLS;
 
-public class PassiveBacNetIpDriver implements PlcDriver {
+public class PassiveKnxNetIpDriver implements PlcDriver {
 
-    public static final int BACNET_IP_PORT = 47808;
+    public static final int KNXNET_IP_PORT = 3671;
 
-    private static final Pattern PASSIVE_BACNET_IP_URI_PATTERN =
-        Pattern.compile("^bachnet-ip-passive://(?<networkDevice>.*)(?<params>\\?.*)?");
+    private static final Pattern PASSIVE_KNXNET_IP_URI_PATTERN =
+        Pattern.compile("^knxnet-ip-passive://(?<networkDevice>.*)(?<params>\\?.*)?");
 
     @Override
     public String getProtocolCode() {
-        return "bacnet-ip";
+        return "knxnet-ip-passive";
     }
 
     @Override
     public String getProtocolName() {
-        return "BACnet/IP";
+        return "KNXNet/IP (Passive)";
     }
 
     @Override
     public PlcConnection connect(String url) throws PlcConnectionException {
-        Matcher matcher = PASSIVE_BACNET_IP_URI_PATTERN.matcher(url);
+        Matcher matcher = PASSIVE_KNXNET_IP_URI_PATTERN.matcher(url);
         if (!matcher.matches()) {
             throw new PlcConnectionException(
-                "Connection url doesn't match the format 'bacnet-ip://{host|ip}'");
+                "Connection url doesn't match the format 'knxnet-ip-passive://{host|ip}'");
         }
         String networkDevice = matcher.group("networkDevice");
 
@@ -61,8 +61,8 @@ public class PassiveBacNetIpDriver implements PlcDriver {
 
         try {
             RawSocketIpAddress rawSocketAddress = new RawSocketIpAddress(
-                networkDevice, ALL_PROTOCOLS, null, BACNET_IP_PORT);
-            return new PassiveBacNetIpPlcConnection(rawSocketAddress, params, new HelloWorldProtocol());
+                networkDevice, ALL_PROTOCOLS, null, KNXNET_IP_PORT);
+            return new PassiveKnxNetIpPlcConnection(rawSocketAddress, params, new HelloWorldProtocol());
         } catch (Exception e) {
             throw new PlcConnectionException("Error connecting to host", e);
         }
@@ -70,7 +70,7 @@ public class PassiveBacNetIpDriver implements PlcDriver {
 
     @Override
     public PlcConnection connect(String url, PlcAuthentication authentication) throws PlcConnectionException {
-        throw new PlcConnectionException("BACnet/IP connections don't support authentication.");
+        throw new PlcConnectionException("KNXNet/IP connections don't support authentication.");
     }
 
 }
diff --git a/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/connection/KnxNetIpFieldHandler.java b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/connection/KnxNetIpFieldHandler.java
new file mode 100644
index 0000000..6e6996d
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/connection/KnxNetIpFieldHandler.java
@@ -0,0 +1,36 @@
+/*
+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.knxnetip.connection;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.base.connection.DefaultPlcFieldHandler;
+import org.apache.plc4x.java.knxnetip.model.KnxNetIpField;
+
+public class KnxNetIpFieldHandler extends DefaultPlcFieldHandler {
+
+    @Override
+    public PlcField createField(String fieldQuery) throws PlcInvalidFieldException {
+        if (KnxNetIpField.matches(fieldQuery)) {
+            return KnxNetIpField.of(fieldQuery);
+        }
+        throw new PlcInvalidFieldException(fieldQuery);
+    }
+
+}
diff --git a/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/connection/PassiveKnxNetIpPlcConnection.java b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/connection/PassiveKnxNetIpPlcConnection.java
new file mode 100644
index 0000000..e82c7df
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/connection/PassiveKnxNetIpPlcConnection.java
@@ -0,0 +1,116 @@
+/*
+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.knxnetip.connection;
+
+import io.netty.channel.*;
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.base.connection.ChannelFactory;
+import org.apache.plc4x.java.base.connection.NettyPlcConnection;
+import org.apache.plc4x.java.base.connection.RawSocketChannelFactory;
+import org.apache.plc4x.java.base.events.ConnectedEvent;
+import org.apache.plc4x.java.base.messages.*;
+import org.apache.plc4x.java.knxnetip.model.KnxNetIpField;
+import org.apache.plc4x.java.knxnetip.protocol.KnxNetIpProtocol;
+import org.apache.plc4x.java.utils.rawsockets.netty.RawSocketAddress;
+import org.apache.plc4x.java.utils.rawsockets.netty.RawSocketIpAddress;
+import org.apache.plc4x.java.utils.rawsockets.netty.UdpIpPacketHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CompletableFuture;
+
+public class PassiveKnxNetIpPlcConnection extends NettyPlcConnection implements PlcReader {
+
+    private static final Logger logger = LoggerFactory.getLogger(PassiveKnxNetIpPlcConnection.class);
+
+    private final ChannelHandler handler;
+
+    public PassiveKnxNetIpPlcConnection(RawSocketIpAddress address, String params, ChannelHandler handler) {
+        this(new RawSocketChannelFactory(address.getDeviceName(), null,
+            address.getPort(), RawSocketAddress.ALL_PROTOCOLS, new UdpIpPacketHandler()), params, handler);
+    }
+
+    public PassiveKnxNetIpPlcConnection(ChannelFactory channelFactory, String params, ChannelHandler handler) {
+        super(channelFactory, true);
+        this.handler = handler;
+    }
+
+    @Override
+    protected void sendChannelCreatedEvent() {
+        // As this type of protocol doesn't require any form of connection, we just send the connected event.
+        channel.pipeline().fireUserEventTriggered(new ConnectedEvent());
+    }
+
+    @Override
+    public PlcField prepareField(String fieldQuery) throws PlcInvalidFieldException {
+        return KnxNetIpField.of(fieldQuery);
+    }
+
+    @Override
+    protected ChannelHandler getChannelHandler(CompletableFuture<Void> sessionSetupCompleteFuture) {
+        return new ChannelInitializer() {
+            @Override
+            protected void initChannel(Channel channel) {
+                // Build the protocol stack for communicating with the s7 protocol.
+                ChannelPipeline pipeline = channel.pipeline();
+                pipeline.addLast(new ChannelInboundHandlerAdapter() {
+                    @Override
+                    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+                        if (evt instanceof ConnectedEvent) {
+                            sessionSetupCompleteFuture.complete(null);
+                        } else {
+                            super.userEventTriggered(ctx, evt);
+                        }
+                    }
+                });
+                pipeline.addLast(new KnxNetIpProtocol());
+                pipeline.addLast(handler);
+            }
+        };
+    }
+
+    @Override
+    public boolean canRead() {
+        return true;
+    }
+
+    @Override
+    public PlcReadRequest.Builder readRequestBuilder() {
+        return new DefaultPlcReadRequest.Builder(this, new KnxNetIpFieldHandler());
+    }
+
+    @Override
+    public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
+        InternalPlcReadRequest internalReadRequest = checkInternal(readRequest, InternalPlcReadRequest.class);
+        CompletableFuture<InternalPlcReadResponse> future = new CompletableFuture<>();
+        PlcRequestContainer<InternalPlcReadRequest, InternalPlcReadResponse> container =
+            new PlcRequestContainer<>(internalReadRequest, future);
+        channel.writeAndFlush(container).addListener(f -> {
+            if (!f.isSuccess()) {
+                future.completeExceptionally(f.cause());
+            }
+        });
+        return future
+            .thenApply(PlcReadResponse.class::cast);
+    }
+
+}
diff --git a/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/model/KnxNetIpField.java b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/model/KnxNetIpField.java
new file mode 100644
index 0000000..fbf207a
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/model/KnxNetIpField.java
@@ -0,0 +1,47 @@
+/*
+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.knxnetip.model;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.model.PlcField;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class KnxNetIpField implements PlcField {
+
+    private static final Pattern ADDRESS_PATTERN =
+        Pattern.compile("^N(?<fileNumber>\\d{1,7}):(?<elementNumber>\\d{1,7})(/(?<bitNumber>\\d{1,7}))?:(?<dataType>[a-zA-Z_]+)(\\[(?<size>\\d+)])?");
+
+    public KnxNetIpField() {
+    }
+
+    public static boolean matches(String fieldString) {
+        return ADDRESS_PATTERN.matcher(fieldString).matches();
+    }
+
+    public static KnxNetIpField of(String fieldString) {
+        Matcher matcher = ADDRESS_PATTERN.matcher(fieldString);
+        if(matcher.matches()) {
+            return new KnxNetIpField();
+        }
+        throw new PlcInvalidFieldException("Unable to parse address: " + fieldString);
+    }
+
+}
diff --git a/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/protocol/HelloWorldProtocol.java b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/protocol/HelloWorldProtocol.java
new file mode 100644
index 0000000..6bd481a
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/protocol/HelloWorldProtocol.java
@@ -0,0 +1,72 @@
+/*
+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.knxnetip.protocol;
+
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.plc4x.java.base.PlcMessageToMessageCodec;
+import org.apache.plc4x.java.base.messages.PlcRequestContainer;
+import org.apache.plc4x.java.knxnetip.readwrite.KNXNetIPMessage;
+
+import java.util.List;
+
+public class HelloWorldProtocol extends PlcMessageToMessageCodec<KNXNetIPMessage, PlcRequestContainer> {
+
+    private int packetCount = 0;
+    private long startTime = -1;
+
+    @Override
+    protected void encode(ChannelHandlerContext channelHandlerContext, PlcRequestContainer plcRequestContainer, List<Object> list) throws Exception {
+        System.out.println(plcRequestContainer);
+    }
+
+    @Override
+    protected void decode(ChannelHandlerContext channelHandlerContext, KNXNetIPMessage packet, List<Object> list) throws Exception {
+        if(startTime == -1) {
+            startTime = System.currentTimeMillis();
+        }
+        packetCount++;
+        if(packetCount % 10000 == 0) {
+            long curTime = System.currentTimeMillis();
+            System.out.println("Read " + packetCount + " packets in " + (curTime - startTime) + "ms");
+        }
+/*        if(packet instanceof BVLCOriginalUnicastNPDU) {
+            final NPDU npdu = ((BVLCOriginalUnicastNPDU) packet).getNpdu();
+            final APDU apdu = npdu.getApdu();
+            if(apdu instanceof APDUSimpleAck) {
+                APDUSimpleAck ack = (APDUSimpleAck) apdu;
+                System.out.println("Simple-ACK(" + ack.getOriginalInvokeId() + ")");
+            } else if(apdu instanceof APDUConfirmedRequest) {
+                APDUConfirmedRequest request = (APDUConfirmedRequest) apdu;
+                final BACnetConfirmedServiceRequest serviceRequest = request.getServiceRequest();
+                if(serviceRequest instanceof BACnetConfirmedServiceRequestConfirmedCOVNotification) {
+                    BACnetConfirmedServiceRequestConfirmedCOVNotification covNotification = (BACnetConfirmedServiceRequestConfirmedCOVNotification) serviceRequest;
+                    final BACnetTagWithContent[] notifications = covNotification.getNotifications();
+                    System.out.println("Simple-ACK(" + request.getInvokeId() + "): Confirmed COV Notification [" + notifications.length + "]");
+                } else {
+                    System.out.println("Simple-ACK(" + request.getInvokeId() + "): Other");
+                }
+            } else {
+                System.out.println("Other");
+            }
+        } else {
+            System.out.println("Other");
+        }*/
+    }
+
+}
diff --git a/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocol.java b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocol.java
new file mode 100644
index 0000000..d8e5cc9
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocol.java
@@ -0,0 +1,59 @@
+/*
+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.knxnetip.protocol;
+
+import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.base.GeneratedDriverByteToMessageCodec;
+import org.apache.plc4x.java.knxnetip.readwrite.KNXNetIPMessage;
+import org.apache.plc4x.java.knxnetip.readwrite.io.KNXNetIPMessageIO;
+import org.apache.plc4x.java.utils.MessageIO;
+import org.apache.plc4x.java.utils.ParseException;
+import org.apache.plc4x.java.utils.ReadBuffer;
+import org.apache.plc4x.java.utils.WriteBuffer;
+
+public class KnxNetIpProtocol extends GeneratedDriverByteToMessageCodec<KNXNetIPMessage> {
+
+    public KnxNetIpProtocol() {
+        super(new MessageIO<KNXNetIPMessage, KNXNetIPMessage>() {
+            @Override
+            public KNXNetIPMessage parse(ReadBuffer io) throws ParseException {
+                return KNXNetIPMessageIO.parse(io);
+            }
+
+            @Override
+            public void serialize(WriteBuffer io, KNXNetIPMessage value) throws ParseException {
+                KNXNetIPMessageIO.serialize(io, value);
+            }
+        });
+    }
+
+    @Override
+    protected int getPacketSize(ByteBuf byteBuf) {
+        if(byteBuf.readableBytes() >= 6) {
+            return byteBuf.getUnsignedShort(byteBuf.readerIndex() + 4);
+        }
+        return -1;
+    }
+
+    @Override
+    protected void removeRestOfCorruptPackage(ByteBuf byteBuf) {
+        // Not really any marker byte to look for.
+    }
+
+}
diff --git a/sandbox/test-java-knxnetip-driver/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver b/sandbox/test-java-knxnetip-driver/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
new file mode 100644
index 0000000..afe85bf
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+org.apache.plc4x.java.knxnetip.PassiveKnxNetIpDriver
diff --git a/sandbox/test-java-knxnetip-driver/src/test/java/org/apache/plc4x/java/knxnetip/ManualKnxNetIp.java b/sandbox/test-java-knxnetip-driver/src/test/java/org/apache/plc4x/java/knxnetip/ManualKnxNetIp.java
new file mode 100644
index 0000000..a1ac874
--- /dev/null
+++ b/sandbox/test-java-knxnetip-driver/src/test/java/org/apache/plc4x/java/knxnetip/ManualKnxNetIp.java
@@ -0,0 +1,69 @@
+/*
+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.knxnetip;
+
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.base.PlcMessageToMessageCodec;
+import org.apache.plc4x.java.base.connection.NettyPlcConnection;
+import org.apache.plc4x.java.base.connection.PcapChannelFactory;
+import org.apache.plc4x.java.base.messages.PlcRequestContainer;
+import org.apache.plc4x.java.knxnetip.connection.PassiveKnxNetIpPlcConnection;
+import org.apache.plc4x.java.knxnetip.readwrite.KNXNetIPMessage;
+import org.apache.plc4x.java.utils.pcapsockets.netty.PcapSocketAddress;
+import org.apache.plc4x.java.utils.pcapsockets.netty.PcapSocketChannelConfig;
+import org.apache.plc4x.java.utils.pcapsockets.netty.UdpIpPacketHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.List;
+
+public class ManualKnxNetIp {
+
+    private static final Logger logger = LoggerFactory.getLogger(ManualKnxNetIp.class);
+
+    private static NettyPlcConnection connection;
+
+    public static void main(String[] args) throws Exception {
+        try {
+            connection = new PassiveKnxNetIpPlcConnection(new PcapChannelFactory(
+                new File(args[0]), null,
+                PassiveKnxNetIpDriver.KNXNET_IP_PORT, PcapSocketAddress.ALL_PROTOCOLS,
+                PcapSocketChannelConfig.SPEED_REALTIME, new UdpIpPacketHandler()), "",
+                new PlcMessageToMessageCodec<KNXNetIPMessage, PlcRequestContainer>() {
+
+                    @Override
+                    protected void decode(ChannelHandlerContext channelHandlerContext, KNXNetIPMessage packet, List<Object> list) throws Exception {
+                        System.out.println(packet);
+                    }
+
+                    @Override
+                    protected void encode(ChannelHandlerContext ctx, PlcRequestContainer msg, List<Object> out) throws Exception {
+                        // Ignore this as we don't send anything.
+                    }
+                });
+            connection.connect();
+        } catch (PlcConnectionException e) {
+            logger.error("An error occurred starting the BACnet/IP driver", e);
+            throw new Exception(e);
+        }
+    }
+
+}