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

kpvdr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-interop-test.git

commit e4ef29bf0e0918198382e595350f4364ca395924
Author: QIT Development Team <[email protected]>
AuthorDate: Fri Aug 7 16:45:43 2026 -0400

    Add client library bug documentation with standalone reproducers
    
    Document 14 bugs found across ProtonJ2, Proton .NET, and Rhea during
    QIT 2.0 interop testing. Each bug has a minimal standalone repro script
    in docs/bug-repros/ that demonstrates the failure independently of QIT.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 docs/CLIENT_BUGS.md                                | Bin 0 -> 12735 bytes
 docs/bug-repros/README.md                          |  30 +++++++++++
 docs/bug-repros/dotnet-binary-correlationid.cs     |  33 +++++++++++++
 docs/bug-repros/dotnet-binary-in-list.cs           |  42 ++++++++++++++++
 docs/bug-repros/dotnet-empty-binary.cs             |  35 +++++++++++++
 docs/bug-repros/dotnet-list-null-nre.cs            |  34 +++++++++++++
 docs/bug-repros/dotnet-timestamp-type.cs           |  45 +++++++++++++++++
 docs/bug-repros/pom.xml                            |  22 +++++++++
 docs/bug-repros/protonj2-binary-correlationid.java |  33 +++++++++++++
 docs/bug-repros/protonj2-char-null.java            | Bin 0 -> 1365 bytes
 docs/bug-repros/protonj2-list-null-npe.java        |  41 +++++++++++++++
 docs/bug-repros/protonj2-timestamp-type.java       |  51 +++++++++++++++++++
 docs/bug-repros/protonj2-ulong-overflow.java       |  55 +++++++++++++++++++++
 docs/bug-repros/rhea-ulong-precision.js            |  55 +++++++++++++++++++++
 14 files changed, 476 insertions(+)

diff --git a/docs/CLIENT_BUGS.md b/docs/CLIENT_BUGS.md
new file mode 100644
index 0000000..0f3fb17
Binary files /dev/null and b/docs/CLIENT_BUGS.md differ
diff --git a/docs/bug-repros/README.md b/docs/bug-repros/README.md
new file mode 100644
index 0000000..4a6dc99
--- /dev/null
+++ b/docs/bug-repros/README.md
@@ -0,0 +1,30 @@
+# Bug Reproducers
+
+Minimal standalone scripts that demonstrate each client library bug found by 
QIT 2.0. See [../CLIENT_BUGS.md](../CLIENT_BUGS.md) for the full writeup.
+
+All scripts require an Artemis broker on localhost:5672 with user/pass 
artemis/artemis.
+
+## Java (ProtonJ2) repros
+
+```bash
+mvn dependency:copy-dependencies
+javac -cp "target/dependency/*" protonj2-list-null-npe.java
+java -cp ".:target/dependency/*" ProtonJ2ListNullNpe
+```
+
+## .NET repros
+
+Create a project, add the package reference, then copy in the .cs file:
+```bash
+dotnet new console -o repro && cd repro
+dotnet add package Apache.Qpid.Proton.Client --version 1.0.0
+cp ../dotnet-list-null-nre.cs Program.cs
+dotnet run
+```
+
+## JavaScript (Rhea) repros
+
+```bash
+npm install rhea
+node rhea-ulong-precision.js
+```
diff --git a/docs/bug-repros/dotnet-binary-correlationid.cs 
b/docs/bug-repros/dotnet-binary-correlationid.cs
new file mode 100644
index 0000000..2da541e
--- /dev/null
+++ b/docs/bug-repros/dotnet-binary-correlationid.cs
@@ -0,0 +1,33 @@
+/**
+ * Proton .NET Bug: Cannot send binary correlation IDs
+ *
+ * AMQP spec allows correlation-id to be binary, but Proton .NET's encoder
+ * rejects byte[] and IProtonBuffer for the correlation-id field.
+ *
+ * Tested with: Apache.Qpid.Proton.Client 1.0.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Expected: binary correlation ID accepted
+ * Actual:   encoder error
+ */
+
+using Apache.Qpid.Proton.Client;
+
+var client = IClient.Create();
+var options = new ConnectionOptions { User = "artemis", Password = "artemis" };
+
+try
+{
+    using var conn = client.Connect("localhost", 5672, options);
+    using var sender = conn.OpenSender("test.bug.binary-corrid");
+
+    var msg = IMessage<object>.Create();
+    msg.Body = "test";
+    msg.CorrelationId = new byte[] { 0x01, 0x02, 0x03 };
+    sender.Send(msg);
+    Console.WriteLine("PASS: binary correlation ID accepted");
+}
+catch (Exception e)
+{
+    Console.WriteLine($"FAIL: {e.GetType().Name}: {e.Message}");
+}
diff --git a/docs/bug-repros/dotnet-binary-in-list.cs 
b/docs/bug-repros/dotnet-binary-in-list.cs
new file mode 100644
index 0000000..506bedf
--- /dev/null
+++ b/docs/bug-repros/dotnet-binary-in-list.cs
@@ -0,0 +1,42 @@
+/**
+ * Proton .NET Bug: byte[] encoded as array-of-ubyte instead of binary in lists
+ *
+ * When byte[] appears inside a List<object>, Proton .NET encodes it as an
+ * AMQP array<ubyte> instead of AMQP binary. Other clients then receive an
+ * array of integers rather than a binary blob.
+ *
+ * Root cause: byte[] implements IList<byte>, so the encoder picks the array
+ * code path over the binary code path.
+ *
+ * Tested with: Apache.Qpid.Proton.Client 1.0.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Run .NET sender, then receive with Python:
+ *   python3 -c "
+ *   from proton.handlers import MessagingHandler
+ *   from proton.reactor import Container
+ *   class R(MessagingHandler):
+ *       def on_message(self, event):
+ *           body = event.message.body
+ *           elem = body[0] if body else None
+ *           print(f'Type: {type(elem).__name__}, Value: {elem}')
+ *           event.connection.close()
+ *   
Container(R('amqp://artemis:artemis@localhost:5672/test.bug.binary-list')).run()
+ *   "
+ *
+ * Expected: Python receives bytes (b'\x01\x02\x03')
+ * Actual:   Python receives list or array ([1, 2, 3])
+ */
+
+using Apache.Qpid.Proton.Client;
+
+var client = IClient.Create();
+var options = new ConnectionOptions { User = "artemis", Password = "artemis" };
+
+using var conn = client.Connect("localhost", 5672, options);
+using var sender = conn.OpenSender("test.bug.binary-list");
+
+var msg = IMessage<object>.Create();
+msg.Body = new List<object> { new byte[] { 0x01, 0x02, 0x03 } };
+sender.Send(msg);
+Console.WriteLine("Sent list containing byte[] — check receiver for type");
diff --git a/docs/bug-repros/dotnet-empty-binary.cs 
b/docs/bug-repros/dotnet-empty-binary.cs
new file mode 100644
index 0000000..95b71af
--- /dev/null
+++ b/docs/bug-repros/dotnet-empty-binary.cs
@@ -0,0 +1,35 @@
+/**
+ * Proton .NET Bug: Empty binary round-trip loses type
+ *
+ * Sending an empty byte[] (zero-length binary) and receiving it back with
+ * Proton .NET results in a different type (null or empty string).
+ *
+ * Tested with: Apache.Qpid.Proton.Client 1.0.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Expected: received byte[] of length 0
+ * Actual:   null or wrong type
+ */
+
+using Apache.Qpid.Proton.Client;
+
+var client = IClient.Create();
+var options = new ConnectionOptions { User = "artemis", Password = "artemis" };
+
+using var conn = client.Connect("localhost", 5672, options);
+using var sender = conn.OpenSender("test.bug.empty-binary");
+using var receiver = conn.OpenReceiver("test.bug.empty-binary");
+
+var msg = IMessage<object>.Create();
+msg.Body = new byte[0];
+sender.Send(msg);
+
+var delivery = receiver.Receive(TimeSpan.FromSeconds(10));
+var body = delivery.Message().Body;
+
+if (body is byte[] bytes && bytes.Length == 0)
+    Console.WriteLine("PASS: received empty byte[]");
+else if (body == null)
+    Console.WriteLine("FAIL: received null instead of empty byte[]");
+else
+    Console.WriteLine($"FAIL: received {body.GetType().Name}: {body}");
diff --git a/docs/bug-repros/dotnet-list-null-nre.cs 
b/docs/bug-repros/dotnet-list-null-nre.cs
new file mode 100644
index 0000000..ea86bc5
--- /dev/null
+++ b/docs/bug-repros/dotnet-list-null-nre.cs
@@ -0,0 +1,34 @@
+/**
+ * Proton .NET Bug: ListTypeEncoder NullReferenceException on 
null-after-non-null
+ *
+ * Same root cause as the ProtonJ2 NPE: encoding a list with null after
+ * non-null crashes the encoder.
+ *
+ * Tested with: Apache.Qpid.Proton.Client 1.0.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Run: dotnet run
+ *
+ * Expected: message sent successfully
+ * Actual:   NullReferenceException in ListTypeEncoder
+ */
+
+using Apache.Qpid.Proton.Client;
+
+var client = IClient.Create();
+var options = new ConnectionOptions { User = "artemis", Password = "artemis" };
+
+try
+{
+    using var conn = client.Connect("localhost", 5672, options);
+    using var sender = conn.OpenSender("test.bug.list-null-nre");
+
+    var msg = IMessage<object>.Create();
+    msg.Body = new List<object> { "hello", null };
+    sender.Send(msg);
+    Console.WriteLine("PASS: message sent");
+}
+catch (Exception e)
+{
+    Console.WriteLine($"FAIL: {e.GetType().Name}: {e.Message}");
+}
diff --git a/docs/bug-repros/dotnet-timestamp-type.cs 
b/docs/bug-repros/dotnet-timestamp-type.cs
new file mode 100644
index 0000000..b29aade
--- /dev/null
+++ b/docs/bug-repros/dotnet-timestamp-type.cs
@@ -0,0 +1,45 @@
+/**
+ * Proton .NET Bug: AMQP timestamp decoded as Int64 instead of DateTime
+ *
+ * When receiving a message with an AMQP timestamp body, Proton .NET returns
+ * System.Int64 (raw milliseconds) instead of System.DateTime, losing the
+ * type information.
+ *
+ * Tested with: Apache.Qpid.Proton.Client 1.0.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Send a timestamp from Python first:
+ *
+ *   python3 -c "
+ *   from proton import Message, timestamp
+ *   from proton.handlers import MessagingHandler
+ *   from proton.reactor import Container
+ *   class S(MessagingHandler):
+ *       def on_sendable(self, event):
+ *           event.sender.send(Message(body=timestamp(1234567890000)))
+ *           event.sender.close()
+ *           event.connection.close()
+ *   
Container(S('amqp://artemis:artemis@localhost:5672/test.bug.ts-type')).run()
+ *   "
+ *
+ * Expected: DateTime or DateTimeOffset
+ * Actual:   Int64
+ */
+
+using Apache.Qpid.Proton.Client;
+
+var client = IClient.Create();
+var options = new ConnectionOptions { User = "artemis", Password = "artemis" };
+
+using var conn = client.Connect("localhost", 5672, options);
+using var receiver = conn.OpenReceiver("test.bug.ts-type");
+
+var delivery = receiver.Receive(TimeSpan.FromSeconds(10));
+var body = delivery.Message().Body;
+Console.WriteLine($"Type: {body.GetType().Name}");
+Console.WriteLine($"Value: {body}");
+
+if (body is DateTime || body is DateTimeOffset)
+    Console.WriteLine("PASS: received as temporal type");
+else
+    Console.WriteLine($"FAIL: expected DateTime, got {body.GetType().Name}");
diff --git a/docs/bug-repros/pom.xml b/docs/bug-repros/pom.xml
new file mode 100644
index 0000000..8f5d4a0
--- /dev/null
+++ b/docs/bug-repros/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.qpid.qit</groupId>
+    <artifactId>bug-repros</artifactId>
+    <version>1.0.0</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>protonj2-client</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+    </dependencies>
+
+    <!--
+      Usage:
+        mvn dependency:copy-dependencies
+        javac -cp target/dependency/* BugRepro.java
+        java -cp .:target/dependency/* BugRepro
+    -->
+</project>
diff --git a/docs/bug-repros/protonj2-binary-correlationid.java 
b/docs/bug-repros/protonj2-binary-correlationid.java
new file mode 100644
index 0000000..b139033
--- /dev/null
+++ b/docs/bug-repros/protonj2-binary-correlationid.java
@@ -0,0 +1,33 @@
+/**
+ * ProtonJ2 Bug: Cannot send binary correlation IDs
+ *
+ * AMQP spec allows correlation-id to be: message-id-ulong, message-id-uuid,
+ * message-id-binary, or message-id-string. ProtonJ2 rejects byte[] as a
+ * correlation ID value.
+ *
+ * Also: when receiving a binary correlation ID (sent by another client),
+ * ProtonJ2 decodes it as a UTF-8 string instead of preserving the binary.
+ *
+ * Tested with: protonj2-client 1.1.0
+ */
+
+import org.apache.qpid.protonj2.client.*;
+
+public class ProtonJ2BinaryCorrelationId {
+    public static void main(String[] args) throws Exception {
+        try (Client client = Client.create();
+             Connection conn = client.connect("localhost", 5672,
+                 new ConnectionOptions().user("artemis").password("artemis"));
+             Sender sender = conn.openSender("test.bug.binary-corrid")) {
+
+            Message<String> msg = Message.create("test");
+            try {
+                msg.correlationId(new byte[] {0x01, 0x02, 0x03});
+                sender.send(msg);
+                System.out.println("PASS: binary correlation ID accepted");
+            } catch (Exception e) {
+                System.out.println("FAIL: " + e.getClass().getSimpleName() + 
": " + e.getMessage());
+            }
+        }
+    }
+}
diff --git a/docs/bug-repros/protonj2-char-null.java 
b/docs/bug-repros/protonj2-char-null.java
new file mode 100644
index 0000000..548f9fa
Binary files /dev/null and b/docs/bug-repros/protonj2-char-null.java differ
diff --git a/docs/bug-repros/protonj2-list-null-npe.java 
b/docs/bug-repros/protonj2-list-null-npe.java
new file mode 100644
index 0000000..5424d4a
--- /dev/null
+++ b/docs/bug-repros/protonj2-list-null-npe.java
@@ -0,0 +1,41 @@
+/**
+ * ProtonJ2 Bug: ListTypeEncoder NullPointerException on null-after-non-null
+ *
+ * AMQP lists MAY contain null elements, but ProtonJ2 crashes when encoding
+ * a list with a null element following a non-null element.
+ *
+ * Tested with: protonj2-client 1.1.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Build & run:
+ *   mvn dependency:copy-dependencies  # or use the pom.xml below
+ *   javac -cp target/dependency/* protonj2-list-null-npe.java
+ *   java -cp .:target/dependency/* ProtonJ2ListNullNpe
+ *
+ * Expected: message sent successfully
+ * Actual:   NullPointerException in ListTypeEncoder
+ */
+
+import org.apache.qpid.protonj2.client.*;
+import java.util.*;
+
+public class ProtonJ2ListNullNpe {
+    public static void main(String[] args) throws Exception {
+        try (Client client = Client.create();
+             Connection conn = client.connect("localhost", 5672,
+                 new ConnectionOptions().user("artemis").password("artemis"));
+             Sender sender = conn.openSender("test.bug.list-null-npe")) {
+
+            List<Object> body = new ArrayList<>();
+            body.add("hello");
+            body.add(null);
+
+            Message<List<Object>> msg = Message.create(body);
+            sender.send(msg);
+            System.out.println("PASS: message sent");
+        } catch (Exception e) {
+            System.out.println("FAIL: " + e.getClass().getSimpleName() + ": " 
+ e.getMessage());
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/docs/bug-repros/protonj2-timestamp-type.java 
b/docs/bug-repros/protonj2-timestamp-type.java
new file mode 100644
index 0000000..a2e8df0
--- /dev/null
+++ b/docs/bug-repros/protonj2-timestamp-type.java
@@ -0,0 +1,51 @@
+/**
+ * ProtonJ2 Bug: AMQP timestamp decoded as java.lang.Long instead of Date
+ *
+ * When receiving a message with an AMQP timestamp body, ProtonJ2 returns
+ * a Long (raw milliseconds) instead of java.util.Date, losing the type
+ * information. The receiver cannot distinguish a timestamp from a long.
+ *
+ * Tested with: protonj2-client 1.1.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Send a timestamp from Python first:
+ *
+ *   python3 -c "
+ *   from proton import Message, timestamp
+ *   from proton.handlers import MessagingHandler
+ *   from proton.reactor import Container
+ *   class S(MessagingHandler):
+ *       def on_sendable(self, event):
+ *           event.sender.send(Message(body=timestamp(1234567890000)))
+ *           event.sender.close()
+ *           event.connection.close()
+ *   
Container(S('amqp://artemis:artemis@localhost:5672/test.bug.timestamp')).run()
+ *   "
+ *
+ * Expected: java.util.Date
+ * Actual:   java.lang.Long
+ */
+
+import org.apache.qpid.protonj2.client.*;
+
+public class ProtonJ2TimestampType {
+    public static void main(String[] args) throws Exception {
+        try (Client client = Client.create();
+             Connection conn = client.connect("localhost", 5672,
+                 new ConnectionOptions().user("artemis").password("artemis"));
+             Receiver receiver = conn.openReceiver("test.bug.timestamp")) {
+
+            Delivery delivery = receiver.receive(10_000);
+            Object body = delivery.message().body();
+            String typeName = body.getClass().getName();
+            System.out.println("Type: " + typeName);
+            System.out.println("Value: " + body);
+
+            if (body instanceof java.util.Date) {
+                System.out.println("PASS: received as Date");
+            } else {
+                System.out.println("FAIL: expected java.util.Date, got " + 
typeName);
+            }
+        }
+    }
+}
diff --git a/docs/bug-repros/protonj2-ulong-overflow.java 
b/docs/bug-repros/protonj2-ulong-overflow.java
new file mode 100644
index 0000000..2961c9f
--- /dev/null
+++ b/docs/bug-repros/protonj2-ulong-overflow.java
@@ -0,0 +1,55 @@
+/**
+ * ProtonJ2 Bug: ulong values > 2^63 decoded as negative signed long
+ *
+ * AMQP ulong is unsigned 64-bit (0 to 2^64-1). ProtonJ2 maps it to Java
+ * long (signed), so values > Long.MAX_VALUE wrap to negative.
+ *
+ * Tested with: protonj2-client 1.1.0
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * This test sends from Python (which handles ulong correctly) and receives
+ * with ProtonJ2. Run the Python sender first:
+ *
+ *   python3 -c "
+ *   from proton import Message
+ *   from proton.handlers import MessagingHandler
+ *   from proton.reactor import Container
+ *   class S(MessagingHandler):
+ *       def on_sendable(self, event):
+ *           from proton import ulong
+ *           event.sender.send(Message(body=ulong(18446744073709551615)))
+ *           event.sender.close()
+ *           event.connection.close()
+ *   Container(S('amqp://artemis:artemis@localhost:5672/test.bug.ulong')).run()
+ *   "
+ *
+ * Then run this Java receiver.
+ *
+ * Expected: 18446744073709551615
+ * Actual:   -1
+ */
+
+import org.apache.qpid.protonj2.client.*;
+
+public class ProtonJ2UlongOverflow {
+    public static void main(String[] args) throws Exception {
+        try (Client client = Client.create();
+             Connection conn = client.connect("localhost", 5672,
+                 new ConnectionOptions().user("artemis").password("artemis"));
+             Receiver receiver = conn.openReceiver("test.bug.ulong")) {
+
+            Delivery delivery = receiver.receive(10_000);
+            Object body = delivery.message().body();
+            System.out.println("Type: " + body.getClass().getName());
+            System.out.println("Value: " + body);
+
+            if (body instanceof Long && (Long)body < 0) {
+                long v = (Long)body;
+                System.out.printf("FAIL: got signed %d, should be unsigned 
%s%n",
+                    v, Long.toUnsignedString(v));
+            } else {
+                System.out.println("PASS");
+            }
+        }
+    }
+}
diff --git a/docs/bug-repros/rhea-ulong-precision.js 
b/docs/bug-repros/rhea-ulong-precision.js
new file mode 100644
index 0000000..0444d8d
--- /dev/null
+++ b/docs/bug-repros/rhea-ulong-precision.js
@@ -0,0 +1,55 @@
+/**
+ * Rhea Bug: 64-bit integer precision loss / RangeError
+ *
+ * JavaScript Number is IEEE 754 double (safe integer range: -(2^53-1) to 
2^53-1).
+ * Rhea uses Number for ulong and long, so values outside this range are 
silently
+ * rounded or throw RangeError.
+ *
+ * The library should use BigInt for 64-bit integer types.
+ *
+ * Tested with: rhea 3.0.3
+ * Requires: Artemis broker on localhost:5672 (user: artemis, pass: artemis)
+ *
+ * Run: node rhea-ulong-precision.js
+ */
+
+const rhea = require('rhea');
+const container = rhea.create_container();
+
+const ULONG_MAX = BigInt('18446744073709551615');
+const SAFE_MAX = BigInt(Number.MAX_SAFE_INTEGER);
+
+console.log(`Number.MAX_SAFE_INTEGER: ${SAFE_MAX}`);
+console.log(`ULONG_MAX:              ${ULONG_MAX}`);
+console.log(`Can Number hold ULONG_MAX? ${Number(ULONG_MAX) === 
Number(ULONG_MAX - 1n) ? 'NO (precision loss)' : 'yes'}`);
+
+// Demonstrate precision loss with a value just above MAX_SAFE_INTEGER
+const testValue = Number(SAFE_MAX) + 10;
+const testValue2 = Number(SAFE_MAX) + 11;
+console.log(`\n${SAFE_MAX + 10n} === ${SAFE_MAX + 11n}?`);
+console.log(`In JS Number: ${testValue} === ${testValue2}? ${testValue === 
testValue2 ? 'YES — precision lost!' : 'no'}`);
+
+// Demonstrate with actual AMQP send
+container.on('sendable', function(context) {
+    try {
+        // This value exceeds safe integer range
+        const val = 18446744073709551615;
+        console.log(`\nAttempting to send ulong: 18446744073709551615`);
+        console.log(`JS Number representation: ${val}`);
+        console.log(`Already corrupted before send: ${val !== 
18446744073709551615 ? 'YES' : 'no'}`);
+
+        context.sender.send({ body: rhea.types.wrap_ulong(val) });
+        console.log('Sent (but value was already corrupted by JS Number)');
+    } catch(e) {
+        console.log(`RangeError on send: ${e.message}`);
+    }
+    context.sender.close();
+    context.connection.close();
+});
+
+container.connect({
+    host: 'localhost',
+    port: 5672,
+    username: 'artemis',
+    password: 'artemis'
+}).open_sender('test.bug.rhea-precision');


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to