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]
