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

cdutz pushed a commit to branch refactor/spi3
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 0238c9f7ef7719ecf3a366126c357dea47682942
Author: Christofer Dutz <[email protected]>
AuthorDate: Fri Mar 20 09:08:53 2026 +0100

    feat: Re-Implemented the pcap-replay transport.
---
 .../java/bacnetip/readwrite/BACnetVendorId.java    |  22 +
 .../java/knxnetip/readwrite/KnxManufacturer.java   |  22 +-
 plc4j/transports/pcap-replay/pom.xml               |  60 +-
 .../DefaultPcapReplayTransportConfiguration.java   |  77 --
 .../java/transport/pcapreplay/PcapFilePlayer.java  | 329 +++++++++
 .../pcapreplay/PcapReplayChannelFactory.java       |  78 --
 .../transport/pcapreplay/PcapReplayTransport.java  |  42 +-
 .../PcapReplayTransportConfiguration.java          |  31 -
 .../pcapreplay/PcapReplayTransportInstance.java    | 488 +++++++++++++
 .../config/PcapReplayTransportConfiguration.java   | 149 ++++
 .../transport/pcapreplay/PcapFilePlayerTest.java   | 688 ++++++++++++++++++
 .../PcapReplayTransportInstanceTest.java           | 782 +++++++++++++++++++++
 .../pcapreplay/PcapReplayTransportTest.java        | 206 ++++++
 .../PcapReplayTransportConfigurationTest.java      | 298 ++++++++
 .../src/test/resources/generate_test_pcap.py       | 123 ++++
 .../pcap-replay/src/test/resources/test.pcap       | Bin 0 -> 328 bytes
 plc4j/transports/pom.xml                           |   4 +-
 17 files changed, 3170 insertions(+), 229 deletions(-)

diff --git 
a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java
 
b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java
index 510bcd7d8a..3ea55e3736 100644
--- 
a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java
+++ 
b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java
@@ -3195,6 +3195,28 @@ public enum BACnetVendorId {
 
   ALLBEEBV((int) 1594, (int) 1594, "Allbee B.V."),
 
+  KAMPMANN_GMBH_COKG((int) 1595, (int) 1595, "Kampmann GmbH & Co. KG"),
+
+  COSEL_ELEKTRONIK_OTOMASYON_SISTEMLERI_SAN_TIC_LTD_STI((int) 1596, (int) 
1596, "Cosel Elektronik Otomasyon Sistemleri San. Tic. Ltd. Sti."),
+
+  RACTL_DESIGNS((int) 1597, (int) 1597, "Ractl Designs"),
+
+  DATABRIDGE_DYNAMIC_PVT_LTD((int) 1598, (int) 1598, "Databridge Dynamic Pvt 
Ltd"),
+
+  EVOLO_TECHNOLOGIESAS((int) 1599, (int) 1599, "Evolo Technologies AS"),
+
+  ENERGY_PLUS_TEKNOLOJI_IMALAT_ITHALATVE_IHRACAT_SANAYIAS((int) 1600, (int) 
1600, "EnergyPlus Teknoloji Imalat Ithalat ve Ihracat Sanayi AS"),
+
+  GAIAMESH_INC((int) 1601, (int) 1601, "Gaiamesh Inc."),
+
+  LOVATO_ELECTRICSPA((int) 1602, (int) 1602, "Lovato Electric S.P.A."),
+
+  QPAC_SYSTEMS_INC((int) 1603, (int) 1603, "Q-PAC Systems, Inc."),
+
+  YOBIIQ_INTELLIGENCEBV((int) 1604, (int) 1604, "Yobiiq Intelligence B.V."),
+
+  CAMFILUSA_INC((int) 1605, (int) 1605, "Camfil USA Inc."),
+
   UNKNOWN_VENDOR((int) 0xFFFF, (int) 0xFFFF, "Unknown");
 
   private static final Map<Integer, BACnetVendorId> map;
diff --git 
a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java
 
b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java
index 07de37d900..d13bbd7439 100644
--- 
a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java
+++ 
b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java
@@ -1579,7 +1579,7 @@ public enum KnxManufacturer {
 
   M_LEXA_LIFESTYLE((int) 776, (int) 837, "Lexa LIFESTYLE"),
 
-  M_CASAMBI((int) 777, (int) 838, "casambi"),
+  M_CASAMBI_TECHNOLOGIES_OY((int) 777, (int) 838, "Casambi Technologies Oy"),
 
   M_BELCAB_UK_LTD((int) 778, (int) 839, "BELCAB UK LTD"),
 
@@ -1591,9 +1591,25 @@ public enum KnxManufacturer {
 
   M_OCCHIO_GMBH((int) 782, (int) 843, "Occhio GmbH"),
 
-  M_ABB___RESERVED((int) 783, (int) 43954, "ABB - reserved"),
+  M_INSTELL((int) 783, (int) 844, "Instell"),
 
-  M_BUSCH_JAEGER_ELEKTRO___RESERVED((int) 784, (int) 43959, "Busch-Jaeger 
Elektro - reserved");
+  M_VOKA_VOGTLAENDISCHES_KABELWERK_GMBH((int) 784, (int) 845, "VOKA 
Vogtländisches Kabelwerk GmbH"),
+
+  M_QINGDAO_MAISI_INTELLIGENT_TECHNOLOGY_CO__LTD((int) 785, (int) 846, 
"Qingdao Maisi Intelligent Technology Co.,Ltd"),
+
+  M_CHONGQING_MEXIN_MESSON_DOORS_INDUSTRY_CO___LTD__((int) 786, (int) 847, 
"Chongqing Mexin Messon Doors Industry Co., Ltd.,"),
+
+  M_HOLFMANN_CULTURE_ART_CO__LTD((int) 787, (int) 848, "Holfmann Culture Art 
Co.,LTD"),
+
+  M_GUANGZHOU_BAITE_IOT_TECHNOLOGY_CO___LTD((int) 788, (int) 849, "Guangzhou 
Baite IoT Technology Co., Ltd"),
+
+  M_OWL_AUTOMATA((int) 789, (int) 850, "OWL Automata"),
+
+  M_AUTOMATED_PTY_LTD((int) 790, (int) 851, "Automated Pty Ltd"),
+
+  M_ABB___RESERVED((int) 791, (int) 43954, "ABB - reserved"),
+
+  M_BUSCH_JAEGER_ELEKTRO___RESERVED((int) 792, (int) 43959, "Busch-Jaeger 
Elektro - reserved");
 
   private static final Map<Integer, KnxManufacturer> map;
 
diff --git a/plc4j/transports/pcap-replay/pom.xml 
b/plc4j/transports/pcap-replay/pom.xml
index 89ddb0e30c..e454f4f92f 100644
--- a/plc4j/transports/pcap-replay/pom.xml
+++ b/plc4j/transports/pcap-replay/pom.xml
@@ -38,50 +38,70 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-api</artifactId>
+      <artifactId>plc4j-transports-api</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-spi</artifactId>
+      <artifactId>plc4j-spi-config</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-transport-pcap-shared</artifactId>
+      <artifactId>plc4j-spi-utils</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-utils-pcap-shared</artifactId>
+      <artifactId>plc4j-utils-audit-log-api</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
+
+    <!-- Logging -->
     <dependency>
-      <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-utils-pcap-replay</artifactId>
-      <version>0.14.0-SNAPSHOT</version>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <!-- Pcap4J for reading pcap files -->
+    <dependency>
+      <groupId>org.pcap4j</groupId>
+      <artifactId>pcap4j-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.pcap4j</groupId>
+      <artifactId>pcap4j-packetfactory-static</artifactId>
     </dependency>
 
+    <!-- Test Dependencies -->
     <dependency>
-      <groupId>io.netty</groupId>
-      <artifactId>netty-transport</artifactId>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <scope>test</scope>
     </dependency>
   </dependencies>
+
   <build>
     <plugins>
-      <!--
-       Generate an OSGI compatible MANIFEST file.
-     -->
       <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
         <configuration>
-          <instructions>
-            
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
-            
<Bundle-Activator>org.apache.plc4x.java.osgi.TransportActivator</Bundle-Activator>
-            
<Export-Service>org.apache.plc4x.java.spi.transport.Transport,org.apache.plc4x.java.transport.pcapreplay.PcapReplayTransport</Export-Service>
-          </instructions>
+          <usedDependencies>
+            
<usedDependency>org.pcap4j:pcap4j-packetfactory-static</usedDependency>
+          </usedDependencies>
         </configuration>
       </plugin>
     </plugins>
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/DefaultPcapReplayTransportConfiguration.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/DefaultPcapReplayTransportConfiguration.java
deleted file mode 100644
index 3994dfbb6c..0000000000
--- 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/DefaultPcapReplayTransportConfiguration.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.plc4x.java.transport.pcapreplay;
-
-import 
org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
-import org.apache.plc4x.java.spi.configuration.annotations.Description;
-import 
org.apache.plc4x.java.spi.configuration.annotations.defaults.BooleanDefaultValue;
-import 
org.apache.plc4x.java.spi.configuration.annotations.defaults.FloatDefaultValue;
-import org.apache.plc4x.java.transport.pcap.DefaultPcapTransportConfiguration;
-
-public abstract class DefaultPcapReplayTransportConfiguration extends 
DefaultPcapTransportConfiguration implements PcapReplayTransportConfiguration {
-
-    @ConfigurationParameter("replay-speed-factor")
-    @FloatDefaultValue(1.0f)
-    @Description("Numeric value for changing the replay speed:\n" +
-        "- 1 = Normal speed (default)\n" +
-        "- 0 = Maximum speed\n" +
-        "- 0.5 = Half speed\n" +
-        "- 2 = Double speed")
-    private float replaySpeedFactor;
-
-    @ConfigurationParameter("loop")
-    @BooleanDefaultValue(false)
-    @Description("Should the replay start at the beginning of the file as soon 
as the end is reached?\n" +
-        "- true = Automatically start again\n" +
-        "- false = Stop at the end (default)")
-    private boolean loop;
-
-    @ConfigurationParameter("filter")
-    @Description("Filter expression used to filter out unwanted packets from 
the replay.")
-    private String filter;
-
-    @Override
-    public float getReplaySpeedFactor() {
-        return replaySpeedFactor;
-    }
-
-    public void setReplaySpeedFactor(float replaySpeedFactor) {
-        this.replaySpeedFactor = replaySpeedFactor;
-    }
-
-    @Override
-    public boolean isLoop() {
-        return loop;
-    }
-
-    public void setLoop(boolean loop) {
-        this.loop = loop;
-    }
-
-    @Override
-    public String getFilter() {
-        return filter;
-    }
-
-    public void setFilter(String filter) {
-        this.filter = filter;
-    }
-
-}
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapFilePlayer.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapFilePlayer.java
new file mode 100644
index 0000000000..583f6b4bb7
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapFilePlayer.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay;
+
+import org.pcap4j.core.*;
+import org.pcap4j.packet.EthernetPacket;
+import org.pcap4j.packet.Packet;
+import org.pcap4j.util.MacAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Plays back packets from a PCAP file, simulating network traffic.
+ * Supports speed control, looping, and MAC-address/protocol filtering.
+ * Can read from both filesystem paths and classpath resources.
+ */
+public class PcapFilePlayer {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PcapFilePlayer.class);
+
+    private final String pcapFilePath;
+    private final MacAddress localMac;
+    private final MacAddress remoteMac;
+    private final int protocolId;
+    private final double speedFactor;
+    private final boolean loop;
+    private final boolean onlyIncoming;
+    private final boolean onlyOutgoing;
+    private final int queueSize;
+    private Path tempPcapFile; // Temporary file for classpath resources
+
+    private final BlockingQueue<byte[]> packetQueue;
+    private final AtomicBoolean playing = new AtomicBoolean(false);
+    private final AtomicBoolean stopped = new AtomicBoolean(false);
+    private final AtomicLong packetsReplayed = new AtomicLong(0);
+    private Thread playerThread;
+
+    public PcapFilePlayer(String pcapFilePath, MacAddress localMac, MacAddress 
remoteMac,
+                          int protocolId, double speedFactor, boolean loop,
+                          boolean onlyIncoming, boolean onlyOutgoing, int 
queueSize) {
+        LOGGER.debug("PcapFilePlayer: Pre Java 21 version");
+        this.pcapFilePath = resolvePcapFile(pcapFilePath);
+        this.localMac = localMac;
+        this.remoteMac = remoteMac;
+        this.protocolId = protocolId;
+        this.speedFactor = speedFactor;
+        this.loop = loop;
+        this.onlyIncoming = onlyIncoming;
+        this.onlyOutgoing = onlyOutgoing;
+        this.queueSize = queueSize;
+        this.packetQueue = new LinkedBlockingQueue<>(queueSize);
+    }
+
+    /**
+     * Starts replaying the PCAP file.
+     */
+    public void start() {
+        if (playing.compareAndSet(false, true)) {
+            stopped.set(false);
+            playerThread = new Thread(this::playbackLoop);
+            playerThread.start();
+            LOGGER.info("Started PCAP replay from: {}", pcapFilePath);
+        }
+    }
+
+    /**
+     * Stops replaying the PCAP file.
+     */
+    public void stop() {
+        if (stopped.compareAndSet(false, true)) {
+            playing.set(false);
+            if (playerThread != null) {
+                playerThread.interrupt();
+            }
+            cleanupTempFile();
+            LOGGER.info("Stopped PCAP replay (replayed {} packets)", 
packetsReplayed.get());
+        }
+    }
+
+    /**
+     * Checks if the player is currently playing.
+     */
+    public boolean isPlaying() {
+        return playing.get();
+    }
+
+    /**
+     * Gets the next packet from the queue (blocking).
+     */
+    public byte[] getNextPacket(long timeout, TimeUnit unit) throws 
InterruptedException {
+        return packetQueue.poll(timeout, unit);
+    }
+
+    /**
+     * Gets the number of packets currently in the queue.
+     */
+    public int getQueueSize() {
+        return packetQueue.size();
+    }
+
+    /**
+     * Main playback loop.
+     */
+    private void playbackLoop() {
+        try {
+            do {
+                replayPcapFile();
+            } while (loop && !stopped.get());
+
+        } catch (Exception e) {
+            if (!stopped.get()) {
+                LOGGER.error("Error during PCAP replay", e);
+            }
+        } finally {
+            playing.set(false);
+            LOGGER.debug("PCAP playback thread stopped");
+        }
+    }
+
+    /**
+     * Replays the PCAP file once.
+     */
+    private void replayPcapFile() throws Exception {
+        PcapHandle handle = null;
+
+        try {
+            handle = Pcaps.openOffline(pcapFilePath, 
PcapHandle.TimestampPrecision.MICRO);
+
+            long previousTimestamp = -1;
+            Packet packet;
+
+            while (!stopped.get() && (packet = handle.getNextPacketEx()) != 
null) {
+                PcapHandle.TimestampPrecision precision = 
handle.getTimestampPrecision();
+                long timestamp = handle.getTimestamp().getTime() * 1000; // 
Convert to microseconds
+
+                // Handle timing for realistic replay
+                if (speedFactor > 0 && previousTimestamp >= 0) {
+                    long delay = (long) ((timestamp - previousTimestamp) / 
speedFactor);
+                    if (delay > 0) {
+                        TimeUnit.MICROSECONDS.sleep(delay);
+                    }
+                }
+                previousTimestamp = timestamp;
+
+                // Process Ethernet packet
+                if (packet.contains(EthernetPacket.class)) {
+                    EthernetPacket ethPacket = 
packet.get(EthernetPacket.class);
+
+                    if (matchesFilter(ethPacket)) {
+                        byte[] payload = extractPayload(ethPacket);
+                        if (payload != null && payload.length > 0) {
+                            // Try to add to queue, drop if full
+                            if (!packetQueue.offer(payload)) {
+                                LOGGER.warn("Packet queue full, dropping 
packet");
+                            } else {
+                                packetsReplayed.incrementAndGet();
+                                LOGGER.trace("Replayed packet: {} bytes", 
payload.length);
+                            }
+                        }
+                    }
+                }
+            }
+
+        } catch (EOFException e) {
+            LOGGER.debug("Reached end of PCAP file");
+        } finally {
+            if (handle != null && handle.isOpen()) {
+                handle.close();
+            }
+        }
+    }
+
+    /**
+     * Checks if a packet matches filter criteria.
+     */
+    private boolean matchesFilter(EthernetPacket ethPacket) {
+        EthernetPacket.EthernetHeader header = ethPacket.getHeader();
+
+        // Check protocol if specified
+        if (protocolId > 0) {
+            // Mask to 16 bits to avoid sign-extension from EtherType's signed 
short
+            int packetProto = header.getType().value() & 0xFFFF;
+            if (packetProto != protocolId) {
+                return false;
+            }
+        }
+
+        MacAddress src = header.getSrcAddr();
+        MacAddress dst = header.getDstAddr();
+
+        // Filter by direction
+        if (onlyIncoming && onlyOutgoing) {
+            // Both flags set - this is contradictory, reject
+            return false;
+        }
+
+        if (onlyIncoming) {
+            // Only packets from remote to local
+            return src.equals(remoteMac) && dst.equals(localMac);
+        } else if (onlyOutgoing) {
+            // Only packets from local to remote
+            return src.equals(localMac) && dst.equals(remoteMac);
+        } else {
+            // Any packet involving local or remote MAC addresses
+            return src.equals(localMac) || src.equals(remoteMac) ||
+                dst.equals(localMac) || dst.equals(remoteMac);
+        }
+    }
+
+    /**
+     * Extracts payload from an Ethernet packet.
+     */
+    private byte[] extractPayload(EthernetPacket ethPacket) {
+        Packet payload = ethPacket.getPayload();
+        if (payload != null) {
+            return payload.getRawData();
+        }
+        return null;
+    }
+
+    /**
+     * Gets statistics about replay.
+     */
+    public long getPacketsReplayed() {
+        return packetsReplayed.get();
+    }
+
+    /**
+     * Resolves a PCAP file path, handling both filesystem paths and classpath 
resources.
+     * If the path starts with "/", it's treated as a classpath resource and 
extracted to a temp file.
+     *
+     * @param pcapFile The PCAP file path (filesystem or classpath)
+     * @return The resolved filesystem path
+     */
+    private String resolvePcapFile(String pcapFile) {
+        // Check if it's a classpath resource (starts with '/')
+        if (pcapFile.startsWith("/")) {
+            return extractClasspathResource(pcapFile);
+        }
+
+        // Treat as a filesystem path
+        File file = new File(pcapFile);
+        if (!file.exists()) {
+            LOGGER.warn("PCAP file not found: {}", pcapFile);
+            return pcapFile; // Return as-is, error will occur when trying to 
open
+        }
+        return file.getAbsolutePath();
+    }
+
+    /**
+     * Extracts a classpath resource to a temporary file.
+     *
+     * @param resourcePath The classpath resource path
+     * @return The absolute path to the temporary file
+     */
+    private String extractClasspathResource(String resourcePath) {
+        try {
+            // Load resource from the classpath
+            InputStream resourceStream = 
getClass().getResourceAsStream(resourcePath);
+            if (resourceStream == null) {
+                LOGGER.error("Classpath resource not found: {}", resourcePath);
+                return resourcePath; // Return as-is, error will occur when 
trying to open
+            }
+
+            // Create temporary file
+            String fileName = 
resourcePath.substring(resourcePath.lastIndexOf('/') + 1);
+            String baseName = fileName.contains(".") ? fileName.substring(0, 
fileName.lastIndexOf('.')) : fileName;
+            String extension = fileName.contains(".") ? 
fileName.substring(fileName.lastIndexOf('.')) : ".pcap";
+
+            tempPcapFile = Files.createTempFile(baseName + "_", extension);
+            Files.copy(resourceStream, tempPcapFile, 
StandardCopyOption.REPLACE_EXISTING);
+            resourceStream.close();
+
+            String tempPath = tempPcapFile.toAbsolutePath().toString();
+            LOGGER.debug("Extracted classpath resource {} to temporary file: 
{}", resourcePath, tempPath);
+
+            return tempPath;
+
+        } catch (Exception e) {
+            LOGGER.error("Failed to extract classpath resource: " + 
resourcePath, e);
+            return resourcePath; // Return as-is, error will occur when trying 
to open
+        }
+    }
+
+    /**
+     * Cleans up temporary files created from classpath resources.
+     */
+    private void cleanupTempFile() {
+        if (tempPcapFile != null) {
+            try {
+                if (Files.exists(tempPcapFile)) {
+                    Files.delete(tempPcapFile);
+                    LOGGER.debug("Deleted temporary PCAP file: {}", 
tempPcapFile);
+                }
+            } catch (Exception e) {
+                LOGGER.warn("Failed to delete temporary PCAP file: {}", 
tempPcapFile, e);
+            }
+            tempPcapFile = null;
+        }
+    }
+}
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayChannelFactory.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayChannelFactory.java
deleted file mode 100644
index ee1f37730e..0000000000
--- 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayChannelFactory.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.plc4x.java.transport.pcapreplay;
-
-import io.netty.bootstrap.Bootstrap;
-import io.netty.channel.*;
-import io.netty.channel.oio.OioEventLoopGroup;
-import org.apache.plc4x.java.spi.configuration.HasConfiguration;
-import org.apache.plc4x.java.spi.connection.NettyChannelFactory;
-import org.apache.plc4x.java.utils.pcap.netty.address.PcapSocketAddress;
-import org.apache.plc4x.java.utils.pcap.netty.config.PcapChannelOption;
-import org.apache.plc4x.java.utils.pcapreplay.netty.PcapReplayChannel;
-import 
org.apache.plc4x.java.utils.pcapreplay.netty.config.PcapReplayChannelOption;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class PcapReplayChannelFactory extends NettyChannelFactory implements 
HasConfiguration<PcapReplayTransportConfiguration> {
-
-    private static final Logger logger = 
LoggerFactory.getLogger(PcapReplayChannelFactory.class);
-
-    private PcapReplayTransportConfiguration configuration;
-
-    public PcapReplayChannelFactory(PcapSocketAddress address) {
-        super(address);
-    }
-
-    @Override
-    public void setConfiguration(PcapReplayTransportConfiguration 
configuration) {
-        this.configuration = configuration;
-    }
-
-    @Override
-    public Class<? extends Channel> getChannel() {
-        return PcapReplayChannel.class;
-    }
-
-    @Override
-    public boolean isPassive() {
-        return true;
-    }
-
-    @Override
-    public void configureBootstrap(Bootstrap bootstrap) {
-        if(configuration != null) {
-            logger.info("Configuring Bootstrap with {}", configuration);
-            bootstrap.option(PcapChannelOption.SUPPORT_VLANS, 
configuration.getSupportVlans());
-            bootstrap.option(PcapChannelOption.PORT, 
configuration.getDefaultPort());
-            bootstrap.option(PcapChannelOption.PROTOCOL_ID, 
configuration.getProtocolId());
-            if(configuration.getPcapPacketHandler() != null) {
-                bootstrap.option(PcapChannelOption.PACKET_HANDLER, 
configuration.getPcapPacketHandler());
-            }
-            bootstrap.option(PcapReplayChannelOption.SPEED_FACTOR, 
configuration.getReplaySpeedFactor());
-            bootstrap.option(PcapReplayChannelOption.FILTER, 
configuration.getFilter());
-        }
-    }
-
-    @Override
-    public EventLoopGroup getEventLoopGroup() {
-        return new OioEventLoopGroup();
-    }
-
-}
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransport.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransport.java
index 877327b498..e31746bee7 100644
--- 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransport.java
+++ 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransport.java
@@ -18,39 +18,45 @@
  */
 package org.apache.plc4x.java.transport.pcapreplay;
 
-import org.apache.plc4x.java.spi.configuration.PlcTransportConfiguration;
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.spi.connection.ChannelFactory;
-import org.apache.plc4x.java.spi.transport.Transport;
-import org.apache.plc4x.java.utils.pcapreplay.netty.address.PcapReplayAddress;
+import org.apache.plc4x.java.spi.transports.api.Transport;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.spi.transports.api.config.TransportConfiguration;
+import org.apache.plc4x.java.spi.transports.api.exceptions.TransportException;
+import 
org.apache.plc4x.java.transport.pcapreplay.config.PcapReplayTransportConfiguration;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+import org.pcap4j.core.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.io.File;
+public class PcapReplayTransport implements 
Transport<PcapReplayTransportConfiguration> {
 
-public class PcapReplayTransport implements Transport {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PcapReplayTransport.class);
 
     @Override
     public String getTransportCode() {
-        return "pcap";
+        return "pcap-replay";
     }
 
     @Override
     public String getTransportName() {
-        return "PCAP(NG) Playback Transport";
+        return "PCAP Replay";
     }
 
     @Override
-    public ChannelFactory createChannelFactory(String transportConfig) {
-        File pcapFile = new File(transportConfig);
-        if(!pcapFile.exists() || !pcapFile.isFile()) {
-            throw new PlcRuntimeException("File not found at " + 
transportConfig);
-        }
-        PcapReplayAddress address = new PcapReplayAddress(pcapFile);
-        return new PcapReplayChannelFactory(address);
+    public Class<PcapReplayTransportConfiguration> getTransportConfigType() {
+        return PcapReplayTransportConfiguration.class;
     }
 
     @Override
-    public Class<? extends PlcTransportConfiguration> getTransportConfigType() 
{
-        return DefaultPcapReplayTransportConfiguration.class;
+    public TransportInstance<PcapReplayTransportConfiguration> 
createTransportInstance(
+        String transportUrl, TransportConfiguration configuration, AuditLog 
auditLog) throws TransportException {
+        if (!(configuration instanceof PcapReplayTransportConfiguration 
pcapReplayTransportConfiguration)) {
+            throw new IllegalArgumentException(String.format("Expected 
configuration of type %s but got %s",
+                PcapReplayTransportConfiguration.class.getSimpleName(), 
configuration.getClass().getSimpleName()));
+        }
+
+        LOGGER.debug("Creating PCAP replay transport for file: {}", 
pcapReplayTransportConfiguration.pcapFile);
+        return new 
PcapReplayTransportInstance(pcapReplayTransportConfiguration, auditLog);
     }
 
 }
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportConfiguration.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportConfiguration.java
deleted file mode 100644
index 1f9c9a1b8f..0000000000
--- 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportConfiguration.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.plc4x.java.transport.pcapreplay;
-
-import org.apache.plc4x.java.transport.pcap.PcapTransportConfiguration;
-
-public interface PcapReplayTransportConfiguration extends 
PcapTransportConfiguration {
-
-    float getReplaySpeedFactor();
-
-    boolean isLoop();
-
-    String getFilter();
-
-}
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportInstance.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportInstance.java
new file mode 100644
index 0000000000..d5d83f9bbe
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportInstance.java
@@ -0,0 +1,488 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay;
+
+import org.apache.plc4x.java.spi.transports.api.BaseTransportInstance;
+import org.apache.plc4x.java.spi.transports.api.RingBuffer;
+import org.apache.plc4x.java.spi.transports.api.exceptions.TransportException;
+import org.apache.plc4x.java.spi.utils.StaticHelper;
+import 
org.apache.plc4x.java.transport.pcapreplay.config.PcapReplayTransportConfiguration;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLogEventType;
+import org.pcap4j.core.PcapHandle;
+import org.pcap4j.core.Pcaps;
+import org.pcap4j.packet.EthernetPacket;
+import org.pcap4j.packet.Packet;
+import org.pcap4j.util.MacAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Java 21 PCAP Replay transport instance that simulates a real Ethernet device
+ * by replaying packets from a Wireshark capture file.
+ */
+public class PcapReplayTransportInstance extends 
BaseTransportInstance<PcapReplayTransportConfiguration> {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PcapReplayTransportInstance.class);
+
+    private final PcapFilePlayer player;
+    private final MacAddress localMac;
+    private final MacAddress remoteMac;
+    private final RingBuffer ringBuffer;
+    private final Lock readLock = new ReentrantLock();
+    private final Lock writeLock = new ReentrantLock();
+    private volatile boolean open = true;
+    private Path tempPcapFile; // Temporary file for classpath resources
+
+    public PcapReplayTransportInstance(PcapReplayTransportConfiguration 
configuration, AuditLog auditLog) throws TransportException {
+        super(configuration, auditLog);
+        this.ringBuffer = new RingBuffer(configuration.maxFrameSize);
+
+        try {
+            // Resolve PCAP file from the filesystem or classpath
+            String pcapFilePath = resolvePcapFile(configuration.pcapFile);
+
+            // Auto-detect MAC addresses if needed
+            MacAddress tempLocalMac;
+            MacAddress tempRemoteMac;
+
+            if (configuration.autoDetectMacAddresses) {
+                MacAddress[] detectedMacs = detectMacAddresses(pcapFilePath, 
configuration.protocolId);
+                tempLocalMac = configuration.localAddress != null && 
!configuration.localAddress.isEmpty()
+                    ? MacAddress.getByName(configuration.localAddress)
+                    : detectedMacs[0];
+                tempRemoteMac = configuration.remoteAddress != null && 
!configuration.remoteAddress.isEmpty()
+                    ? MacAddress.getByName(configuration.remoteAddress)
+                    : detectedMacs[1];
+            } else {
+                if (configuration.localAddress == null || 
configuration.localAddress.isEmpty()) {
+                    throw new TransportException("Local MAC address must be 
specified when auto-detect is disabled");
+                }
+                if (configuration.remoteAddress == null || 
configuration.remoteAddress.isEmpty()) {
+                    throw new TransportException("Remote MAC address must be 
specified when auto-detect is disabled");
+                }
+                tempLocalMac = 
MacAddress.getByName(configuration.localAddress);
+                tempRemoteMac = 
MacAddress.getByName(configuration.remoteAddress);
+            }
+
+            LOGGER.debug("PCAP replay - Local MAC: {}, Remote MAC: {}", 
tempLocalMac, tempRemoteMac);
+
+            if (configuration.mockPlayer == null) {
+                // Create player
+                PcapFilePlayer tempPlayer = new PcapFilePlayer(
+                    pcapFilePath,
+                    tempLocalMac,
+                    tempRemoteMac,
+                    configuration.protocolId,
+                    configuration.speedFactor,
+                    configuration.loop,
+                    configuration.onlyIncomingPackets,
+                    configuration.onlyOutgoingPackets,
+                    configuration.packetQueueSize
+                );
+
+                LOGGER.info("PCAP replay transport created for {} (speed: {}x, 
loop: {})",
+                    configuration.pcapFile,
+                    configuration.speedFactor > 0 ? configuration.speedFactor 
: "max",
+                    configuration.loop);
+
+                this.player = tempPlayer;
+            } else {
+                this.player = configuration.mockPlayer;
+            }
+            this.localMac = tempLocalMac;
+            this.remoteMac = tempRemoteMac;
+
+            // Auto-start if configured
+            if (configuration.autoStart) {
+                player.start();
+            }
+
+            getAuditLog().write(AuditLogEventType.CONNECT, String.format(
+                "PCAP replay transport created for %s (speed: %sx, loop: %s)",
+                configuration.pcapFile,
+                configuration.speedFactor > 0 ? 
String.valueOf(configuration.speedFactor) : "max",
+                configuration.loop));
+        } catch (Exception e) {
+            String errorMsg = String.format("Failed to create PCAP replay 
transport for %s - %s",
+                configuration.pcapFile, e.getMessage());
+            LOGGER.error(errorMsg, e);
+            getAuditLog().write(AuditLogEventType.ERROR, "Error in 
constructor: " + errorMsg);
+            throw new TransportException(errorMsg, e);
+        }
+    }
+
+    /**
+     * Detects local and remote MAC addresses from the first suitable packet 
in the PCAP.
+     */
+    private MacAddress[] detectMacAddresses(String pcapFile, int protocolId) 
throws Exception {
+        PcapHandle handle = null;
+        try {
+            handle = Pcaps.openOffline(pcapFile, 
PcapHandle.TimestampPrecision.MICRO);
+
+            Packet packet;
+            while ((packet = handle.getNextPacketEx()) != null) {
+                if (packet.contains(EthernetPacket.class)) {
+                    EthernetPacket ethPacket = 
packet.get(EthernetPacket.class);
+                    EthernetPacket.EthernetHeader header = 
ethPacket.getHeader();
+
+                    // Check if matches protocol filter
+                    if (protocolId > 0) {
+                        // Mask to 16 bits to avoid sign-extension from 
EtherType's signed short
+                        int packetProto = header.getType().value() & 0xFFFF;
+                        if (packetProto != protocolId) {
+                            continue;
+                        }
+                    }
+
+                    // Use first packet's addresses
+                    MacAddress src = header.getSrcAddr();
+                    MacAddress dst = header.getDstAddr();
+
+                    // Avoid broadcast addresses
+                    if (dst.equals(MacAddress.ETHER_BROADCAST_ADDRESS)) {
+                        continue;
+                    }
+
+                    LOGGER.debug("Auto-detected MAC addresses from PCAP: 
local={}, remote={}", dst, src);
+                    return new MacAddress[]{dst, src}; // Assume dst is local, 
src is remote
+                }
+            }
+
+            throw new TransportException("No suitable packets found in PCAP 
file for MAC detection");
+
+        } finally {
+            if (handle != null && handle.isOpen()) {
+                handle.close();
+            }
+        }
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open;
+    }
+
+    @Override
+    public int getNumBytesAvailable() {
+        readLock.lock();
+        try {
+            if (!isOpen()) {
+                return 0;
+            }
+
+            return ringBuffer.availableForReading();
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    @Override
+    public byte[] peekReadableBytes(int numBytes) throws TransportException {
+        if (numBytes <= 0) {
+            return new byte[0];
+        }
+
+        readLock.lock();
+        try {
+            ensureOpen();
+
+            // Ensure we have enough data in the buffer
+            while (ringBuffer.availableForReading() < numBytes && isOpen()) {
+                int beforeFill = ringBuffer.availableForReading();
+                fillRingBuffer(numBytes - beforeFill);
+                if (ringBuffer.availableForReading() == beforeFill) {
+                    // No progress — player has no more data to provide
+                    break;
+                }
+            }
+
+            if (ringBuffer.availableForReading() < numBytes) {
+                throw new TransportException(
+                    String.format("Requested %d bytes but only %d available", 
numBytes, ringBuffer.availableForReading())
+                );
+            }
+
+            // Peek without consuming
+            return ringBuffer.peek(numBytes);
+        } catch (TransportException e) {
+            getAuditLog().write(AuditLogEventType.ERROR, "Error in 
peekReadableBytes: " + e.getMessage());
+            throw e;
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    @Override
+    public byte[] read(int numBytes) throws TransportException {
+        if (numBytes <= 0) {
+            return new byte[0];
+        }
+
+        readLock.lock();
+        try {
+            ensureOpen();
+
+            // Ensure we have enough data in the buffer
+            while (ringBuffer.availableForReading() < numBytes && isOpen()) {
+                int beforeFill = ringBuffer.availableForReading();
+                fillRingBuffer(numBytes - beforeFill);
+                if (ringBuffer.availableForReading() == beforeFill) {
+                    // No progress — player has no more data to provide
+                    break;
+                }
+            }
+
+            if (ringBuffer.availableForReading() < numBytes) {
+                throw new TransportException(
+                    String.format("Requested %d bytes but only %d available", 
numBytes, ringBuffer.availableForReading())
+                );
+            }
+
+            // Read and consume bytes
+            byte[] bytes = ringBuffer.read(numBytes);
+
+            // Log the bytes to the audit log
+            if (getAuditLog().isEnabled()) {
+                getAuditLog().write(AuditLogEventType.INCOMING_BYTES, 
StaticHelper.ENCODE_HEX(bytes));
+            }
+
+            return bytes;
+        } catch (TransportException e) {
+            getAuditLog().write(AuditLogEventType.ERROR, "Error in read: " + 
e.getMessage());
+            throw e;
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    @Override
+    public void write(byte[] bytes) throws TransportException {
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+
+        writeLock.lock();
+        try {
+            ensureOpen();
+
+            // PCAP replay is read-only - writing is a no-op or could be logged
+            LOGGER.debug("Write operation on PCAP replay transport (simulated, 
{} bytes)", bytes.length);
+
+            // Log the write attempt to audit log
+            if (getAuditLog().isEnabled()) {
+                getAuditLog().write(AuditLogEventType.OUTGOING_BYTES, "Write 
(simulated): " + StaticHelper.ENCODE_HEX(bytes));
+            }
+
+            // In a real implementation, you might want to:
+            // 1. Store written data for validation
+            // 2. Trigger specific responses from the PCAP based on written 
data
+            // 3. Log the write operation for debugging purposes
+            throw new TransportException("PCAP replay transport is read-only");
+        } catch (TransportException e) {
+            getAuditLog().write(AuditLogEventType.ERROR, "Error in write: " + 
e.getMessage());
+            throw e;
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void close() throws TransportException {
+        if (!open) {
+            return;
+        }
+
+        writeLock.lock();
+        try {
+            readLock.lock();
+            try {
+                open = false;
+                player.stop();
+                LOGGER.debug("Closed PCAP replay transport (replayed {} 
packets)", player.getPacketsReplayed());
+
+                // Clean up temporary file if created
+                if (tempPcapFile != null) {
+                    try {
+                        Files.deleteIfExists(tempPcapFile);
+                        LOGGER.debug("Deleted temporary PCAP file: {}", 
tempPcapFile);
+                    } catch (Exception e) {
+                        LOGGER.warn("Failed to delete temporary PCAP file: 
{}", e.getMessage());
+                    }
+                }
+
+                getAuditLog().write(AuditLogEventType.CLOSE, String.format(
+                    "Closed PCAP replay transport (replayed %d packets)", 
player.getPacketsReplayed()));
+            } finally {
+                readLock.unlock();
+            }
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    /**
+     * Starts the PCAP replay if not already started.
+     */
+    public void startReplay() {
+        if (!player.isPlaying()) {
+            player.start();
+        }
+    }
+
+    /**
+     * Stops the PCAP replay.
+     */
+    public void stopReplay() {
+        if (player.isPlaying()) {
+            player.stop();
+        }
+    }
+
+    /**
+     * Checks if replay is currently active.
+     */
+    public boolean isReplaying() {
+        return player.isPlaying();
+    }
+
+    /**
+     * Gets the number of packets replayed so far.
+     */
+    public long getPacketsReplayed() {
+        return player.getPacketsReplayed();
+    }
+
+    /**
+     * Fills the ring buffer with at least minBytes from the player.
+     */
+    private void fillRingBuffer(int minBytes) throws TransportException {
+        try {
+            int totalRead = 0;
+
+            while (totalRead < minBytes && isOpen()) {
+                // Get packet from player
+                byte[] packet = getConfiguration().readTimeout > 0
+                    ? player.getNextPacket(getConfiguration().readTimeout, 
TimeUnit.MILLISECONDS)
+                    : player.getNextPacket(Long.MAX_VALUE, TimeUnit.DAYS);
+
+                if (packet == null) {
+                    // Timeout or no more packets
+                    break;
+                }
+
+                // Write packet data to the ring buffer
+                int written = ringBuffer.write(packet);
+                totalRead += written;
+
+                // If a packet didn't fit completely, this is a problem
+                if (written < packet.length) {
+                    LOGGER.warn("Packet truncated: {} bytes lost", 
packet.length - written);
+                }
+            }
+
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new TransportException("Read interrupted", e);
+        }
+    }
+
+    /**
+     * Ensures the transport is still open, throws exception otherwise.
+     */
+    private void ensureOpen() throws TransportException {
+        if (!isOpen()) {
+            throw new TransportException("Transport is closed");
+        }
+    }
+
+    /**
+     * Resolves a PCAP file path, supporting both filesystem and classpath 
resources.
+     * If the path starts with '/', it's treated as a classpath resource and 
extracted
+     * to a temporary file. Otherwise, it's treated as a filesystem path.
+     *
+     * @param pcapFile The PCAP file path (filesystem or classpath resource 
starting with '/')
+     * @return The resolved absolute file path
+     * @throws TransportException If the file cannot be found or accessed
+     */
+    private String resolvePcapFile(String pcapFile) throws TransportException {
+        // Check if it's a classpath resource (starts with '/')
+        if (pcapFile.startsWith("/")) {
+            return extractClasspathResource(pcapFile);
+        }
+
+        // Treat as a filesystem path
+        File file = new File(pcapFile);
+        if (!file.exists()) {
+            throw new TransportException("PCAP file not found: " + pcapFile);
+        }
+        if (!file.canRead()) {
+            throw new TransportException("Cannot read PCAP file: " + pcapFile);
+        }
+
+        return file.getAbsolutePath();
+    }
+
+    /**
+     * Extracts a classpath resource to a temporary file.
+     *
+     * @param resourcePath The classpath resource path
+     * @return The absolute path to the temporary file
+     * @throws TransportException If the resource cannot be found or extracted
+     */
+    private String extractClasspathResource(String resourcePath) throws 
TransportException {
+        try {
+            // Load resource from the classpath
+            InputStream resourceStream = 
getClass().getResourceAsStream(resourcePath);
+            if (resourceStream == null) {
+                throw new TransportException("Classpath resource not found: " 
+ resourcePath);
+            }
+
+            // Create a temporary file
+            String fileName = 
resourcePath.substring(resourcePath.lastIndexOf('/') + 1);
+            String prefix = fileName.contains(".") ? fileName.substring(0, 
fileName.lastIndexOf('.')) : fileName;
+            String suffix = fileName.contains(".") ? 
fileName.substring(fileName.lastIndexOf('.')) : ".tmp";
+
+            tempPcapFile = Files.createTempFile(prefix + "_", suffix);
+
+            // Copy resource to the temporary file
+            Files.copy(resourceStream, tempPcapFile, 
StandardCopyOption.REPLACE_EXISTING);
+            resourceStream.close();
+
+            // Mark for deletion on JVM exit (backup cleanup)
+            tempPcapFile.toFile().deleteOnExit();
+
+            LOGGER.debug("Extracted classpath resource {} to temporary file: 
{}", resourcePath, tempPcapFile);
+
+            return tempPcapFile.toAbsolutePath().toString();
+
+        } catch (Exception e) {
+            throw new TransportException("Failed to extract classpath resource 
" + resourcePath + ": " + e.getMessage(), e);
+        }
+    }
+}
diff --git 
a/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/config/PcapReplayTransportConfiguration.java
 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/config/PcapReplayTransportConfiguration.java
new file mode 100644
index 0000000000..cb9d78f30f
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/main/java/org/apache/plc4x/java/transport/pcapreplay/config/PcapReplayTransportConfiguration.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay.config;
+
+
+import org.apache.plc4x.java.spi.config.annotations.ConfigurationParameter;
+import org.apache.plc4x.java.spi.config.annotations.Description;
+import org.apache.plc4x.java.spi.config.annotations.Required;
+import 
org.apache.plc4x.java.spi.config.annotations.defaults.BooleanDefaultValue;
+import org.apache.plc4x.java.spi.config.annotations.defaults.IntDefaultValue;
+import org.apache.plc4x.java.spi.transports.api.config.TransportConfiguration;
+import org.apache.plc4x.java.transport.pcapreplay.PcapFilePlayer;
+
+public class PcapReplayTransportConfiguration implements 
TransportConfiguration {
+
+    /**
+     * Path to the PCAP file to replay.
+     */
+    @ConfigurationParameter("pcap-file")
+    @Description("Path to the PCAP file to replay.")
+    @Required
+    public String pcapFile;
+
+    /**
+     * Local MAC address to simulate (acts as this device).
+     * If not set, uses the first MAC found in the PCAP.
+     */
+    @ConfigurationParameter("local-address")
+    @Description( "Local MAC address to simulate (acts as this device).")
+    public String localAddress;
+
+    /**
+     * Remote MAC address to communicate with.
+     * If not set, uses the first different MAC found in the PCAP.
+     */
+    @ConfigurationParameter("remote-address")
+    @Description( "Remote MAC address to communicate with.")
+    public String remoteAddress;
+
+    /**
+     * EtherType / Protocol number to filter (e.g., 0x88B5 for PROFINET).
+     * If 0, accepts all EtherTypes.
+     */
+    @ConfigurationParameter( "protocol-id")
+    @Description( "EtherType / Protocol number to filter (e.g., 0x88B5 for 
PROFINET).")
+    @IntDefaultValue(0)
+    public int protocolId;
+
+    /**
+     * Replay speed multiplier. 1.0 = real-time, 0.0 = as fast as possible, 
2.0 = double speed.
+     */
+    @ConfigurationParameter( "speed-factor")
+    @Description( "Replay speed multiplier. 1.0 = real-time, 0.0 = as fast as 
possible, 2.0 = double speed.")
+    @IntDefaultValue(0)
+    public double speedFactor;
+
+    /**
+     * Loop the PCAP file when it reaches the end.
+     */
+    @ConfigurationParameter( "loop")
+    @Description( "Loop the PCAP file when it reaches the end.")
+    @BooleanDefaultValue(false)
+    public boolean loop;
+
+    /**
+     * Only replay packets from remote to local (simulates device responses).
+     * When false, replays all packets.
+     */
+    @ConfigurationParameter( "only-incoming-packets")
+    @Description("Only replay packets from remote to local (simulates device 
responses). When false, replays all packets.")
+    @BooleanDefaultValue(true)
+    public boolean onlyIncomingPackets;
+
+    /**
+     * Only replay packets from local to remote (simulates device requests).
+     * When false, replays all packets.
+     */
+    @ConfigurationParameter( "only-outgoing-packets")
+    @Description("Only replay packets from local to remote (simulates device 
requests).")
+    @BooleanDefaultValue(false)
+    public boolean onlyOutgoingPackets;
+
+    /**
+     * Maximum frame size in bytes.
+     */
+    @ConfigurationParameter( "max-frame-size")
+    @Description("Maximum frame size in bytes.")
+    @IntDefaultValue(1500)
+    public int maxFrameSize;
+
+    /**
+     * Read timeout for blocking reads in milliseconds. 0 means no timeout.
+     */
+    @ConfigurationParameter("read-timeout")
+    @Description("Read timeout for blocking reads in milliseconds.")
+    @IntDefaultValue(0)
+    public int readTimeout;
+
+    /**
+     * Buffer size for packet queue.
+     */
+    @ConfigurationParameter( "packet-queue-size")
+    @Description("Buffer size for packet queue.")
+    @IntDefaultValue(1000)
+    public int packetQueueSize;
+
+    /**
+     * Auto-detect MAC addresses from PCAP file based on first packet.
+     */
+    @ConfigurationParameter( "auto-detect-mac-addresses")
+    @Description("Auto-detect MAC addresses from PCAP file based on first 
packet.")
+    @BooleanDefaultValue(true)
+    public boolean autoDetectMacAddresses;
+
+    /**
+     * Start replay automatically when transport is created.
+     */
+    @ConfigurationParameter( "auto-start")
+    @Description("Start replay automatically when transport is created.")
+    @BooleanDefaultValue(true)
+    public boolean autoStart;
+
+    /**
+     * Filter by VLAN ID (0 means no VLAN filtering).
+     */
+    @ConfigurationParameter( "vlan-id")
+    @Description("Filter by VLAN ID (0 means no VLAN filtering).")
+    @IntDefaultValue(0)
+    public int vlanId;
+
+    public PcapFilePlayer mockPlayer;
+
+}
diff --git 
a/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapFilePlayerTest.java
 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapFilePlayerTest.java
new file mode 100644
index 0000000000..c4397eed63
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapFilePlayerTest.java
@@ -0,0 +1,688 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.pcap4j.packet.factory.PacketFactories;
+import org.pcap4j.packet.namednumber.DataLinkType;
+import org.pcap4j.util.MacAddress;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Tests for PcapFilePlayer.
+ */
+class PcapFilePlayerTest {
+
+    private PcapFilePlayer player;
+    private MacAddress localMac;
+    private MacAddress remoteMac;
+
+    @BeforeAll
+    static void initPacketFactory() {
+        // Force pcap4j to eagerly load the PacketFactoryBinder.
+        // Without this, the factory loads lazily on the first player thread,
+        // causing a race where early tests finish before parsing is available.
+        PacketFactories.getFactory(org.pcap4j.packet.Packet.class, 
DataLinkType.class);
+    }
+
+    @BeforeEach
+    void setUp() {
+        try {
+            // For some reason it doesn't work if we pass this in from the 
outside.
+            //if (os == "mac") {
+            // On my Intel Mac I found the libs in: 
"/usr/local/Cellar/libpcap/1.10.1/lib"
+            // On my M1 Mac I found the libs in: 
"/opt/homebrew/Cellar/libpcap/1.10.1/lib"
+            if (new File("/usr/local/Cellar/libpcap/1.10.1/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/usr/local/Cellar/libpcap/1.10.1/lib");
+            } else if (new 
File("/usr/local/Cellar/libpcap/1.10.5/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/usr/local/Cellar/libpcap/1.10.5/lib");
+            } else if (new File("/opt/homebrew/opt/libpcap/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/opt/homebrew/opt/libpcap/lib");
+            }
+            //}
+        } catch (Error e) {
+            e.printStackTrace();
+        }
+
+        localMac = MacAddress.getByName("00:11:22:33:44:55");
+        remoteMac = MacAddress.getByName("AA:BB:CC:DD:EE:FF");
+    }
+
+    @AfterEach
+    void tearDown() {
+        if (player != null) {
+            player.stop();
+        }
+    }
+
+    @Test
+    void testInitialState() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            1.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        assertFalse(player.isPlaying());
+        assertEquals(0, player.getPacketsReplayed());
+    }
+
+    @Test
+    void testStart_changesPlayingState() throws InterruptedException {
+        // Use valid test pcap with looping to ensure player stays running
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,  // No protocol filter
+            0.0,
+            true,  // Enable looping so player doesn't stop immediately
+            false,
+            false,
+            100
+        );
+
+        player.start();
+        // Give the thread time to start and stabilize
+        Thread.sleep(50);
+        assertTrue(player.isPlaying());
+    }
+
+    @Test
+    void testStop_changesPlayingState() throws InterruptedException {
+        // Use valid test pcap with looping to ensure player stays running
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,  // No protocol filter
+            0.0,
+            true,  // Enable looping so player doesn't stop immediately
+            false,
+            false,
+            100
+        );
+
+        player.start();
+        // Give the thread time to start and stabilize
+        Thread.sleep(50);
+        assertTrue(player.isPlaying());
+
+        player.stop();
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testGetNextPacket_timeout() throws InterruptedException {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        // Should timeout since no valid file
+        byte[] packet = player.getNextPacket(100, TimeUnit.MILLISECONDS);
+        assertNull(packet); // Timeout
+    }
+
+    @Test
+    void testGetQueueSize_initially() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        assertEquals(0, player.getQueueSize());
+    }
+
+    @Test
+    void testPlayback_withValidPcap() throws Exception {
+        // test.pcap contains 4 packets (2 incoming, 2 outgoing) with 
EtherType 0x88B5
+        // and the same MAC addresses used in this test class
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0, // As fast as possible
+            false,
+            true, // Only incoming
+            false,
+            100
+        );
+
+        player.start();
+        Thread.sleep(200); // Let it play for a bit
+
+        assertTrue(player.getPacketsReplayed() > 0);
+
+        byte[] packet = player.getNextPacket(1, TimeUnit.SECONDS);
+        assertNotNull(packet);
+        assertTrue(packet.length > 0);
+    }
+
+    @Test
+    void testPlayback_withLoop() throws Exception {
+        // test.pcap has 2 incoming 0x88B5 packets; with looping, count grows 
over time.
+        // Use a large queue so it doesn't fill up and stall the counter.
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            true, // Loop
+            true,
+            false,
+            100000
+        );
+
+        player.start();
+        Thread.sleep(200); // Let it loop
+
+        long packets1 = player.getPacketsReplayed();
+        Thread.sleep(200);
+        long packets2 = player.getPacketsReplayed();
+
+        // Should have replayed more packets (looping)
+        assertTrue(packets2 > packets1,
+                "Expected more packets after looping: packets1=" + packets1 + 
", packets2=" + packets2);
+    }
+
+    @Test
+    void testPlayback_speedFactor() throws Exception {
+        // test.pcap has 2 incoming 0x88B5 packets 20ms apart (at indices 0 
and 2).
+        // At real-time speed (1.0), replaying should take at least ~20ms.
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            1.0, // Real-time
+            false,
+            true,
+            false,
+            100
+        );
+
+        long start = System.currentTimeMillis();
+        player.start();
+
+        // Wait for the 2 incoming packets to be replayed (with timeout to 
avoid hang)
+        long deadline = start + 5000;
+        while (player.getPacketsReplayed() < 2 && System.currentTimeMillis() < 
deadline) {
+            Thread.sleep(10);
+        }
+
+        long elapsed = System.currentTimeMillis() - start;
+
+        // At real-time speed, the 20ms inter-packet gap means replay takes 
nonzero time
+        assertTrue(elapsed > 10, "Real-time replay should take measurable 
time");
+        assertTrue(player.getPacketsReplayed() >= 2, "Should have replayed 
both incoming packets");
+    }
+
+    @Test
+    void testGetPacketsReplayed_initially() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        assertEquals(0, player.getPacketsReplayed());
+    }
+
+    @Test
+    void testConstructor_withAllParameters() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            2.0,  // Double speed
+            true,  // Loop
+            false, // Not only incoming
+            true,  // Only outgoing
+            500    // Queue size
+        );
+
+        assertNotNull(player);
+        assertFalse(player.isPlaying());
+        assertEquals(0, player.getPacketsReplayed());
+        assertEquals(0, player.getQueueSize());
+    }
+
+    @Test
+    void testStart_alreadyPlaying() throws InterruptedException {
+        // Use valid test pcap with looping to ensure player stays running
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,  // No protocol filter
+            0.0,
+            true,  // Enable looping so player doesn't stop immediately
+            false,
+            false,
+            100
+        );
+
+        player.start();
+        // Give the thread time to start and stabilize
+        Thread.sleep(50);
+        assertTrue(player.isPlaying());
+
+        // Start again - should be idempotent
+        player.start();
+        assertTrue(player.isPlaying());
+    }
+
+    @Test
+    void testStop_notPlaying() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        // Stop without starting - should not throw
+        assertDoesNotThrow(() -> player.stop());
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testStop_idempotent() throws InterruptedException {
+        // Use valid test pcap with looping to ensure player stays running
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,  // No protocol filter to ensure packets match
+            0.0,
+            true,  // Enable looping so player doesn't stop immediately
+            false,
+            false,
+            100
+        );
+
+        player.start();
+        // Give the thread time to start and stabilize
+        Thread.sleep(50);
+        assertTrue(player.isPlaying());
+
+        player.stop();
+        assertFalse(player.isPlaying());
+
+        // Stop again - should be idempotent
+        player.stop();
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testConstructor_withSmallQueueSize() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0,  // No protocol filter
+            0.0,
+            false,
+            false, // Not only incoming
+            false, // Not only outgoing
+            1    // Minimal queue size
+        );
+
+        assertNotNull(player);
+        assertEquals(0, player.getQueueSize());
+    }
+
+    @Test
+    void testGetNextPacket_withoutStart() throws InterruptedException {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        // Should timeout immediately since no packets are being played
+        byte[] packet = player.getNextPacket(50, TimeUnit.MILLISECONDS);
+        assertNull(packet);
+    }
+
+    @Test
+    void testConstructor_withBothDirectionFlags() {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            1.5,
+            false,
+            true,  // Only incoming
+            true,  // Only outgoing (contradictory)
+            100
+        );
+
+        assertNotNull(player);
+        // The contradictory flags are handled in the filter logic
+    }
+
+    @Test
+    void testGetQueueSize_afterStarting() throws InterruptedException {
+        player = new PcapFilePlayer(
+            "/path/to/test.pcap",
+            localMac,
+            remoteMac,
+            0x88B5,
+            0.0,
+            false,
+            true,
+            false,
+            100
+        );
+
+        player.start();
+        Thread.sleep(50); // Give it time to process
+
+        // Queue size should be >= 0
+        int size = player.getQueueSize();
+        assertTrue(size >= 0);
+    }
+
+    @Test
+    void testPlayback_withTestPcap() throws Exception {
+        // Use the test.pcap from classpath (will be extracted automatically 
by PcapFilePlayer)
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,  // No protocol filter
+            0.0, // As fast as possible
+            false,
+            false, // Accept all packets
+            false,
+            1000
+        );
+
+        player.start();
+        assertTrue(player.isPlaying());
+
+        // Wait for replay to complete
+        Thread.sleep(200);
+
+        // Should have processed some packets
+        long packetsReplayed = player.getPacketsReplayed();
+        assertTrue(packetsReplayed >= 0);
+
+        player.stop();
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testPlayback_withProtocolFilter() throws Exception {
+        // Test with protocol filter
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0x0800,  // IPv4 protocol
+            0.0,
+            false,
+            false,
+            false,
+            1000
+        );
+
+        player.start();
+        Thread.sleep(100);
+
+        long packetsReplayed = player.getPacketsReplayed();
+        assertTrue(packetsReplayed >= 0);
+
+        player.stop();
+    }
+
+    @Test
+    void testPlayback_onlyIncomingPackets() throws Exception {
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            false,
+            true,  // Only incoming
+            false,
+            1000
+        );
+
+        player.start();
+        Thread.sleep(100);
+
+        player.stop();
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testPlayback_onlyOutgoingPackets() throws Exception {
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            false,
+            false,
+            true,  // Only outgoing
+            1000
+        );
+
+        player.start();
+        Thread.sleep(100);
+
+        player.stop();
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testPlayback_withBothDirectionFilters() throws Exception {
+        // Both flags set - should reject all packets
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            false,
+            true,  // Only incoming
+            true,  // Only outgoing (contradictory)
+            1000
+        );
+
+        player.start();
+        Thread.sleep(100);
+
+        // Should not have replayed any packets due to contradictory filters
+        long packetsReplayed = player.getPacketsReplayed();
+        assertEquals(0, packetsReplayed);
+
+        player.stop();
+    }
+
+    @Test
+    void testPlayback_withLooping() throws Exception {
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            true,  // Loop enabled
+            false,
+            false,
+            1000
+        );
+
+        player.start();
+        Thread.sleep(300); // Give time to loop
+
+        long packetsAfterDelay = player.getPacketsReplayed();
+
+        player.stop();
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testPlayback_withSpeedFactor() throws Exception {
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.5,  // Half speed
+            false,
+            false,
+            false,
+            1000
+        );
+
+        long startTime = System.currentTimeMillis();
+        player.start();
+        Thread.sleep(200);
+
+        player.stop();
+    }
+
+    @Test
+    void testGetNextPacket_withActualData() throws Exception {
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            false,
+            false,
+            false,
+            1000
+        );
+
+        player.start();
+        Thread.sleep(100); // Wait for some packets
+
+        // Try to get a packet with timeout
+        byte[] packet = player.getNextPacket(500, TimeUnit.MILLISECONDS);
+        // May be null if no matching packets, or contain data
+
+        player.stop();
+    }
+
+    @Test
+    void testStop_duringPlayback() throws Exception {
+        player = new PcapFilePlayer(
+            "/test.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            true,  // Loop - will run indefinitely
+            false,
+            false,
+            1000
+        );
+
+        player.start();
+        assertTrue(player.isPlaying());
+
+        Thread.sleep(50);
+
+        player.stop();
+        assertFalse(player.isPlaying());
+
+        // Wait for thread to fully stop
+        Thread.sleep(50);
+        assertFalse(player.isPlaying());
+    }
+
+    @Test
+    void testPlayback_invalidPcapFile() throws Exception {
+        player = new PcapFilePlayer(
+            "/nonexistent/invalid.pcap",
+            localMac,
+            remoteMac,
+            0,
+            0.0,
+            false,
+            false,
+            false,
+            1000
+        );
+
+        player.start();
+        Thread.sleep(100);
+
+        // Should handle gracefully - player might stop due to error
+        // but shouldn't crash
+        player.stop();
+    }
+}
diff --git 
a/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportInstanceTest.java
 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportInstanceTest.java
new file mode 100644
index 0000000000..b3310f01bb
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportInstanceTest.java
@@ -0,0 +1,782 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay;
+
+import org.apache.plc4x.java.spi.transports.api.exceptions.TransportException;
+import 
org.apache.plc4x.java.transport.pcapreplay.config.PcapReplayTransportConfiguration;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.pcap4j.util.MacAddress;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests for PcapReplayTransportInstance.
+ */
+class PcapReplayTransportInstanceTest {
+
+    private PcapFilePlayer mockPlayer;
+    private PcapReplayTransportInstance transportInstance;
+    private PcapReplayTransportConfiguration config;
+    private MacAddress localMac;
+    private MacAddress remoteMac;
+
+    @BeforeEach
+    void setUp() throws TransportException {
+        try {
+            // For some reason it doesn't work if we pass this in from the 
outside.
+            //if (os == "mac") {
+            // On my Intel Mac I found the libs in: 
"/usr/local/Cellar/libpcap/1.10.1/lib"
+            // On my M1 Mac I found the libs in: 
"/opt/homebrew/Cellar/libpcap/1.10.1/lib"
+            if (new File("/usr/local/Cellar/libpcap/1.10.1/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/usr/local/Cellar/libpcap/1.10.1/lib");
+            } else if (new 
File("/usr/local/Cellar/libpcap/1.10.5/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/usr/local/Cellar/libpcap/1.10.5/lib");
+            } else if (new File("/opt/homebrew/opt/libpcap/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/opt/homebrew/opt/libpcap/lib");
+            }
+            //}
+        } catch (Error e) {
+            e.printStackTrace();
+        }
+
+        // Create a mock player
+        mockPlayer = mock(PcapFilePlayer.class);
+        when(mockPlayer.isPlaying()).thenReturn(true);
+        when(mockPlayer.getPacketsReplayed()).thenReturn(0L);
+
+        // Set up MAC addresses
+        localMac = MacAddress.getByName("00:11:22:33:44:55");
+        remoteMac = MacAddress.getByName("AA:BB:CC:DD:EE:FF");
+
+        // Create configuration
+        config = new PcapReplayTransportConfiguration();
+        config.pcapFile = "/test.pcap";
+        config.maxFrameSize = 1500;
+        config.readTimeout = 100;
+        config.localAddress = localMac.toString();
+        config.remoteAddress = remoteMac.toString();
+        config.packetQueueSize = 100;
+        config.maxFrameSize = 1500;
+        config.autoStart = false; // Don't auto-start in tests
+        config.mockPlayer = mockPlayer;
+
+        // Create transport-instance
+        transportInstance = new PcapReplayTransportInstance(config, 
AuditLog.builder().build());
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        if (transportInstance != null && transportInstance.isOpen()) {
+            transportInstance.close();
+        }
+    }
+
+    @Test
+    void testGetConfiguration() {
+        PcapReplayTransportConfiguration config = 
transportInstance.getConfiguration();
+        assertNotNull(config);
+    }
+
+    @Test
+    void testIsOpen_initially() {
+        assertTrue(transportInstance.isOpen());
+    }
+
+    @Test
+    void testIsOpen_whenClosed() throws TransportException {
+        transportInstance.close();
+        assertFalse(transportInstance.isOpen());
+    }
+
+    @Test
+    void testGetNumBytesAvailable() throws TransportException {
+        when(mockPlayer.getQueueSize()).thenReturn(5);
+
+        int available = transportInstance.getNumBytesAvailable();
+        assertTrue(available >= 0);
+    }
+
+    @Test
+    void testWrite_simulatedOperation() throws TransportException {
+        byte[] data = new byte[]{0x01, 0x02, 0x03, 0x04};
+
+        // Should not throw (write is simulated in replay mode)
+        assertThrows(TransportException.class, () -> 
transportInstance.write(data));
+    }
+
+    @Test
+    void testWrite_emptyArray() throws TransportException {
+        assertDoesNotThrow(() -> transportInstance.write(new byte[0]));
+    }
+
+    @Test
+    void testWrite_nullArray() throws TransportException {
+        assertDoesNotThrow(() -> transportInstance.write(null));
+    }
+
+    @Test
+    void testRead_zeroBytes() throws TransportException {
+        byte[] result = transportInstance.read(0);
+        assertEquals(0, result.length);
+    }
+
+    @Test
+    void testRead_whenClosed_throwsException() throws TransportException {
+        transportInstance.close();
+
+        assertThrows(TransportException.class, () ->
+            transportInstance.read(10)
+        );
+    }
+
+    @Test
+    void testWrite_whenClosed_throwsException() throws TransportException {
+        transportInstance.close();
+
+        assertThrows(TransportException.class, () ->
+            transportInstance.write(new byte[]{0x01})
+        );
+    }
+
+    @Test
+    void testStartReplay() {
+        when(mockPlayer.isPlaying()).thenReturn(false);
+        transportInstance.startReplay();
+        verify(mockPlayer, atLeastOnce()).start();
+    }
+
+    @Test
+    void testStopReplay() {
+        transportInstance.stopReplay();
+        verify(mockPlayer).stop();
+    }
+
+    @Test
+    void testIsReplaying() {
+        when(mockPlayer.isPlaying()).thenReturn(true);
+        assertTrue(transportInstance.isReplaying());
+
+        when(mockPlayer.isPlaying()).thenReturn(false);
+        assertFalse(transportInstance.isReplaying());
+    }
+
+    @Test
+    void testGetPacketsReplayed() {
+        when(mockPlayer.getPacketsReplayed()).thenReturn(42L);
+        assertEquals(42L, transportInstance.getPacketsReplayed());
+    }
+
+    @Test
+    void testClose_idempotent() throws TransportException {
+        transportInstance.close();
+        assertFalse(transportInstance.isOpen());
+
+        // Should not throw
+        assertDoesNotThrow(() -> transportInstance.close());
+        assertFalse(transportInstance.isOpen());
+
+        verify(mockPlayer, atLeastOnce()).stop(); // Called twice
+    }
+
+    @Test
+    void testClose_stopsPlayer() throws TransportException {
+        transportInstance.close();
+        verify(mockPlayer).stop();
+    }
+
+    @Test
+    void testRead_withData() throws Exception {
+        byte[] testData = new byte[]{0x01, 0x02, 0x03, 0x04};
+
+        // Mock player to return data when polled
+        when(mockPlayer.getNextPacket(anyLong(), 
any(TimeUnit.class))).thenReturn(testData);
+
+        byte[] result = transportInstance.read(4);
+        assertArrayEquals(testData, result);
+    }
+
+    @Test
+    void testPeekReadableBytes_doesNotConsume() throws Exception {
+        byte[] testData = new byte[]{0x01, 0x02, 0x03, 0x04};
+
+        // Mock player to return data when polled
+        when(mockPlayer.getNextPacket(anyLong(), 
any(TimeUnit.class))).thenReturn(testData);
+
+        // Peek twice — should return the same data both times
+        byte[] peek1 = transportInstance.peekReadableBytes(4);
+        byte[] peek2 = transportInstance.peekReadableBytes(4);
+
+        assertArrayEquals(peek1, peek2);
+
+        // Read should still return same data and consume it
+        byte[] read = transportInstance.read(4);
+        assertArrayEquals(testData, read);
+    }
+
+    @Test
+    void testPeekReadableBytes_zeroBytes() throws TransportException {
+        byte[] result = transportInstance.peekReadableBytes(0);
+        assertNotNull(result);
+        assertEquals(0, result.length);
+    }
+
+    @Test
+    void testPeekReadableBytes_whenClosed() throws TransportException {
+        transportInstance.close();
+
+        assertThrows(TransportException.class, () ->
+            transportInstance.peekReadableBytes(10)
+        );
+    }
+
+    @Test
+    void testStartReplay_multipleTimes() {
+        // First call: isPlaying returns false, so start() is called
+        when(mockPlayer.isPlaying()).thenReturn(false);
+        transportInstance.startReplay();
+
+        // Second call: isPlaying returns true, so start() is not called again
+        when(mockPlayer.isPlaying()).thenReturn(true);
+        transportInstance.startReplay();
+
+        // Should have called start once (when player was not playing)
+        verify(mockPlayer, times(1)).start();
+    }
+
+    @Test
+    void testStopReplay_multipleTimes() {
+        transportInstance.stopReplay();
+        transportInstance.stopReplay();
+        // Should handle multiple stops gracefully
+        verify(mockPlayer, times(2)).stop();
+    }
+
+    @Test
+    void testConstructor_withAutoStart() throws TransportException {
+        config.autoStart = true;
+        PcapFilePlayer autoStartPlayer = mock(PcapFilePlayer.class);
+        config.mockPlayer = autoStartPlayer;
+
+        PcapReplayTransportInstance autoStartInstance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Should have started automatically
+        verify(autoStartPlayer).start();
+
+        autoStartInstance.close();
+    }
+
+    @Test
+    void testConstructor_withoutMockPlayer() throws TransportException {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+
+        // Should create actual player (will use classpath resource)
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+        assertNotNull(instance);
+        assertTrue(instance.isOpen());
+
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withProtocolFilter() throws TransportException {
+        config.protocolId = 0x88B5; // PROFINET
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withVlanId() throws TransportException {
+        config.vlanId = 100;
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withSpeedFactor() throws TransportException {
+        config.speedFactor = 2.0; // Double speed
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withLoopEnabled() throws TransportException {
+        config.loop = true;
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withOnlyIncomingPackets() throws TransportException {
+        config.onlyIncomingPackets = true;
+        config.onlyOutgoingPackets = false;
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withOnlyOutgoingPackets() throws TransportException {
+        config.onlyIncomingPackets = false;
+        config.onlyOutgoingPackets = true;
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testGetNumBytesAvailable_withZeroBytes() {
+        when(mockPlayer.getQueueSize()).thenReturn(0);
+
+        assertDoesNotThrow(() -> {
+            int available = transportInstance.getNumBytesAvailable();
+            assertEquals(0, available);
+        });
+    }
+
+    @Test
+    void testGetNumBytesAvailable_whenClosed() throws TransportException {
+        transportInstance.close();
+
+        // Should return 0 when closed
+        int available = transportInstance.getNumBytesAvailable();
+        assertEquals(0, available);
+    }
+
+    @Test
+    void testWrite_largeArray() throws TransportException {
+        byte[] largeData = new byte[10000];
+        for (int i = 0; i < largeData.length; i++) {
+            largeData[i] = (byte) (i % 256);
+        }
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Writing should throw an exception as this is a replay-only transport
+        assertThrows(TransportException.class, () -> 
instance.write(largeData));
+
+        instance.close();
+    }
+
+    @Test
+    void testIsReplaying_afterClose() throws TransportException {
+        when(mockPlayer.isPlaying()).thenReturn(true);
+        transportInstance.close();
+
+        // Player should be stopped after close
+        when(mockPlayer.isPlaying()).thenReturn(false);
+        assertFalse(transportInstance.isReplaying());
+    }
+
+    @Test
+    void testGetPacketsReplayed_afterMultipleOperations() {
+        when(mockPlayer.getPacketsReplayed()).thenReturn(10L);
+        assertEquals(10L, transportInstance.getPacketsReplayed());
+
+        when(mockPlayer.getPacketsReplayed()).thenReturn(25L);
+        assertEquals(25L, transportInstance.getPacketsReplayed());
+    }
+
+    @Test
+    void testConstructor_withManualMacAddresses() throws TransportException {
+        config.autoDetectMacAddresses = false;
+        config.localAddress = "00:11:22:33:44:55";
+        config.remoteAddress = "AA:BB:CC:DD:EE:FF";
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+        assertNotNull(instance);
+        assertTrue(instance.isOpen());
+        instance.close();
+    }
+
+    @Test
+    void testRead_withActualPcapPlayback() throws Exception {
+        // Create a real player instance that uses test.pcap
+        config.mockPlayer = null; // Use real player
+        config.pcapFile = "/test.pcap";
+        config.autoStart = true;
+        config.onlyIncomingPackets = false;
+        config.onlyOutgoingPackets = false;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Wait for some packets to be queued
+        Thread.sleep(200);
+
+        // Try to read - may get data or timeout
+        try {
+            byte[] data = instance.read(0);
+            assertNotNull(data);
+        } catch (TransportException e) {
+            // Timeout is acceptable
+        }
+
+        instance.close();
+    }
+
+    @Test
+    void testPeekReadableBytes_withActualData() throws Exception {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.autoStart = true;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        Thread.sleep(200);
+
+        // Try to peek
+        try {
+            byte[] peeked = instance.peekReadableBytes(0);
+            assertNotNull(peeked);
+        } catch (TransportException e) {
+            // Timeout is acceptable
+        }
+
+        instance.close();
+    }
+
+    @Test
+    void testGetNumBytesAvailable_withRealPlayer() throws Exception {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.autoStart = true;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        Thread.sleep(150);
+
+        int available = instance.getNumBytesAvailable();
+        assertTrue(available >= 0);
+
+        instance.close();
+    }
+
+    @Test
+    void testStartStopReplay_withRealPlayer() throws Exception {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.autoStart = false;
+        config.loop = true;  // Enable looping so player keeps running
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertFalse(instance.isReplaying());
+
+        instance.startReplay();
+        Thread.sleep(100);  // Give it more time to start
+        assertTrue(instance.isReplaying());
+
+        instance.stopReplay();
+        Thread.sleep(50);
+        assertFalse(instance.isReplaying());
+
+        instance.close();
+    }
+
+    @Test
+    void testGetPacketsReplayed_withRealPlayer() throws Exception {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.autoStart = true;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        Thread.sleep(200);
+
+        long packets = instance.getPacketsReplayed();
+        assertTrue(packets >= 0);
+
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withReadTimeout() throws TransportException {
+        config.readTimeout = 1000; // 1 second timeout
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withDifferentPacketQueueSizes() throws 
TransportException {
+        config.packetQueueSize = 10;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+        assertNotNull(instance);
+        instance.close();
+
+        config.packetQueueSize = 5000;
+        instance = new PcapReplayTransportInstance(config, 
AuditLog.builder().build());
+        assertNotNull(instance);
+        instance.close();
+    }
+
+    @Test
+    void testMultipleReadsInSequence() throws Exception {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.autoStart = true;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        Thread.sleep(200);
+
+        // Try multiple reads
+        for (int i = 0; i < 3; i++) {
+            try {
+                byte[] data = instance.read(0);
+                assertNotNull(data);
+            } catch (TransportException e) {
+                // Timeout acceptable
+            }
+        }
+
+        instance.close();
+    }
+
+    @Test
+    void testRead_afterStoppingReplay() throws Exception {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.autoStart = true;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        Thread.sleep(100);
+
+        instance.stopReplay();
+        Thread.sleep(50);
+
+        // Read should still work but likely timeout
+        try {
+            byte[] data = instance.read(0);
+        } catch (TransportException e) {
+            // Expected timeout
+        }
+
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_allConfigurationCombinations() throws 
TransportException {
+        // Test various configuration combinations
+        config.protocolId = 0x88B5;
+        config.speedFactor = 1.0;
+        config.loop = true;
+        config.vlanId = 100;
+        config.onlyIncomingPackets = true;
+        config.maxFrameSize = 9000;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+        assertNotNull(instance);
+        assertTrue(instance.isOpen());
+        instance.close();
+    }
+
+    @Test
+    void testWrite_throwsException() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Writing should throw an exception as this is a replay-only transport
+        assertThrows(TransportException.class, () -> instance.write(new 
byte[]{1, 2, 3}));
+
+        instance.close();
+    }
+
+    @Test
+    void testPeekReadableBytes_withZeroBytes() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        byte[] result = instance.peekReadableBytes(0);
+        assertNotNull(result);
+        assertEquals(0, result.length);
+
+        instance.close();
+    }
+
+    @Test
+    void testPeekReadableBytes_withNegativeBytes() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Negative bytes should throw an exception
+        assertArrayEquals(new byte[0], instance.peekReadableBytes(-1));
+
+        instance.close();
+    }
+
+    @Test
+    void testRead_withZeroBytes() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        byte[] result = instance.read(0);
+        assertNotNull(result);
+        assertEquals(0, result.length);
+
+        instance.close();
+    }
+
+    @Test
+    void testRead_withNegativeBytes() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Negative bytes should throw an exception
+        assertArrayEquals(new byte[0], instance.read(-1));
+
+        instance.close();
+    }
+
+    @Test
+    void testMultipleClose() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        instance.close();
+        assertFalse(instance.isOpen());
+
+        // Closing again should be idempotent
+        instance.close();
+        assertFalse(instance.isOpen());
+    }
+
+    @Test
+    void testIsOpen_initialState() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        assertTrue(instance.isOpen());
+
+        instance.close();
+    }
+
+    @Test
+    void testGetNumBytesAvailable_initially() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Initially should be 0
+        assertEquals(0, instance.getNumBytesAvailable());
+
+        instance.close();
+    }
+
+    @Test
+    void testStartReplay_alreadyPlaying() throws TransportException {
+        config.mockPlayer = mockPlayer;
+        when(mockPlayer.isPlaying()).thenReturn(true);
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Start when already playing should not call start again
+        instance.startReplay();
+        verify(mockPlayer, times(0)).start();
+
+        instance.close();
+    }
+
+    @Test
+    void testStopReplay_notPlaying() throws TransportException {
+        config.mockPlayer = mockPlayer;
+        when(mockPlayer.isPlaying()).thenReturn(false);
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Stop when not playing should not call stop
+        instance.stopReplay();
+        verify(mockPlayer, times(0)).stop();
+
+        instance.close();
+    }
+
+    @Test
+    void testConstructor_withInvalidConfiguration() {
+        config.pcapFile = null;
+        config.mockPlayer = null;
+
+        // Should throw exception with invalid configuration
+        assertThrows(TransportException.class, () -> new 
PcapReplayTransportInstance(config, AuditLog.builder().build()));
+    }
+
+    @Test
+    void testConstructor_withEmptyMacAddress() {
+        config.mockPlayer = null;
+        config.pcapFile = "/test.pcap";
+        config.localAddress = "";
+        config.remoteAddress = "";
+        config.autoDetectMacAddresses = false;
+
+        // Should throw exception with empty MAC addresses
+        assertThrows(TransportException.class, () -> new 
PcapReplayTransportInstance(config, AuditLog.builder().build()));
+    }
+
+    @Test
+    void testPeekReadableBytes_moreThanAvailable() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Requesting more bytes than available should throw (player returns 
no data)
+        assertThrows(TransportException.class, () -> 
instance.peekReadableBytes(100));
+
+        instance.close();
+    }
+
+    @Test
+    void testRead_moreThanAvailable() throws TransportException {
+        config.mockPlayer = mockPlayer;
+
+        PcapReplayTransportInstance instance = new 
PcapReplayTransportInstance(config, AuditLog.builder().build());
+
+        // Requesting more bytes than available should throw (player returns 
no data)
+        assertThrows(TransportException.class, () -> instance.read(100));
+
+        instance.close();
+    }
+
+}
diff --git 
a/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportTest.java
 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportTest.java
new file mode 100644
index 0000000000..84e85eb90c
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/PcapReplayTransportTest.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay;
+
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.spi.transports.api.exceptions.TransportException;
+import 
org.apache.plc4x.java.transport.pcapreplay.config.PcapReplayTransportConfiguration;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.pcap4j.packet.factory.PacketFactories;
+import org.pcap4j.packet.namednumber.DataLinkType;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PcapReplayTransportTest {
+
+    private PcapReplayTransport transport;
+    private File tempPcapFile;
+
+    @BeforeAll
+    static void initPacketFactory() {
+        // Force pcap4j to eagerly load the PacketFactoryBinder.
+        // Without this, the factory loads lazily on the first player thread,
+        // causing a race where early tests finish before parsing is available.
+        PacketFactories.getFactory(org.pcap4j.packet.Packet.class, 
DataLinkType.class);
+    }
+
+    @BeforeEach
+    void setUp() throws IOException {
+        try {
+            // For some reason it doesn't work if we pass this in from the 
outside.
+            //if (os == "mac") {
+            // On my Intel Mac I found the libs in: 
"/usr/local/Cellar/libpcap/1.10.1/lib"
+            // On my M1 Mac I found the libs in: 
"/opt/homebrew/Cellar/libpcap/1.10.1/lib"
+            if (new File("/usr/local/Cellar/libpcap/1.10.1/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/usr/local/Cellar/libpcap/1.10.1/lib");
+            } else if (new 
File("/usr/local/Cellar/libpcap/1.10.5/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/usr/local/Cellar/libpcap/1.10.5/lib");
+            } else if (new File("/opt/homebrew/opt/libpcap/lib").exists()) {
+                System.getProperties().setProperty("jna.library.path", 
"/opt/homebrew/opt/libpcap/lib");
+            }
+            //}
+        } catch (Error e) {
+            e.printStackTrace();
+        }
+
+        transport = new PcapReplayTransport();
+
+        // Create a minimal test PCAP file
+        tempPcapFile = File.createTempFile("test", ".pcap");
+        // Note: For real tests, you'd need a valid PCAP file
+        // This is just for basic structure testing
+    }
+
+    @AfterEach
+    void tearDown() {
+        if (tempPcapFile != null && tempPcapFile.exists()) {
+            tempPcapFile.delete();
+        }
+    }
+
+    @Test
+    void testGetTransportCode() {
+        assertEquals("pcap-replay", transport.getTransportCode());
+    }
+
+    @Test
+    void testGetTransportName() {
+        assertEquals("PCAP Replay", transport.getTransportName());
+    }
+
+    @Test
+    void testGetTransportConfigType() {
+        assertEquals(PcapReplayTransportConfiguration.class, 
transport.getTransportConfigType());
+    }
+
+    @Test
+    void testCreateTransportInstance_fileNotFound() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        config.pcapFile = "/nonexistent/path/to/file.pcap";
+        config.autoDetectMacAddresses = false;
+        config.localAddress = "00:11:22:33:44:55";
+        config.remoteAddress = "AA:BB:CC:DD:EE:FF";
+        config.protocolId = 0x88B5;
+        config.maxFrameSize = 1500;
+
+        assertThrows(TransportException.class, () ->
+            transport.createTransportInstance("pcap-replay://test", config, 
AuditLog.builder().build())
+        );
+    }
+
+    @Test
+    void testCreateTransportInstance_missingMacAddresses() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        config.pcapFile = tempPcapFile.getAbsolutePath();
+        config.autoDetectMacAddresses = false;
+        config.maxFrameSize = 1500;
+        // Missing MAC addresses
+
+        assertThrows(TransportException.class, () ->
+            transport.createTransportInstance("pcap-replay://test", config, 
AuditLog.builder().build())
+        );
+    }
+
+    @Test
+    void testCreateTransportInstance_withManualMacAddresses() {
+        // This test would require a valid PCAP file
+        // Skipping actual creation due to need for valid PCAP content
+
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        config.pcapFile = tempPcapFile.getAbsolutePath();
+        config.autoDetectMacAddresses = false;
+        config.localAddress = "00:11:22:33:44:55";
+        config.remoteAddress = "AA:BB:CC:DD:EE:FF";
+        config.protocolId = 0x88B5;
+        config.autoStart = false;
+        config.maxFrameSize = 1500;
+
+        // Would fail due to invalid PCAP format, but tests configuration
+        assertThrows(TransportException.class, () ->
+            transport.createTransportInstance("pcap-replay://test", config, 
AuditLog.builder().build())
+        );
+    }
+
+    @Test
+    void testCreateTransportInstance_withValidPcap() throws Exception {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        config.pcapFile = "/test.pcap";
+        config.autoDetectMacAddresses = true;
+        config.protocolId = 0x88B5;
+        config.speedFactor = 0; // As fast as possible
+        config.loop = false;
+        config.autoStart = true;
+        config.maxFrameSize = 1500;
+        config.packetQueueSize = 1000;
+
+        TransportInstance<PcapReplayTransportConfiguration> instance =
+            transport.createTransportInstance("pcap-replay://test", config, 
AuditLog.builder().build());
+
+        assertNotNull(instance);
+        assertTrue(instance.isOpen());
+
+        instance.close();
+    }
+
+    @Test
+    void testCreateTransportInstance_withLoop() throws Exception {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        config.pcapFile = "/test.pcap";
+        config.autoDetectMacAddresses = true;
+        config.protocolId = 0x88B5;
+        config.speedFactor = 2.0; // Double speed
+        config.loop = true;
+        config.autoStart = true;
+        config.maxFrameSize = 1500;
+        config.packetQueueSize = 1000;
+
+        TransportInstance<PcapReplayTransportConfiguration> instance =
+            transport.createTransportInstance("pcap-replay://test", config, 
AuditLog.builder().build());
+
+        assertNotNull(instance);
+        assertTrue(instance.isOpen());
+
+        instance.close();
+    }
+
+    @Test
+    void testCreateTransportInstance_onlyIncoming() throws Exception {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        config.pcapFile = "/test.pcap";
+        config.autoDetectMacAddresses = true;
+        config.protocolId = 0x88B5;
+        config.onlyIncomingPackets = true;
+        config.onlyOutgoingPackets = false;
+        config.maxFrameSize = 1500;
+        config.packetQueueSize = 1000;
+
+        TransportInstance<PcapReplayTransportConfiguration> instance =
+            transport.createTransportInstance("pcap-replay://test", config, 
AuditLog.builder().build());
+
+        assertNotNull(instance);
+        instance.close();
+    }
+}
diff --git 
a/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/config/PcapReplayTransportConfigurationTest.java
 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/config/PcapReplayTransportConfigurationTest.java
new file mode 100644
index 0000000000..d1ea6d2318
--- /dev/null
+++ 
b/plc4j/transports/pcap-replay/src/test/java/org/apache/plc4x/java/transport/pcapreplay/config/PcapReplayTransportConfigurationTest.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.transport.pcapreplay.config;
+
+import org.apache.plc4x.java.spi.transports.api.config.TransportConfiguration;
+import org.apache.plc4x.java.transport.pcapreplay.PcapFilePlayer;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PcapReplayTransportConfigurationTest {
+
+    @Test
+    void testInstantiation() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertNotNull(config);
+    }
+
+    @Test
+    void testImplementsTransportConfiguration() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertTrue(config instanceof TransportConfiguration);
+    }
+
+    @Test
+    void testPcapFileField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertNull(config.pcapFile);
+
+        config.pcapFile = "/path/to/test.pcap";
+        assertEquals("/path/to/test.pcap", config.pcapFile);
+    }
+
+    @Test
+    void testLocalAddressField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertNull(config.localAddress);
+
+        config.localAddress = "00:11:22:33:44:55";
+        assertEquals("00:11:22:33:44:55", config.localAddress);
+    }
+
+    @Test
+    void testRemoteAddressField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertNull(config.remoteAddress);
+
+        config.remoteAddress = "AA:BB:CC:DD:EE:FF";
+        assertEquals("AA:BB:CC:DD:EE:FF", config.remoteAddress);
+    }
+
+    @Test
+    void testProtocolIdField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertEquals(0, config.protocolId);
+
+        config.protocolId = 0x88B5; // PROFINET
+        assertEquals(0x88B5, config.protocolId);
+    }
+
+    @Test
+    void testSpeedFactorField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertEquals(0.0, config.speedFactor, 0.001);
+
+        config.speedFactor = 1.0; // Real-time
+        assertEquals(1.0, config.speedFactor, 0.001);
+
+        config.speedFactor = 2.0; // Double speed
+        assertEquals(2.0, config.speedFactor, 0.001);
+
+        config.speedFactor = 0.5; // Half speed
+        assertEquals(0.5, config.speedFactor, 0.001);
+    }
+
+    @Test
+    void testLoopField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertFalse(config.loop);
+
+        config.loop = true;
+        assertTrue(config.loop);
+
+        config.loop = false;
+        assertFalse(config.loop);
+    }
+
+    @Test
+    void testOnlyIncomingPacketsField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertFalse(config.onlyIncomingPackets);
+
+        config.onlyIncomingPackets = true;
+        assertTrue(config.onlyIncomingPackets);
+
+        config.onlyIncomingPackets = false;
+        assertFalse(config.onlyIncomingPackets);
+    }
+
+    @Test
+    void testOnlyOutgoingPacketsField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertFalse(config.onlyOutgoingPackets);
+
+        config.onlyOutgoingPackets = true;
+        assertTrue(config.onlyOutgoingPackets);
+
+        config.onlyOutgoingPackets = false;
+        assertFalse(config.onlyOutgoingPackets);
+    }
+
+    @Test
+    void testMaxFrameSizeField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertEquals(0, config.maxFrameSize);
+
+        config.maxFrameSize = 1500; // Standard Ethernet MTU
+        assertEquals(1500, config.maxFrameSize);
+
+        config.maxFrameSize = 9000; // Jumbo frame
+        assertEquals(9000, config.maxFrameSize);
+    }
+
+    @Test
+    void testReadTimeoutField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertEquals(0, config.readTimeout);
+
+        config.readTimeout = 5000; // 5 seconds
+        assertEquals(5000, config.readTimeout);
+
+        config.readTimeout = 0; // No timeout
+        assertEquals(0, config.readTimeout);
+    }
+
+    @Test
+    void testPacketQueueSizeField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertEquals(0, config.packetQueueSize);
+
+        config.packetQueueSize = 1000;
+        assertEquals(1000, config.packetQueueSize);
+
+        config.packetQueueSize = 100;
+        assertEquals(100, config.packetQueueSize);
+    }
+
+    @Test
+    void testAutoDetectMacAddressesField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertFalse(config.autoDetectMacAddresses);
+
+        config.autoDetectMacAddresses = true;
+        assertTrue(config.autoDetectMacAddresses);
+
+        config.autoDetectMacAddresses = false;
+        assertFalse(config.autoDetectMacAddresses);
+    }
+
+    @Test
+    void testAutoStartField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertFalse(config.autoStart);
+
+        config.autoStart = true;
+        assertTrue(config.autoStart);
+
+        config.autoStart = false;
+        assertFalse(config.autoStart);
+    }
+
+    @Test
+    void testVlanIdField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertEquals(0, config.vlanId);
+
+        config.vlanId = 100;
+        assertEquals(100, config.vlanId);
+
+        config.vlanId = 4095; // Max VLAN ID
+        assertEquals(4095, config.vlanId);
+    }
+
+    @Test
+    void testMockPlayerField() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+        assertNull(config.mockPlayer);
+
+        PcapFilePlayer mockPlayer = Mockito.mock(PcapFilePlayer.class);
+        config.mockPlayer = mockPlayer;
+        assertEquals(mockPlayer, config.mockPlayer);
+    }
+
+    @Test
+    void testCompleteConfiguration() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+
+        // Set all fields
+        config.pcapFile = "/path/to/capture.pcap";
+        config.localAddress = "00:11:22:33:44:55";
+        config.remoteAddress = "AA:BB:CC:DD:EE:FF";
+        config.protocolId = 0x88B5;
+        config.speedFactor = 1.5;
+        config.loop = true;
+        config.onlyIncomingPackets = false;
+        config.onlyOutgoingPackets = true;
+        config.maxFrameSize = 1500;
+        config.readTimeout = 3000;
+        config.packetQueueSize = 500;
+        config.autoDetectMacAddresses = false;
+        config.autoStart = false;
+        config.vlanId = 200;
+        PcapFilePlayer mockPlayer = Mockito.mock(PcapFilePlayer.class);
+        config.mockPlayer = mockPlayer;
+
+        // Verify all fields
+        assertEquals("/path/to/capture.pcap", config.pcapFile);
+        assertEquals("00:11:22:33:44:55", config.localAddress);
+        assertEquals("AA:BB:CC:DD:EE:FF", config.remoteAddress);
+        assertEquals(0x88B5, config.protocolId);
+        assertEquals(1.5, config.speedFactor, 0.001);
+        assertTrue(config.loop);
+        assertFalse(config.onlyIncomingPackets);
+        assertTrue(config.onlyOutgoingPackets);
+        assertEquals(1500, config.maxFrameSize);
+        assertEquals(3000, config.readTimeout);
+        assertEquals(500, config.packetQueueSize);
+        assertFalse(config.autoDetectMacAddresses);
+        assertFalse(config.autoStart);
+        assertEquals(200, config.vlanId);
+        assertEquals(mockPlayer, config.mockPlayer);
+    }
+
+    @Test
+    void testMutuallyExclusivePacketDirection() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+
+        // Test that both can be set (validation would be done elsewhere)
+        config.onlyIncomingPackets = true;
+        config.onlyOutgoingPackets = true;
+
+        assertTrue(config.onlyIncomingPackets);
+        assertTrue(config.onlyOutgoingPackets);
+    }
+
+    @Test
+    void testNegativeValues() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+
+        // Test that negative values can be set (validation would be done 
elsewhere)
+        config.protocolId = -1;
+        config.maxFrameSize = -100;
+        config.readTimeout = -1000;
+        config.packetQueueSize = -50;
+        config.vlanId = -10;
+
+        assertEquals(-1, config.protocolId);
+        assertEquals(-100, config.maxFrameSize);
+        assertEquals(-1000, config.readTimeout);
+        assertEquals(-50, config.packetQueueSize);
+        assertEquals(-10, config.vlanId);
+    }
+
+    @Test
+    void testEdgeCaseSpeedFactors() {
+        PcapReplayTransportConfiguration config = new 
PcapReplayTransportConfiguration();
+
+        // Test edge cases
+        config.speedFactor = 0.0; // As fast as possible
+        assertEquals(0.0, config.speedFactor, 0.001);
+
+        config.speedFactor = Double.MAX_VALUE;
+        assertEquals(Double.MAX_VALUE, config.speedFactor, 0.001);
+
+        config.speedFactor = Double.MIN_VALUE;
+        assertEquals(Double.MIN_VALUE, config.speedFactor, 0.001);
+
+        config.speedFactor = -1.0; // Negative (validation would handle this)
+        assertEquals(-1.0, config.speedFactor, 0.001);
+    }
+}
diff --git 
a/plc4j/transports/pcap-replay/src/test/resources/generate_test_pcap.py 
b/plc4j/transports/pcap-replay/src/test/resources/generate_test_pcap.py
new file mode 100644
index 0000000000..7cf580aa2b
--- /dev/null
+++ b/plc4j/transports/pcap-replay/src/test/resources/generate_test_pcap.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+"""
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+Generate a test PCAP file with sample Ethernet frames.
+This script creates a minimal PCAP file for testing the pcap-replay transport.
+"""
+
+import struct
+import time
+
+def write_pcap_header(f):
+    """Write PCAP global header."""
+    magic = 0xa1b2c3d4  # Magic number (microsecond precision)
+    version_major = 2
+    version_minor = 4
+    thiszone = 0  # GMT to local correction
+    sigfigs = 0   # Accuracy of timestamps
+    snaplen = 65535  # Max length of captured packets
+    network = 1   # Data link type (1 = Ethernet)
+
+    f.write(struct.pack('<IHHIIII', magic, version_major, version_minor,
+                        thiszone, sigfigs, snaplen, network))
+
+def write_packet(f, data, timestamp=None):
+    """Write a packet to the PCAP file."""
+    if timestamp is None:
+        timestamp = time.time()
+
+    ts_sec = int(timestamp)
+    ts_usec = int((timestamp - ts_sec) * 1000000)
+    incl_len = len(data)
+    orig_len = len(data)
+
+    # Write packet header
+    f.write(struct.pack('<IIII', ts_sec, ts_usec, incl_len, orig_len))
+    # Write packet data
+    f.write(data)
+
+def create_ethernet_frame(dst_mac, src_mac, ethertype, payload):
+    """Create an Ethernet frame."""
+    frame = bytearray()
+
+    # Destination MAC (6 bytes)
+    frame.extend(bytes.fromhex(dst_mac.replace(':', '')))
+
+    # Source MAC (6 bytes)
+    frame.extend(bytes.fromhex(src_mac.replace(':', '')))
+
+    # EtherType (2 bytes)
+    frame.extend(struct.pack('>H', ethertype))
+
+    # Payload
+    frame.extend(payload)
+
+    # Padding to minimum frame size (60 bytes without FCS)
+    while len(frame) < 60:
+        frame.append(0)
+
+    return bytes(frame)
+
+def main():
+    """Generate test.pcap file."""
+
+    # MAC addresses matching the test
+    local_mac = '00:11:22:33:44:55'
+    remote_mac = 'AA:BB:CC:DD:EE:FF'
+
+    # Custom EtherType for testing (using a test protocol ID)
+    ethertype = 0x88B5  # Example custom protocol
+
+    # Create test payloads
+    payloads = [
+        b'\x01\x02\x03\x04',  # Simple test data
+        b'\x05\x06\x07\x08\x09\x0A',  # Another test packet
+        b'Hello World!',  # Text payload
+        bytes(range(32)),  # Sequential bytes
+    ]
+
+    output_file = 'test.pcap'
+
+    with open(output_file, 'wb') as f:
+        # Write PCAP header
+        write_pcap_header(f)
+
+        base_time = time.time()
+
+        # Write packets with different directions
+        for i, payload in enumerate(payloads):
+            timestamp = base_time + (i * 0.01)  # 10ms apart
+
+            # Alternate between incoming and outgoing
+            if i % 2 == 0:
+                # Incoming: from remote to local
+                frame = create_ethernet_frame(local_mac, remote_mac, 
ethertype, payload)
+            else:
+                # Outgoing: from local to remote
+                frame = create_ethernet_frame(remote_mac, local_mac, 
ethertype, payload)
+
+            write_packet(f, frame, timestamp)
+
+    print(f'Created {output_file} with {len(payloads)} packets')
+    print(f'Local MAC: {local_mac}')
+    print(f'Remote MAC: {remote_mac}')
+    print(f'EtherType: 0x{ethertype:04X}')
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
diff --git a/plc4j/transports/pcap-replay/src/test/resources/test.pcap 
b/plc4j/transports/pcap-replay/src/test/resources/test.pcap
new file mode 100644
index 0000000000..006123a142
Binary files /dev/null and 
b/plc4j/transports/pcap-replay/src/test/resources/test.pcap differ
diff --git a/plc4j/transports/pom.xml b/plc4j/transports/pom.xml
index a64de57529..0425563f30 100644
--- a/plc4j/transports/pom.xml
+++ b/plc4j/transports/pom.xml
@@ -39,9 +39,9 @@
 
   <modules>
     <module>api</module>
-    <!--module>can</module>
+    <!--module>can</module-->
     <module>pcap-replay</module>
-    <module>pcap-shared</module>
+    <!--module>pcap-shared</module>
     <module>raw-socket</module>
     <module>socketcan</module-->
     <module>serial</module>

Reply via email to