Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions src/Types/DescribedList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -401,22 +401,17 @@ internal override void DecodeValue(ByteBuffer buffer)

internal override void EncodeValue(ByteBuffer buffer)
{
if (this.fields == 0)
if (this.fieldCount == 0)
{
AmqpBitConverter.WriteUByte(buffer, FormatCode.List0);
}
else
{
// Count non-null fields by removing leading zeros
int count = 0;
int temp = this.fields;
while (temp > 0)
{
count++;
temp <<= 1;
}

count = 32 - count;
// Always encode all defined fields so receivers that access fields by fixed
// numeric index (e.g. Python _pyamqp) get the full-length list regardless of
// which fields are set. This matches real broker behaviour and satisfies
// AMQP 1.0 spec section 1.4 (trailing nulls are permitted).
int count = this.fieldCount;
int pos = buffer.WritePos;
AmqpBitConverter.WriteUByte(buffer, 0);
AmqpBitConverter.WriteULong(buffer, 0);
Expand Down
56 changes: 56 additions & 0 deletions test/Common/EncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,61 @@ public void EncoderNestedNestedListTest()
Assert.AreEqual(1, subList.Count);
Assert.AreEqual("test", subList[0]);
}

// Verify that every AMQP 1.0 performative is encoded with its full field count even
// when only mandatory fields are set. Receivers that access fields by fixed numeric
// index (e.g. Python _pyamqp) must see the complete list length.
[TestMethod]
public void PerformativesEncodeFullFieldCount()
{
// (performative, expected field count per AMQP 1.0 spec)
var cases = new (global::Amqp.Types.DescribedList frame, int expectedCount)[]
{
(new global::Amqp.Framing.Open { ContainerId = "test" }, 10),
(new global::Amqp.Framing.Begin { NextOutgoingId = 0, IncomingWindow = 100, OutgoingWindow = 100 }, 8),
(new global::Amqp.Framing.Attach { LinkName = "test", Handle = 0, Role = true }, 14),
(new global::Amqp.Framing.Flow(), 11),
(new global::Amqp.Framing.Transfer { Handle = 0 }, 11),
(new global::Amqp.Framing.Dispose { Role = false }, 6),
(new global::Amqp.Framing.Detach { Handle = 0 }, 3),
(new global::Amqp.Framing.End(), 1),
(new global::Amqp.Framing.Close(), 1),
};

foreach (var (frame, expectedCount) in cases)
{
var buffer = new ByteBuffer(512, true);
frame.Encode(buffer);

// Skip described-type prefix: 0x00 + ulong format code + descriptor value
AmqpBitConverter.ReadUByte(buffer); // 0x00 (described type)
byte ulongFc = AmqpBitConverter.ReadUByte(buffer);
if (ulongFc == 0x53) // smallulong
AmqpBitConverter.ReadUByte(buffer);
else // 0x80 ulong
AmqpBitConverter.ReadULong(buffer);

// Read list header and extract count
byte listFc = AmqpBitConverter.ReadUByte(buffer);
int count;
if (listFc == 0x45) // list0 — empty
{
count = 0;
}
else if (listFc == 0xc0) // list8 — size(1) + count(1)
{
AmqpBitConverter.ReadUByte(buffer); // size
count = AmqpBitConverter.ReadUByte(buffer);
}
else // list32 (0xd0) — size(4) + count(4)
{
AmqpBitConverter.ReadUInt(buffer); // size
count = (int)AmqpBitConverter.ReadUInt(buffer);
}

Assert.AreEqual(expectedCount, count,
$"{frame.GetType().Name} expected {expectedCount} fields but encoded {count}");
}
}
}
}