You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2018/02/17 17:53:05 UTC

[incubator-plc4x] branch master updated: implemented roundtrip tests and fixed a bunch of bugs while at it + this led to implementation of equals and hashcode in all relevant classes + remove todos about the confusing calculated boolean flag and make use of getters

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

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


The following commit(s) were added to refs/heads/master by this push:
     new c709691  implemented roundtrip tests and fixed a bunch of bugs while at it + this led to implementation of equals and hashcode in all relevant classes + remove todos about the confusing calculated boolean flag and make use of getters
c709691 is described below

commit c7096916792a1cb8b54bcde84c01ad6cdf7c0592
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Feb 17 18:53:00 2018 +0100

    implemented roundtrip tests and fixed a bunch of bugs while at it
    + this led to implementation of equals and hashcode in all relevant classes
    + remove todos about the confusing calculated boolean flag and make use of getters
---
 .../commands/ADSAddDeviceNotificationRequest.java  | 36 ++++++++++++-
 .../commands/ADSAddDeviceNotificationResponse.java | 20 +++++++
 .../ADSDeleteDeviceNotificationRequest.java        | 18 +++++++
 .../ADSDeleteDeviceNotificationResponse.java       | 18 +++++++
 .../api/commands/ADSDeviceNotificationRequest.java | 38 ++++++++-----
 .../api/commands/ADSReadDeviceInfoResponse.java    | 26 +++++++++
 .../java/ads/api/commands/ADSReadRequest.java      | 22 ++++++++
 .../java/ads/api/commands/ADSReadResponse.java     | 43 ++++++++-------
 .../ads/api/commands/ADSReadStateResponse.java     | 18 +++++++
 .../java/ads/api/commands/ADSReadWriteRequest.java | 50 ++++++++++-------
 .../ads/api/commands/ADSReadWriteResponse.java     | 37 +++++++------
 .../ads/api/commands/ADSWriteControlRequest.java   | 40 ++++++++------
 .../ads/api/commands/ADSWriteControlResponse.java  | 18 +++++++
 .../java/ads/api/commands/ADSWriteRequest.java     | 45 ++++++++++------
 .../java/ads/api/commands/ADSWriteResponse.java    | 18 +++++++
 .../java/ads/api/commands/UnknownCommand.java      | 18 +++++++
 .../api/commands/types/AdsNotificationSample.java  | 33 +++++++-----
 .../ads/api/commands/types/AdsStampHeader.java     | 20 +++++++
 .../plc4x/java/ads/api/commands/types/Length.java  |  5 ++
 .../java/ads/api/commands/types/SampleSize.java    |  6 +++
 .../java/ads/api/commands/types/TimeStamp.java     | 16 +++---
 .../java/ads/api/commands/types/WriteLength.java   |  5 ++
 .../plc4x/java/ads/api/generic/AMSHeader.java      | 62 +++++++++++++++-------
 .../plc4x/java/ads/api/generic/AMSTCPHeader.java   | 49 +++++++++--------
 .../plc4x/java/ads/api/generic/AMSTCPPacket.java   | 18 +++++++
 .../java/ads/api/generic/types/DataLength.java     |  5 ++
 .../apache/plc4x/java/ads/api/util/ByteValue.java  |  2 +-
 .../plc4x/java/ads/netty/ADSProtocolTest.java      | 19 ++++++-
 28 files changed, 538 insertions(+), 167 deletions(-)

diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationRequest.java
index aa777f9..b04d69a 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationRequest.java
@@ -19,7 +19,6 @@
 package org.apache.plc4x.java.ads.api.commands;
 
 import org.apache.plc4x.java.ads.api.commands.types.*;
-import org.apache.plc4x.java.ads.api.commands.types.Length;
 import org.apache.plc4x.java.ads.api.generic.ADSData;
 import org.apache.plc4x.java.ads.api.generic.AMSHeader;
 import org.apache.plc4x.java.ads.api.generic.AMSTCPHeader;
@@ -64,7 +63,7 @@ public class ADSAddDeviceNotificationRequest extends ADSAbstractRequest {
     /**
      * 16bytes	Must be set to 0
      */
-    private final Reserved reserved = Reserved.INSTANCE;
+    private final Reserved reserved;
 
     private ADSAddDeviceNotificationRequest(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, Length length, TransmissionMode transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
         super(amstcpHeader, amsHeader);
@@ -74,6 +73,7 @@ public class ADSAddDeviceNotificationRequest extends ADSAbstractRequest {
         this.transmissionMode = requireNonNull(transmissionMode);
         this.maxDelay = requireNonNull(maxDelay);
         this.cycleTime = requireNonNull(cycleTime);
+        this.reserved = Reserved.INSTANCE;
     }
 
     private ADSAddDeviceNotificationRequest(AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, Length length, TransmissionMode transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
@@ -84,6 +84,7 @@ public class ADSAddDeviceNotificationRequest extends ADSAbstractRequest {
         this.transmissionMode = requireNonNull(transmissionMode);
         this.maxDelay = requireNonNull(maxDelay);
         this.cycleTime = requireNonNull(cycleTime);
+        this.reserved = Reserved.INSTANCE;
     }
 
     private ADSAddDeviceNotificationRequest(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Invoke invokeId, IndexGroup indexGroup, IndexOffset indexOffset, Length length, TransmissionMode transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
@@ -94,6 +95,7 @@ public class ADSAddDeviceNotificationRequest extends ADSAbstractRequest {
         this.transmissionMode = requireNonNull(transmissionMode);
         this.maxDelay = requireNonNull(maxDelay);
         this.cycleTime = requireNonNull(cycleTime);
+        this.reserved = Reserved.INSTANCE;
     }
 
     @Override
@@ -142,6 +144,36 @@ public class ADSAddDeviceNotificationRequest extends ADSAbstractRequest {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSAddDeviceNotificationRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSAddDeviceNotificationRequest that = (ADSAddDeviceNotificationRequest) o;
+
+        if (!indexGroup.equals(that.indexGroup)) return false;
+        if (!indexOffset.equals(that.indexOffset)) return false;
+        if (!length.equals(that.length)) return false;
+        if (!transmissionMode.equals(that.transmissionMode)) return false;
+        if (!maxDelay.equals(that.maxDelay)) return false;
+        if (!cycleTime.equals(that.cycleTime)) return false;
+        return reserved.equals(that.reserved);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + indexGroup.hashCode();
+        result = 31 * result + indexOffset.hashCode();
+        result = 31 * result + length.hashCode();
+        result = 31 * result + transmissionMode.hashCode();
+        result = 31 * result + maxDelay.hashCode();
+        result = 31 * result + cycleTime.hashCode();
+        result = 31 * result + reserved.hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "ADSAddDeviceNotificationRequest{" +
             "indexGroup=" + indexGroup +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationResponse.java
index fe22e62..d86666a 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSAddDeviceNotificationResponse.java
@@ -80,6 +80,26 @@ public class ADSAddDeviceNotificationResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSAddDeviceNotificationResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSAddDeviceNotificationResponse that = (ADSAddDeviceNotificationResponse) o;
+
+        if (!result.equals(that.result)) return false;
+        return notificationHandle.equals(that.notificationHandle);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        result1 = 31 * result1 + notificationHandle.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSAddDeviceNotificationResponse{" +
             "result=" + result +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationRequest.java
index e6c3605..fb400b3 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationRequest.java
@@ -77,6 +77,24 @@ public class ADSDeleteDeviceNotificationRequest extends ADSAbstractRequest {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSDeleteDeviceNotificationRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSDeleteDeviceNotificationRequest that = (ADSDeleteDeviceNotificationRequest) o;
+
+        return notificationHandle.equals(that.notificationHandle);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + notificationHandle.hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "ADSDeleteDeviceNotificationRequest{" +
             "notificationHandle=" + notificationHandle +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationResponse.java
index 36fe661..92b2b2b 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeleteDeviceNotificationResponse.java
@@ -68,6 +68,24 @@ public class ADSDeleteDeviceNotificationResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSDeleteDeviceNotificationResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSDeleteDeviceNotificationResponse that = (ADSDeleteDeviceNotificationResponse) o;
+
+        return result.equals(that.result);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSDeleteDeviceNotificationResponse{" +
             "result=" + result +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeviceNotificationRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeviceNotificationRequest.java
index 2a3b71e..7297e3c 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeviceNotificationRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSDeviceNotificationRequest.java
@@ -56,19 +56,13 @@ public class ADSDeviceNotificationRequest extends ADSAbstractRequest {
      */
     private final List<AdsStampHeader> adsStampHeaders;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
 
     private ADSDeviceNotificationRequest(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, Length length, Stamps stamps, List<AdsStampHeader> adsStampHeaders) {
         super(amstcpHeader, amsHeader);
         this.length = requireNonNull(length);
         this.stamps = requireNonNull(stamps);
         this.adsStampHeaders = requireNonNull(adsStampHeaders);
-        calculated = false;
         lengthSupplier = null;
     }
 
@@ -77,7 +71,6 @@ public class ADSDeviceNotificationRequest extends ADSAbstractRequest {
         this.length = requireNonNull(length);
         this.stamps = requireNonNull(stamps);
         this.adsStampHeaders = requireNonNull(adsStampHeaders);
-        calculated = false;
         lengthSupplier = null;
     }
 
@@ -86,7 +79,6 @@ public class ADSDeviceNotificationRequest extends ADSAbstractRequest {
         this.length = null;
         this.stamps = requireNonNull(stamps);
         this.adsStampHeaders = requireNonNull(adsStampHeaders);
-        calculated = true;
         this.lengthSupplier = () -> {
             long aggregateLength = 0;
             for (LengthSupplier supplier : adsStampHeaders) {
@@ -109,7 +101,7 @@ public class ADSDeviceNotificationRequest extends ADSAbstractRequest {
     }
 
     public Length getLength() {
-        return length;
+        return lengthSupplier == null ? length : Length.of(lengthSupplier);
     }
 
     public Stamps getStamps() {
@@ -124,19 +116,37 @@ public class ADSDeviceNotificationRequest extends ADSAbstractRequest {
         return lengthSupplier;
     }
 
-    public boolean isCalculated() {
-        return calculated;
+    @Override
+    public ADSData getAdsData() {
+        return buildADSData(getLength(), stamps, buildADSData(adsStampHeaders.toArray(new ByteReadable[adsStampHeaders.size()])));
     }
 
     @Override
-    public ADSData getAdsData() {
-        return buildADSData(calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length, stamps, buildADSData(adsStampHeaders.toArray(new ByteReadable[adsStampHeaders.size()])));
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSDeviceNotificationRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSDeviceNotificationRequest that = (ADSDeviceNotificationRequest) o;
+
+        if (!getLength().equals(that.getLength())) return false;
+        if (!stamps.equals(that.stamps)) return false;
+        return adsStampHeaders.equals(that.adsStampHeaders);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + getLength().hashCode();
+        result = 31 * result + stamps.hashCode();
+        result = 31 * result + adsStampHeaders.hashCode();
+        return result;
     }
 
     @Override
     public String toString() {
         return "ADSDeviceNotificationRequest{" +
-            "length=" + (calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length) +
+            "length=" + getLength() +
             ", stamps=" + stamps +
             ", adsStampHeaders=" + adsStampHeaders +
             "} " + super.toString();
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadDeviceInfoResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadDeviceInfoResponse.java
index 3345cc9..9f19ece 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadDeviceInfoResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadDeviceInfoResponse.java
@@ -108,6 +108,32 @@ public class ADSReadDeviceInfoResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSReadDeviceInfoResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSReadDeviceInfoResponse that = (ADSReadDeviceInfoResponse) o;
+
+        if (!result.equals(that.result)) return false;
+        if (!majorVersion.equals(that.majorVersion)) return false;
+        if (!minorVersion.equals(that.minorVersion)) return false;
+        if (!version.equals(that.version)) return false;
+        return device.equals(that.device);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        result1 = 31 * result1 + majorVersion.hashCode();
+        result1 = 31 * result1 + minorVersion.hashCode();
+        result1 = 31 * result1 + version.hashCode();
+        result1 = 31 * result1 + device.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSReadDeviceInfoResponse{" +
             "result=" + result +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadRequest.java
index 55f7225..6ba73ed 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadRequest.java
@@ -103,6 +103,28 @@ public class ADSReadRequest extends ADSAbstractRequest {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSReadRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSReadRequest that = (ADSReadRequest) o;
+
+        if (!indexGroup.equals(that.indexGroup)) return false;
+        if (!indexOffset.equals(that.indexOffset)) return false;
+        return length.equals(that.length);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + indexGroup.hashCode();
+        result = 31 * result + indexOffset.hashCode();
+        result = 31 * result + length.hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "ADSReadRequest{" +
             "indexGroup=" + indexGroup +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadResponse.java
index 7b5e7e9..2d6686d 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadResponse.java
@@ -51,12 +51,7 @@ public class ADSReadResponse extends ADSAbstractResponse {
      */
     private final Data data;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
 
     private ADSReadResponse(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, Result result, Length length, Data data) {
         super(amstcpHeader, amsHeader);
@@ -64,7 +59,6 @@ public class ADSReadResponse extends ADSAbstractResponse {
         this.length = requireNonNull(length);
         this.data = requireNonNull(data);
         this.lengthSupplier = null;
-        this.calculated = false;
     }
 
     private ADSReadResponse(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Invoke invokeId, Result result, Data data) {
@@ -73,7 +67,6 @@ public class ADSReadResponse extends ADSAbstractResponse {
         this.length = null;
         this.data = requireNonNull(data);
         this.lengthSupplier = data;
-        this.calculated = true;
     }
 
     public static ADSReadResponse of(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, Result result, Length length, Data data) {
@@ -84,17 +77,9 @@ public class ADSReadResponse extends ADSAbstractResponse {
         return new ADSReadResponse(targetAmsNetId, targetAmsPort, sourceAmsNetId, sourceAmsPort, invokeId, result, data);
     }
 
-    public LengthSupplier getLengthSupplier() {
-        return lengthSupplier;
-    }
-
-    public boolean isCalculated() {
-        return calculated;
-    }
-
     @Override
     public ADSData getAdsData() {
-        return buildADSData(result, calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length, data);
+        return buildADSData(result, getLength(), data);
     }
 
     public Result getResult() {
@@ -102,7 +87,7 @@ public class ADSReadResponse extends ADSAbstractResponse {
     }
 
     public Length getLength() {
-        return calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length;
+        return lengthSupplier == null ? length : Length.of(lengthSupplier);
     }
 
     public Data getData() {
@@ -110,10 +95,32 @@ public class ADSReadResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSReadResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSReadResponse that = (ADSReadResponse) o;
+
+        if (!result.equals(that.result)) return false;
+        if (!getLength().equals(that.getLength())) return false;
+        return data.equals(that.data);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        result1 = 31 * result1 + getLength().hashCode();
+        result1 = 31 * result1 + data.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSReadResponse{" +
             "result=" + result +
-            ", length=" + (calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length) +
+            ", length=" + getLength() +
             ", data=" + data +
             "} " + super.toString();
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadStateResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadStateResponse.java
index c8e7fcd..20d08fc 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadStateResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadStateResponse.java
@@ -68,6 +68,24 @@ public class ADSReadStateResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSReadStateResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSReadStateResponse that = (ADSReadStateResponse) o;
+
+        return result.equals(that.result);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSReadStateResponse{" +
             "result=" + result +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteRequest.java
index 085299f..45847eb 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteRequest.java
@@ -59,12 +59,7 @@ public class ADSReadWriteRequest extends ADSAbstractRequest {
      */
     private final Data data;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
-    private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
+    private final LengthSupplier writeLengthSupplier;
 
     private ADSReadWriteRequest(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, ReadLength readLength, WriteLength writeLength, Data data) {
         super(amstcpHeader, amsHeader);
@@ -73,8 +68,7 @@ public class ADSReadWriteRequest extends ADSAbstractRequest {
         this.readLength = requireNonNull(readLength);
         this.writeLength = requireNonNull(writeLength);
         this.data = requireNonNull(data);
-        this.lengthSupplier = null;
-        this.calculated = false;
+        this.writeLengthSupplier = null;
     }
 
     private ADSReadWriteRequest(AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, ReadLength readLength, WriteLength writeLength, Data data) {
@@ -84,8 +78,7 @@ public class ADSReadWriteRequest extends ADSAbstractRequest {
         this.readLength = requireNonNull(readLength);
         this.writeLength = requireNonNull(writeLength);
         this.data = requireNonNull(data);
-        this.lengthSupplier = null;
-        this.calculated = false;
+        this.writeLengthSupplier = null;
     }
 
     private ADSReadWriteRequest(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Invoke invokeId, IndexGroup indexGroup, IndexOffset indexOffset, ReadLength readLength, Data data) {
@@ -95,8 +88,7 @@ public class ADSReadWriteRequest extends ADSAbstractRequest {
         this.readLength = requireNonNull(readLength);
         this.writeLength = null;
         this.data = requireNonNull(data);
-        this.lengthSupplier = data;
-        this.calculated = true;
+        this.writeLengthSupplier = data;
     }
 
     public static ADSReadWriteRequest of(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, ReadLength readLength, WriteLength writeLength, Data data) {
@@ -124,24 +116,42 @@ public class ADSReadWriteRequest extends ADSAbstractRequest {
     }
 
     public WriteLength getWriteLength() {
-        return writeLength;
+        return writeLengthSupplier == null ? writeLength : WriteLength.of(writeLengthSupplier.getCalculatedLength());
     }
 
     public Data getData() {
         return data;
     }
 
-    public LengthSupplier getLengthSupplier() {
-        return lengthSupplier;
+    @Override
+    public ADSData getAdsData() {
+        return buildADSData(indexGroup, indexOffset, readLength, getWriteLength(), data);
     }
 
-    public boolean isCalculated() {
-        return calculated;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSReadWriteRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSReadWriteRequest that = (ADSReadWriteRequest) o;
+
+        if (!indexGroup.equals(that.indexGroup)) return false;
+        if (!indexOffset.equals(that.indexOffset)) return false;
+        if (!readLength.equals(that.readLength)) return false;
+        if (!getWriteLength().equals(that.getWriteLength())) return false;
+        return data.equals(that.data);
     }
 
     @Override
-    public ADSData getAdsData() {
-        return buildADSData(indexGroup, indexOffset, readLength, calculated ? WriteLength.of(lengthSupplier.getCalculatedLength()) : writeLength, data);
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + indexGroup.hashCode();
+        result = 31 * result + indexOffset.hashCode();
+        result = 31 * result + readLength.hashCode();
+        result = 31 * result + getWriteLength().hashCode();
+        result = 31 * result + data.hashCode();
+        return result;
     }
 
     @Override
@@ -150,7 +160,7 @@ public class ADSReadWriteRequest extends ADSAbstractRequest {
             "indexGroup=" + indexGroup +
             ", indexOffset=" + indexOffset +
             ", readLength=" + readLength +
-            ", writeLength=" + (calculated ? WriteLength.of(lengthSupplier.getCalculatedLength()) : writeLength) +
+            ", writeLength=" + getWriteLength() +
             ", data=" + data +
             "} " + super.toString();
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteResponse.java
index 8e94016..0ac55c2 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSReadWriteResponse.java
@@ -53,12 +53,7 @@ public class ADSReadWriteResponse extends ADSAbstractResponse {
      */
     private final Data data;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
 
     private ADSReadWriteResponse(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, Result result, Length length, Data data) {
         super(amstcpHeader, amsHeader);
@@ -66,7 +61,6 @@ public class ADSReadWriteResponse extends ADSAbstractResponse {
         this.length = requireNonNull(length);
         this.data = requireNonNull(data);
         this.lengthSupplier = null;
-        this.calculated = false;
     }
 
     private ADSReadWriteResponse(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Invoke invokeId, Result result, Data data) {
@@ -75,7 +69,6 @@ public class ADSReadWriteResponse extends ADSAbstractResponse {
         this.length = null;
         this.data = requireNonNull(data);
         this.lengthSupplier = data;
-        this.calculated = true;
     }
 
     public static ADSReadWriteResponse of(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, Result result, Length length, Data data) {
@@ -91,31 +84,45 @@ public class ADSReadWriteResponse extends ADSAbstractResponse {
     }
 
     public Length getLength() {
-        return length;
+        return lengthSupplier == null ? length : Length.of(lengthSupplier);
     }
 
     public Data getData() {
         return data;
     }
 
-    public LengthSupplier getLengthSupplier() {
-        return lengthSupplier;
+    @Override
+    public ADSData getAdsData() {
+        return buildADSData(result, getLength(), data);
     }
 
-    public boolean isCalculated() {
-        return calculated;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSReadWriteResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSReadWriteResponse that = (ADSReadWriteResponse) o;
+
+        if (!result.equals(that.result)) return false;
+        if (!getLength().equals(that.getLength())) return false;
+        return data.equals(that.data);
     }
 
     @Override
-    public ADSData getAdsData() {
-        return buildADSData(result, calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length, data);
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        result1 = 31 * result1 + getLength().hashCode();
+        result1 = 31 * result1 + data.hashCode();
+        return result1;
     }
 
     @Override
     public String toString() {
         return "ADSReadWriteResponse{" +
             "result=" + result +
-            ", length=" + (calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length) +
+            ", length=" + getLength() +
             ", data=" + data +
             "} " + super.toString();
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlRequest.java
index c4ab86f..b5cf50a 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlRequest.java
@@ -58,12 +58,7 @@ public class ADSWriteControlRequest extends ADSAbstractRequest {
      */
     private final Data data;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
 
     private ADSWriteControlRequest(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, ADSState adsState, DeviceState deviceState, Length length, Data data) {
         super(amstcpHeader, amsHeader);
@@ -72,7 +67,6 @@ public class ADSWriteControlRequest extends ADSAbstractRequest {
         this.length = requireNonNull(length);
         this.data = requireNonNull(data);
         this.lengthSupplier = null;
-        this.calculated = false;
     }
 
     private ADSWriteControlRequest(AMSHeader amsHeader, ADSState adsState, DeviceState deviceState, Length length, Data data) {
@@ -82,7 +76,6 @@ public class ADSWriteControlRequest extends ADSAbstractRequest {
         this.length = requireNonNull(length);
         this.data = requireNonNull(data);
         this.lengthSupplier = null;
-        this.calculated = false;
     }
 
     private ADSWriteControlRequest(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Invoke invokeId, ADSState adsState, DeviceState deviceState, Data data) {
@@ -92,7 +85,6 @@ public class ADSWriteControlRequest extends ADSAbstractRequest {
         this.length = null;
         this.data = requireNonNull(data);
         this.lengthSupplier = data;
-        this.calculated = true;
     }
 
     public static ADSWriteControlRequest of(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, ADSState adsState, DeviceState deviceState, Length length, Data data) {
@@ -116,24 +108,40 @@ public class ADSWriteControlRequest extends ADSAbstractRequest {
     }
 
     public Length getLength() {
-        return length;
+        return lengthSupplier == null ? length : Length.of(lengthSupplier);
     }
 
     public Data getData() {
         return data;
     }
 
-    public LengthSupplier getLengthSupplier() {
-        return lengthSupplier;
+    @Override
+    public ADSData getAdsData() {
+        return buildADSData(adsState, deviceState, getLength(), data);
     }
 
-    public boolean isCalculated() {
-        return calculated;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSWriteControlRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSWriteControlRequest that = (ADSWriteControlRequest) o;
+
+        if (!adsState.equals(that.adsState)) return false;
+        if (!deviceState.equals(that.deviceState)) return false;
+        if (!getLength().equals(that.getLength())) return false;
+        return data.equals(that.data);
     }
 
     @Override
-    public ADSData getAdsData() {
-        return buildADSData(adsState, deviceState, calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length, data);
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + adsState.hashCode();
+        result = 31 * result + deviceState.hashCode();
+        result = 31 * result + getLength().hashCode();
+        result = 31 * result + data.hashCode();
+        return result;
     }
 
     @Override
@@ -141,7 +149,7 @@ public class ADSWriteControlRequest extends ADSAbstractRequest {
         return "ADSWriteControlRequest{" +
             "adsState=" + adsState +
             ", deviceState=" + deviceState +
-            ", length=" + (calculated ? Length.of(lengthSupplier.getCalculatedLength()) : length) +
+            ", length=" + getLength() +
             ", data=" + data +
             "} " + super.toString();
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlResponse.java
index 89b56ab..8bbbede 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteControlResponse.java
@@ -67,6 +67,24 @@ public class ADSWriteControlResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSWriteControlResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSWriteControlResponse that = (ADSWriteControlResponse) o;
+
+        return result.equals(that.result);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSWriteControlResponse{" +
             "result=" + result +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteRequest.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteRequest.java
index 4f42c0d..d103ad5 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteRequest.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteRequest.java
@@ -18,7 +18,10 @@
  */
 package org.apache.plc4x.java.ads.api.commands;
 
-import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.commands.types.Data;
+import org.apache.plc4x.java.ads.api.commands.types.IndexGroup;
+import org.apache.plc4x.java.ads.api.commands.types.IndexOffset;
+import org.apache.plc4x.java.ads.api.commands.types.Length;
 import org.apache.plc4x.java.ads.api.generic.ADSData;
 import org.apache.plc4x.java.ads.api.generic.AMSHeader;
 import org.apache.plc4x.java.ads.api.generic.AMSTCPHeader;
@@ -53,12 +56,7 @@ public class ADSWriteRequest extends ADSAbstractRequest {
      */
     private final Data data;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
 
     private ADSWriteRequest(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, Length length, Data data) {
         super(amstcpHeader, amsHeader);
@@ -67,7 +65,6 @@ public class ADSWriteRequest extends ADSAbstractRequest {
         this.length = requireNonNull(length);
         this.data = requireNonNull(data);
         this.lengthSupplier = null;
-        this.calculated = false;
     }
 
     private ADSWriteRequest(AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, Length length, Data data) {
@@ -77,7 +74,6 @@ public class ADSWriteRequest extends ADSAbstractRequest {
         this.length = requireNonNull(length);
         this.data = requireNonNull(data);
         this.lengthSupplier = null;
-        this.calculated = false;
     }
 
     private ADSWriteRequest(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Invoke invokeId, IndexGroup indexGroup, IndexOffset indexOffset, Data data) {
@@ -87,7 +83,6 @@ public class ADSWriteRequest extends ADSAbstractRequest {
         this.length = null;
         this.data = requireNonNull(data);
         this.lengthSupplier = data;
-        this.calculated = true;
     }
 
     public static ADSWriteRequest of(AMSTCPHeader amstcpHeader, AMSHeader amsHeader, IndexGroup indexGroup, IndexOffset indexOffset, Length length, Data data) {
@@ -111,24 +106,40 @@ public class ADSWriteRequest extends ADSAbstractRequest {
     }
 
     public Length getLength() {
-        return length;
+        return lengthSupplier == null ? length : Length.of(lengthSupplier);
     }
 
     public Data getData() {
         return data;
     }
 
-    public LengthSupplier getLengthSupplier() {
-        return lengthSupplier;
+    @Override
+    public ADSData getAdsData() {
+        return buildADSData(indexGroup, indexOffset, getLength(), data);
     }
 
-    public boolean isCalculated() {
-        return calculated;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSWriteRequest)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSWriteRequest that = (ADSWriteRequest) o;
+
+        if (!indexGroup.equals(that.indexGroup)) return false;
+        if (!indexOffset.equals(that.indexOffset)) return false;
+        if (!getLength().equals(that.getLength())) return false;
+        return data.equals(that.data);
     }
 
     @Override
-    public ADSData getAdsData() {
-        return buildADSData(indexGroup, indexOffset, calculated ? WriteLength.of(lengthSupplier.getCalculatedLength()) : length, data);
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + indexGroup.hashCode();
+        result = 31 * result + indexOffset.hashCode();
+        result = 31 * result + getLength().hashCode();
+        result = 31 * result + data.hashCode();
+        return result;
     }
 
     @Override
@@ -136,7 +147,7 @@ public class ADSWriteRequest extends ADSAbstractRequest {
         return "ADSWriteRequest{" +
             "indexGroup=" + indexGroup +
             ", indexOffset=" + indexOffset +
-            ", length=" + (calculated ? WriteLength.of(lengthSupplier.getCalculatedLength()) : length) +
+            ", length=" + getLength() +
             ", data=" + data +
             "} " + super.toString();
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteResponse.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteResponse.java
index 67f5686..fa79968 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteResponse.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/ADSWriteResponse.java
@@ -68,6 +68,24 @@ public class ADSWriteResponse extends ADSAbstractResponse {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ADSWriteResponse)) return false;
+        if (!super.equals(o)) return false;
+
+        ADSWriteResponse that = (ADSWriteResponse) o;
+
+        return result.equals(that.result);
+    }
+
+    @Override
+    public int hashCode() {
+        int result1 = super.hashCode();
+        result1 = 31 * result1 + result.hashCode();
+        return result1;
+    }
+
+    @Override
     public String toString() {
         return "ADSWriteResponse{" +
             "result=" + result +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/UnknownCommand.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/UnknownCommand.java
index 8eb775b..586136b 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/UnknownCommand.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/UnknownCommand.java
@@ -59,6 +59,24 @@ public class UnknownCommand extends AMSTCPPacket {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof UnknownCommand)) return false;
+        if (!super.equals(o)) return false;
+
+        UnknownCommand that = (UnknownCommand) o;
+
+        return remainingBytes.equals(that.remainingBytes);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + remainingBytes.hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "UnknownCommand";
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsNotificationSample.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsNotificationSample.java
index fd6e00a..4fd7fb1 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsNotificationSample.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsNotificationSample.java
@@ -40,18 +40,12 @@ public class AdsNotificationSample implements ByteReadable {
      */
     private final Data data;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
 
     private AdsNotificationSample(NotificationHandle notificationHandle, Data data) {
         this.notificationHandle = requireNonNull(notificationHandle);
         this.sampleSize = null;
         this.data = requireNonNull(data);
-        calculated = true;
         lengthSupplier = data;
     }
 
@@ -59,7 +53,6 @@ public class AdsNotificationSample implements ByteReadable {
         this.notificationHandle = requireNonNull(notificationHandle);
         this.sampleSize = requireNonNull(sampleSize);
         this.data = requireNonNull(data);
-        calculated = true;
         lengthSupplier = null;
     }
 
@@ -73,7 +66,7 @@ public class AdsNotificationSample implements ByteReadable {
 
     @Override
     public ByteBuf getByteBuf() {
-        return buildByteBuff(notificationHandle, calculated ? SampleSize.of(lengthSupplier.getCalculatedLength()) : sampleSize, data);
+        return buildByteBuff(notificationHandle, getSampleSize(), data);
     }
 
     public NotificationHandle getNotificationHandle() {
@@ -81,26 +74,38 @@ public class AdsNotificationSample implements ByteReadable {
     }
 
     public SampleSize getSampleSize() {
-        return sampleSize;
+        return lengthSupplier == null ? sampleSize : SampleSize.of(lengthSupplier);
     }
 
     public Data getData() {
         return data;
     }
 
-    public LengthSupplier getLengthSupplier() {
-        return lengthSupplier;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AdsNotificationSample)) return false;
+
+        AdsNotificationSample that = (AdsNotificationSample) o;
+
+        if (!notificationHandle.equals(that.notificationHandle)) return false;
+        if (!data.equals(that.data)) return false;
+        return getSampleSize().equals(that.getSampleSize());
     }
 
-    public boolean isCalculated() {
-        return calculated;
+    @Override
+    public int hashCode() {
+        int result = notificationHandle.hashCode();
+        result = 31 * result + data.hashCode();
+        result = 31 * result + getSampleSize().hashCode();
+        return result;
     }
 
     @Override
     public String toString() {
         return "AdsNotificationSample{" +
             "notificationHandle=" + notificationHandle +
-            ", sampleSize=" + (calculated ? SampleSize.of(lengthSupplier.getCalculatedLength()) : sampleSize) +
+            ", sampleSize=" + getSampleSize() +
             ", data=" + data +
             '}';
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsStampHeader.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsStampHeader.java
index 5717575..74d36a1 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsStampHeader.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/AdsStampHeader.java
@@ -79,6 +79,26 @@ public class AdsStampHeader implements ByteReadable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AdsStampHeader)) return false;
+
+        AdsStampHeader that = (AdsStampHeader) o;
+
+        if (!timeStamp.equals(that.timeStamp)) return false;
+        if (!samples.equals(that.samples)) return false;
+        return adsNotificationSamples.equals(that.adsNotificationSamples);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = timeStamp.hashCode();
+        result = 31 * result + samples.hashCode();
+        result = 31 * result + adsNotificationSamples.hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "AdsStampHeader{" +
             "timeStamp=" + timeStamp +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Length.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Length.java
index 949af0d..2a5255d 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Length.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Length.java
@@ -19,6 +19,7 @@
 package org.apache.plc4x.java.ads.api.commands.types;
 
 import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.ads.api.util.LengthSupplier;
 import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
 
 public class Length extends UnsignedIntLEByteValue {
@@ -49,6 +50,10 @@ public class Length extends UnsignedIntLEByteValue {
         return new Length(value);
     }
 
+    public static Length of(LengthSupplier lengthSupplier) {
+        return new Length(lengthSupplier.getCalculatedLength());
+    }
+
     public static Length of(ByteBuf byteBuf) {
         return new Length(byteBuf);
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/SampleSize.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/SampleSize.java
index 80ef27e..6d09f0f 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/SampleSize.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/SampleSize.java
@@ -19,6 +19,7 @@
 package org.apache.plc4x.java.ads.api.commands.types;
 
 import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.ads.api.util.LengthSupplier;
 import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
 
 public class SampleSize extends UnsignedIntLEByteValue {
@@ -49,6 +50,10 @@ public class SampleSize extends UnsignedIntLEByteValue {
         return new SampleSize(value);
     }
 
+    public static SampleSize of(LengthSupplier lengthSupplier) {
+        return new SampleSize(lengthSupplier.getCalculatedLength());
+    }
+
     public static SampleSize of(String size) {
         return new SampleSize(size);
     }
@@ -56,4 +61,5 @@ public class SampleSize extends UnsignedIntLEByteValue {
     public static SampleSize of(ByteBuf byteBuf) {
         return new SampleSize(byteBuf);
     }
+
 }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/TimeStamp.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/TimeStamp.java
index 198b45e..0077abf 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/TimeStamp.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/TimeStamp.java
@@ -65,15 +65,15 @@ public class TimeStamp extends ByteValue {
         int length = valueBytes.length;
         return ByteBuffer.allocate(NUM_BYTES)
             // LE
-            .put(length > 0 ? valueBytes[0] : 0)
-            .put(length > 1 ? valueBytes[1] : 0)
-            .put(length > 2 ? valueBytes[2] : 0)
-            .put(length > 3 ? valueBytes[3] : 0)
-
-            .put(length > 4 ? valueBytes[4] : 0)
-            .put(length > 5 ? valueBytes[5] : 0)
-            .put(length > 6 ? valueBytes[6] : 0)
             .put(length > 7 ? valueBytes[7] : 0)
+            .put(length > 6 ? valueBytes[6] : 0)
+            .put(length > 5 ? valueBytes[5] : 0)
+            .put(length > 4 ? valueBytes[4] : 0)
+
+            .put(length > 3 ? valueBytes[3] : 0)
+            .put(length > 2 ? valueBytes[2] : 0)
+            .put(length > 1 ? valueBytes[1] : 0)
+            .put(length > 0 ? valueBytes[0] : 0)
             .array();
     }
 
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/WriteLength.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/WriteLength.java
index 291c288..c649c52 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/WriteLength.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/WriteLength.java
@@ -19,6 +19,7 @@
 package org.apache.plc4x.java.ads.api.commands.types;
 
 import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.ads.api.util.LengthSupplier;
 import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
 
 public class WriteLength extends UnsignedIntLEByteValue {
@@ -49,6 +50,10 @@ public class WriteLength extends UnsignedIntLEByteValue {
         return new WriteLength(value);
     }
 
+    public static WriteLength of(LengthSupplier lengthSupplier) {
+        return new WriteLength(lengthSupplier.getCalculatedLength());
+    }
+
     public static WriteLength of(String length) {
         return new WriteLength(length);
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSHeader.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSHeader.java
index ac29459..ffe42a8 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSHeader.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSHeader.java
@@ -21,6 +21,7 @@ package org.apache.plc4x.java.ads.api.generic;
 import io.netty.buffer.ByteBuf;
 import org.apache.plc4x.java.ads.api.generic.types.*;
 import org.apache.plc4x.java.ads.api.util.ByteReadable;
+import org.apache.plc4x.java.ads.api.util.LengthSupplier;
 
 import static java.util.Objects.requireNonNull;
 import static org.apache.plc4x.java.ads.api.util.ByteReadableUtils.buildByteBuff;
@@ -68,12 +69,7 @@ public class AMSHeader implements ByteReadable {
      */
     private final Invoke invokeId;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
-    private final DataLengthSupplier lengthSupplier;
-    private final boolean calculated;
-    //
-    ///
+    private final LengthSupplier dataLengthSupplier;
 
     private AMSHeader(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Command commandId, State stateFlags, DataLength dataLength, AMSError code, Invoke invokeId) {
         this.targetAmsNetId = requireNonNull(targetAmsNetId);
@@ -85,11 +81,11 @@ public class AMSHeader implements ByteReadable {
         this.dataLength = requireNonNull(dataLength);
         this.code = requireNonNull(code);
         this.invokeId = requireNonNull(invokeId);
-        lengthSupplier = null;
-        calculated = false;
+        dataLengthSupplier = null;
+
     }
 
-    private AMSHeader(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Command commandId, State stateFlags, DataLengthSupplier dataLengthSupplier, AMSError code, Invoke invokeId) {
+    private AMSHeader(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Command commandId, State stateFlags, LengthSupplier dataLengthSupplier, AMSError code, Invoke invokeId) {
         this.targetAmsNetId = requireNonNull(targetAmsNetId);
         this.targetAmsPort = requireNonNull(targetAmsPort);
         this.sourceAmsNetId = requireNonNull(sourceAmsNetId);
@@ -99,15 +95,14 @@ public class AMSHeader implements ByteReadable {
         this.dataLength = null;
         this.code = requireNonNull(code);
         this.invokeId = requireNonNull(invokeId);
-        lengthSupplier = requireNonNull(dataLengthSupplier);
-        calculated = true;
+        this.dataLengthSupplier = requireNonNull(dataLengthSupplier);
     }
 
     public static AMSHeader of(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Command commandId, State stateFlags, DataLength dataLength, AMSError code, Invoke invokeId) {
         return new AMSHeader(targetAmsNetId, targetAmsPort, sourceAmsNetId, sourceAmsPort, commandId, stateFlags, dataLength, code, invokeId);
     }
 
-    public static AMSHeader of(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Command commandId, State stateFlags, DataLengthSupplier dataLengthSupplier, AMSError code, Invoke invokeId) {
+    public static AMSHeader of(AMSNetId targetAmsNetId, AMSPort targetAmsPort, AMSNetId sourceAmsNetId, AMSPort sourceAmsPort, Command commandId, State stateFlags, LengthSupplier dataLengthSupplier, AMSError code, Invoke invokeId) {
         return new AMSHeader(targetAmsNetId, targetAmsPort, sourceAmsNetId, sourceAmsPort, commandId, stateFlags, dataLengthSupplier, code, invokeId);
     }
 
@@ -120,7 +115,7 @@ public class AMSHeader implements ByteReadable {
             sourceAmsPort,
             commandId,
             stateFlags,
-            calculated ? DataLength.of(lengthSupplier.getCalculatedLength()) : dataLength,
+            getDataLength(),
             code,
             invokeId);
     }
@@ -150,7 +145,7 @@ public class AMSHeader implements ByteReadable {
     }
 
     public DataLength getDataLength() {
-        return calculated ? DataLength.of(lengthSupplier.getCalculatedLength()) : dataLength;
+        return dataLengthSupplier == null ? dataLength : DataLength.of(dataLengthSupplier);
     }
 
     public AMSError getCode() {
@@ -162,6 +157,38 @@ public class AMSHeader implements ByteReadable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AMSHeader)) return false;
+
+        AMSHeader amsHeader = (AMSHeader) o;
+
+        if (!targetAmsNetId.equals(amsHeader.targetAmsNetId)) return false;
+        if (!targetAmsPort.equals(amsHeader.targetAmsPort)) return false;
+        if (!sourceAmsNetId.equals(amsHeader.sourceAmsNetId)) return false;
+        if (!sourceAmsPort.equals(amsHeader.sourceAmsPort)) return false;
+        if (commandId != amsHeader.commandId) return false;
+        if (!stateFlags.equals(amsHeader.stateFlags)) return false;
+        if (!code.equals(amsHeader.code)) return false;
+        if (!invokeId.equals(amsHeader.invokeId)) return false;
+        return getDataLength().equals(((AMSHeader) o).getDataLength());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = targetAmsNetId.hashCode();
+        result = 31 * result + targetAmsPort.hashCode();
+        result = 31 * result + sourceAmsNetId.hashCode();
+        result = 31 * result + sourceAmsPort.hashCode();
+        result = 31 * result + commandId.hashCode();
+        result = 31 * result + stateFlags.hashCode();
+        result = 31 * result + code.hashCode();
+        result = 31 * result + invokeId.hashCode();
+        result = 31 * result + getDataLength().hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "AMSHeader{" +
             "targetAmsNetId=" + targetAmsNetId +
@@ -170,14 +197,9 @@ public class AMSHeader implements ByteReadable {
             ", sourceAmsPort=" + sourceAmsPort +
             ", commandId=" + commandId +
             ", stateFlags=" + stateFlags +
-            ", dataLength=" + (calculated ? DataLength.of(lengthSupplier.getCalculatedLength()) : dataLength) +
+            ", dataLength=" + getDataLength() +
             ", code=" + code +
             ", invokeId=" + invokeId +
             '}';
     }
-
-    @FunctionalInterface
-    public interface DataLengthSupplier {
-        long getCalculatedLength();
-    }
 }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPHeader.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPHeader.java
index 5023d30..fb609f8 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPHeader.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPHeader.java
@@ -36,25 +36,23 @@ public class AMSTCPHeader implements ByteReadable {
 
     private final TcpLength tcpLength;
 
-    ////
-    // Used when fields should be calculated. TODO: check if we better work with a subclass.
     private final LengthSupplier[] lengthSuppliers;
-    private final boolean calculated;
-    //
-    ///
 
     private AMSTCPHeader(TcpLength tcpLength) {
         this.reserved = requireNonNull(Reserved.CONSTANT);
         this.tcpLength = requireNonNull(tcpLength);
         lengthSuppliers = null;
-        calculated = false;
+
     }
 
     private AMSTCPHeader(LengthSupplier... lengthSuppliers) {
         this.reserved = requireNonNull(Reserved.CONSTANT);
         this.tcpLength = null;
         this.lengthSuppliers = requireNonNull(lengthSuppliers);
-        calculated = true;
+    }
+
+    public static AMSTCPHeader of(TcpLength tcpLength) {
+        return new AMSTCPHeader(tcpLength);
     }
 
     public static AMSTCPHeader of(long length) {
@@ -66,20 +64,12 @@ public class AMSTCPHeader implements ByteReadable {
     }
 
     public TcpLength getTcpLength() {
-        return tcpLength;
-    }
-
-    public LengthSupplier[] getLengthSuppliers() {
-        return lengthSuppliers;
-    }
-
-    public boolean isCalculated() {
-        return calculated;
+        return lengthSuppliers == null ? tcpLength : TcpLength.of(getCalculatedLength());
     }
 
     @Override
     public ByteBuf getByteBuf() {
-        return buildByteBuff(reserved, calculated ? TcpLength.of(getCalculatedLength()) : tcpLength);
+        return buildByteBuff(reserved, getTcpLength());
     }
 
     /**
@@ -100,22 +90,39 @@ public class AMSTCPHeader implements ByteReadable {
 
     @Override
     public long getCalculatedLength() {
-        if (calculated) {
+        if (lengthSuppliers == null) {
+            return tcpLength.getAsLong();
+        } else {
             long aggregateLength = 0;
             for (LengthSupplier supplier : lengthSuppliers) {
                 aggregateLength += supplier.getCalculatedLength();
             }
             return aggregateLength;
-        } else {
-            return tcpLength.getAsLong();
         }
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AMSTCPHeader)) return false;
+
+        AMSTCPHeader that = (AMSTCPHeader) o;
+
+        return getTcpLength().equals(that.getTcpLength());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = reserved != null ? reserved.hashCode() : 0;
+        result = 31 * result + (getTcpLength() != null ? getTcpLength().hashCode() : 0);
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "AMSTCPHeader{" +
             "reserved=" + reserved +
-            ", tcpLength=" + (calculated ? TcpLength.of(getCalculatedLength()) : tcpLength) +
+            ", tcpLength=" + getTcpLength() +
             '}';
     }
 }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPPacket.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPPacket.java
index eee17c3..3016cd9 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPPacket.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/AMSTCPPacket.java
@@ -80,6 +80,24 @@ public abstract class AMSTCPPacket implements ByteReadable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AMSTCPPacket)) return false;
+
+        AMSTCPPacket that = (AMSTCPPacket) o;
+
+        if (!amsTcpHeader.equals(that.amsTcpHeader)) return false;
+        return amsHeader.equals(that.amsHeader);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = amsTcpHeader.hashCode();
+        result = 31 * result + amsHeader.hashCode();
+        return result;
+    }
+
+    @Override
     public String toString() {
         return getClass().getSimpleName() + "{" +
             "amsTcpHeader=" + amsTcpHeader +
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/DataLength.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/DataLength.java
index 11a64a9..b903725 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/DataLength.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/DataLength.java
@@ -19,6 +19,7 @@
 package org.apache.plc4x.java.ads.api.generic.types;
 
 import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.ads.api.util.LengthSupplier;
 import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
 
 public class DataLength extends UnsignedIntLEByteValue {
@@ -49,6 +50,10 @@ public class DataLength extends UnsignedIntLEByteValue {
         return new DataLength(value);
     }
 
+    public static DataLength of(LengthSupplier lengthSupplier) {
+        return new DataLength(lengthSupplier.getCalculatedLength());
+    }
+
     public static DataLength of(String length) {
         return new DataLength(length);
     }
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/util/ByteValue.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/util/ByteValue.java
index dd81cb5..0d9af49 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/util/ByteValue.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/util/ByteValue.java
@@ -82,6 +82,6 @@ public class ByteValue implements ByteReadable {
     @Override
     public String toString() {
         // TODO: maybe we could find a way to implement this to string
-        return getClass().getSimpleName() + "{value=" + value.length + "bytes}";
+        return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "{value=" + value.length + "bytes}";
     }
 }
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/netty/ADSProtocolTest.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/netty/ADSProtocolTest.java
index 6f799f5..9d8cb26 100644
--- a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/netty/ADSProtocolTest.java
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/netty/ADSProtocolTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.plc4x.java.ads.netty;
 
+import io.netty.buffer.ByteBuf;
 import org.apache.plc4x.java.ads.api.commands.*;
 import org.apache.plc4x.java.ads.api.commands.types.*;
 import org.apache.plc4x.java.ads.api.generic.AMSTCPPacket;
@@ -38,7 +39,7 @@ import java.util.Date;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
@@ -181,4 +182,20 @@ public class ADSProtocolTest {
         assertThat(out, hasSize(1));
     }
 
+    @Test
+    public void roundTrip() throws Exception {
+        ArrayList<Object> outbound = new ArrayList<>();
+        SUT.encode(null, amstcpPacket, outbound);
+        assertEquals(1, outbound.size());
+        assertThat(outbound, hasSize(1));
+        assertThat(outbound.get(0), instanceOf(ByteBuf.class));
+        ByteBuf byteBuf = (ByteBuf) outbound.get(0);
+        ArrayList<Object> inbound = new ArrayList<>();
+        SUT.decode(null, byteBuf, inbound);
+        assertEquals(1, inbound.size());
+        assertThat(inbound, hasSize(1));
+        assertThat(inbound.get(0), instanceOf(AMSTCPPacket.class));
+        AMSTCPPacket inboundAmstcpPacket = (AMSTCPPacket) inbound.get(0);
+        assertThat("inbound divers from outbound", this.amstcpPacket, equalTo(inboundAmstcpPacket));
+    }
 }
\ No newline at end of file

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