You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ro...@apache.org on 2026/07/31 16:35:07 UTC
(qpid-proton-j) branch main updated: various codec and engine improvements
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton-j.git
The following commit(s) were added to refs/heads/main by this push:
new 92047419 various codec and engine improvements
92047419 is described below
commit 92047419cc87bab6220e7adeb354958d8109618e
Author: Robbie Gemmell <ro...@apache.org>
AuthorDate: Fri Jul 31 16:09:07 2026 +0100
various codec and engine improvements
---
.../java/org/apache/qpid/proton/amqp/Symbol.java | 72 +-
.../qpid/proton/codec/AbstractSymbolDecoder.java | 63 +
.../org/apache/qpid/proton/codec/ArrayType.java | 350 ++-
.../org/apache/qpid/proton/codec/BinaryType.java | 41 +-
.../java/org/apache/qpid/proton/codec/Data.java | 3 +
.../java/org/apache/qpid/proton/codec/Decoder.java | 29 +
.../org/apache/qpid/proton/codec/DecoderImpl.java | 322 ++-
.../qpid/proton/codec/DynamicTypeConstructor.java | 11 +
.../org/apache/qpid/proton/codec/EncoderImpl.java | 9 +-
.../org/apache/qpid/proton/codec/ListType.java | 260 +-
.../java/org/apache/qpid/proton/codec/MapType.java | 209 +-
.../org/apache/qpid/proton/codec/StringType.java | 14 +-
.../org/apache/qpid/proton/codec/SymbolType.java | 77 +-
.../qpid/proton/codec/ValidationSupport.java | 110 +
.../apache/qpid/proton/codec/impl/DataDecoder.java | 756 ++++--
.../apache/qpid/proton/codec/impl/DataImpl.java | 49 +
.../proton/codec/impl/DataValidationSupport.java | 128 +
.../codec/messaging/FastPathAcceptedType.java | 24 +-
.../codec/messaging/FastPathAmqpSequenceType.java | 11 +-
.../codec/messaging/FastPathAmqpValueType.java | 29 +-
.../FastPathApplicationPropertiesType.java | 127 +-
.../proton/codec/messaging/FastPathDataType.java | 11 +-
.../messaging/FastPathDeliveryAnnotationsType.java | 134 +-
.../proton/codec/messaging/FastPathFooterType.java | 11 +-
.../proton/codec/messaging/FastPathHeaderType.java | 109 +-
.../messaging/FastPathMessageAnnotationsType.java | 134 +-
.../codec/messaging/FastPathPropertiesType.java | 128 +-
.../codec/transport/FastPathDispositionType.java | 92 +-
.../proton/codec/transport/FastPathFlowType.java | 92 +-
.../codec/transport/FastPathTransferType.java | 92 +-
.../org/apache/qpid/proton/engine/Transport.java | 10 +
.../proton/engine/TransportConditionException.java | 47 +
.../qpid/proton/engine/impl/FrameParser.java | 37 +-
.../qpid/proton/engine/impl/FrameWriter.java | 4 +
.../apache/qpid/proton/engine/impl/SaslImpl.java | 9 +-
.../qpid/proton/engine/impl/TransportImpl.java | 113 +-
.../proton/engine/impl/TransportOutputAdaptor.java | 5 +-
.../qpid/proton/engine/impl/TransportSession.java | 145 +-
.../org/apache/qpid/proton/message/Message.java | 16 +
.../qpid/proton/message/MessageDecodeOptions.java | 85 +
.../qpid/proton/message/impl/MessageImpl.java | 34 +-
.../org/apache/qpid/proton/amqp/SymbolTest.java | 395 +++
.../qpid/proton/codec/AmqpValueTypeCodecTest.java | 145 +-
.../codec/ApplicationPropertiesTypeTest.java | 199 ++
.../qpid/proton/codec/ArrayTypeCodecTest.java | 2526 +++++++++++++++++-
.../qpid/proton/codec/BinaryTypeCodecTest.java | 77 +
.../apache/qpid/proton/codec/CodecTestSupport.java | 8 +-
.../apache/qpid/proton/codec/DecoderImplTest.java | 221 ++
.../proton/codec/DeliveryAnnotationsCodecTest.java | 295 +++
.../qpid/proton/codec/DispositionCodecTest.java | 245 ++
.../apache/qpid/proton/codec/FlowCodecTest.java | 263 ++
.../apache/qpid/proton/codec/FooterCodecTest.java | 245 ++
.../qpid/proton/codec/HeaderTypeCodecTest.java | 144 ++
.../qpid/proton/codec/ListTypeCodecTest.java | 333 +++
.../apache/qpid/proton/codec/MapTypeCodecTest.java | 374 +++
.../codec/MessageAnnotationsTypeCodecTest.java | 195 ++
.../qpid/proton/codec/PropertiesCodecTest.java | 196 ++
.../apache/qpid/proton/codec/StringTypeTest.java | 108 +-
.../apache/qpid/proton/codec/SymbolTypeTest.java | 318 +++
.../apache/qpid/proton/codec/TransferTypeTest.java | 166 ++
.../codec/UnknownDescribedTypeCodecTest.java | 178 ++
.../qpid/proton/codec/impl/DataImplTest.java | 2726 +++++++++++++++++++-
.../apache/qpid/proton/engine/impl/AmqpFramer.java | 46 +
.../qpid/proton/engine/impl/MockTransportImpl.java | 46 +
.../proton/engine/impl/SaslFrameParserTest.java | 6 +-
.../engine/impl/TransportDeliveryTestAccessor.java | 29 +
.../qpid/proton/engine/impl/TransportImplTest.java | 256 +-
.../engine/impl/TransportLinkTestAccessor.java | 30 +
.../engine/impl/TransportSessionTestAccessor.java | 34 +
.../qpid/proton/message/impl/MessageImplTest.java | 194 ++
.../systemtests/DefaultDeliveryStateTest.java | 61 -
.../qpid/proton/systemtests/DeliveryTest.java | 85 -
.../qpid/proton/systemtests/EngineTestBase.java | 154 +-
.../apache/qpid/proton/systemtests/LinkTest.java | 645 ++++-
.../apache/qpid/proton/systemtests/SaslTest.java | 513 ++++
.../qpid/proton/systemtests/SessionTest.java | 419 +++
tests/interop/described.amqp | Bin 33 -> 33 bytes
tests/java/org/apache/qpid/proton/InteropTest.java | 3 +-
tests/python/interop-generate | 2 +-
tests/python/proton_tests/interop.py | 2 +-
tests/python/proton_tests/sasl.py | 2 +-
81 files changed, 14511 insertions(+), 1375 deletions(-)
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java b/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java
index 4e3d4761..a3a09951 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java
@@ -29,10 +29,17 @@ import org.apache.qpid.proton.codec.WritableBuffer;
public final class Symbol implements Comparable<Symbol>, CharSequence
{
+ private static final int MAX_CACHE_SIZE = 4096;
+ private static final int MAX_CACHED_SYMBOL_LENGTH = 64;
+
+ private static final int MAX_SASL_CACHE_SIZE = 64;
+ private static final int MAX_CACHED_SASL_SYMBOL_LENGTH = 32;
+
private final String _underlying;
private final byte[] _underlyingBytes;
private static final ConcurrentHashMap<String, Symbol> _symbols = new ConcurrentHashMap<String, Symbol>(2048);
+ private static final ConcurrentHashMap<String, Symbol> _saslSymbols = new ConcurrentHashMap<String, Symbol>(32);
private Symbol(String underlying)
{
@@ -72,6 +79,23 @@ public final class Symbol implements Comparable<Symbol>, CharSequence
return _underlying.hashCode();
}
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null) {
+ return false;
+ }
+
+ if (other instanceof Symbol) {
+ return _underlying.equals(((Symbol) other)._underlying);
+ }
+
+ return false;
+ }
+
public static Symbol valueOf(String symbolVal)
{
return getSymbol(symbolVal);
@@ -86,12 +110,14 @@ public final class Symbol implements Comparable<Symbol>, CharSequence
Symbol symbol = _symbols.get(symbolVal);
if(symbol == null)
{
- symbolVal = symbolVal.intern();
symbol = new Symbol(symbolVal);
- Symbol existing;
- if((existing = _symbols.putIfAbsent(symbolVal, symbol)) != null)
+ if(symbol.length() <= MAX_CACHED_SYMBOL_LENGTH && _symbols.size() < MAX_CACHE_SIZE)
{
- symbol = existing;
+ Symbol existing;
+ if((existing = _symbols.putIfAbsent(symbolVal, symbol)) != null)
+ {
+ symbol = existing;
+ }
}
}
return symbol;
@@ -106,4 +132,42 @@ public final class Symbol implements Comparable<Symbol>, CharSequence
{
buffer.put(_underlyingBytes, 0, _underlyingBytes.length);
}
+
+ public static Symbol getSaslSymbol(String symbolVal)
+ {
+ if(symbolVal == null)
+ {
+ return null;
+ }
+
+ // Lookup main cache first, then SASL-specific, to reuse where possible
+ Symbol symbol = _symbols.get(symbolVal);
+ if(symbol == null)
+ {
+ symbol = _saslSymbols.get(symbolVal);
+ }
+
+ // If no match, populate into the SASL-specific cache
+ if(symbol == null)
+ {
+ symbol = new Symbol(symbolVal);
+ if(symbol.length() <= MAX_CACHED_SASL_SYMBOL_LENGTH)
+ {
+ synchronized (_saslSymbols)
+ {
+ final Symbol existing = _saslSymbols.get(symbolVal);
+ if (existing != null)
+ {
+ return existing;
+ }
+
+ if (_saslSymbols.size() < MAX_SASL_CACHE_SIZE)
+ {
+ _saslSymbols.put(symbolVal, symbol);
+ }
+ }
+ }
+ }
+ return symbol;
+ }
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/AbstractSymbolDecoder.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/AbstractSymbolDecoder.java
new file mode 100644
index 00000000..853e986d
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/AbstractSymbolDecoder.java
@@ -0,0 +1,63 @@
+package org.apache.qpid.proton.codec;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.codec.ReadableBuffer.ByteBufferReader;
+
+abstract class AbstractSymbolDecoder implements DecoderImpl.TypeDecoder<Symbol>
+{
+ private final int _maxCacheSize;
+ private final int _maxCachedSymbolSize;
+ private final ConcurrentMap<ReadableBuffer, Symbol> _symbolCache;
+
+ public AbstractSymbolDecoder(final int maxCacheSize, final int maxCachedSymbolSize, final ConcurrentMap<ReadableBuffer, Symbol> symbolCache)
+ {
+ _maxCacheSize = maxCacheSize;
+ _maxCachedSymbolSize = maxCachedSymbolSize;
+ _symbolCache = symbolCache;
+ }
+
+ @Override
+ public Symbol decode(DecoderImpl decoder, ReadableBuffer buffer)
+ {
+ final int remaining = buffer.remaining();
+ final boolean canCache = remaining <= _maxCachedSymbolSize;
+
+ Symbol symbol = canCache ? _symbolCache.get(buffer) : null;
+ if (symbol != null)
+ {
+ buffer.position(buffer.limit());
+ return symbol;
+ }
+
+ final byte[] bytes = new byte[remaining];
+ buffer.get(bytes);
+
+ final String symString = new String(bytes, StandardCharsets.US_ASCII);
+ symbol = createSymbol(symString);
+
+ if (canCache)
+ {
+ final ByteBufferReader keyBuffer = ReadableBuffer.ByteBufferReader.wrap(bytes);
+ synchronized (_symbolCache)
+ {
+ final Symbol existing = _symbolCache.get(keyBuffer);
+ if (existing != null)
+ {
+ return existing;
+ }
+
+ if (_symbolCache.size() < _maxCacheSize)
+ {
+ _symbolCache.put(keyBuffer, symbol);
+ }
+ }
+ }
+
+ return symbol;
+ }
+
+ public abstract Symbol createSymbol(String string);
+}
\ No newline at end of file
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java
index 31044867..6ec34bee 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java
@@ -111,6 +111,10 @@ public class ArrayType implements PrimitiveType<Object[]>
}
else
{
+ if (val[0] == null) {
+ throw new IllegalArgumentException("Cannot encode array with null first element");
+ }
+
AMQPType underlyingType = encoder.getTypeFromClass(val.getClass().getComponentType());
boolean checkTypes = false;
@@ -161,8 +165,26 @@ public class ArrayType implements PrimitiveType<Object[]>
checkTypes = true;
underlyingType = encoder.getType(val[0]);
}
- TypeEncoding underlyingEncoding = underlyingType.getEncoding(val[0]);
+
TypeEncoding canonicalEncoding = underlyingType.getCanonicalEncoding();
+ if (checkTypes && canonicalEncoding != null)
+ {
+ for (int i = 0; i < val.length; i++)
+ {
+ if (encoder.getType(val[i]) != underlyingType)
+ {
+ throw new IllegalArgumentException("Non matching types " + underlyingType + " and "
+ + encoder.getType(val[i]) + " in array");
+ }
+ }
+ }
+
+ if (canonicalEncoding != null)
+ {
+ return canonicalEncoding;
+ }
+
+ TypeEncoding underlyingEncoding = underlyingType.getEncoding(val[0]);
for(int i = 0; i < val.length && (checkTypes || underlyingEncoding != canonicalEncoding); i++)
{
@@ -281,6 +303,27 @@ public class ArrayType implements PrimitiveType<Object[]>
return size;
}
+ private static void validateSizeInitial(int size, DecoderImpl decoder)
+ {
+ if (size < 0)
+ {
+ throw new DecodeException("Array size " + size + " cant be negative");
+ }
+
+ if (size > decoder.getByteBufferRemaining())
+ {
+ throw new DecodeException("Array size " + size + " cant be more than remaining bytes " + decoder.getByteBufferRemaining());
+ }
+ }
+
+ private static void validateCountInitial(int count)
+ {
+ if (count < 0)
+ {
+ throw new DecodeException("Array element count " + count + " cant be negative");
+ }
+ }
+
@Override
public ArrayEncoding getCanonicalEncoding()
{
@@ -310,20 +353,7 @@ public class ArrayType implements PrimitiveType<Object[]>
private ArrayEncoding getEncoding(final boolean[] a)
{
- return a.length < 254 || a.length <= 255 && allSameValue(a) ? _shortArrayEncoding : _arrayEncoding;
- }
-
- private boolean allSameValue(final boolean[] a)
- {
- boolean val = a[0];
- for(int i = 1; i < a.length; i++)
- {
- if(val != a[i])
- {
- return false;
- }
- }
- return true;
+ return a.length < 254 ? _shortArrayEncoding : _arrayEncoding;
}
public void write(byte[] a)
@@ -359,19 +389,7 @@ public class ArrayType implements PrimitiveType<Object[]>
private ArrayEncoding getEncoding(final int[] a)
{
- return a.length < 63 || (a.length < 254 && allSmallInts(a)) ? _shortArrayEncoding : _arrayEncoding;
- }
-
- private boolean allSmallInts(final int[] a)
- {
- for(int i = 0; i < a.length; i++)
- {
- if(a[i] < -128 || a[i] > 127)
- {
- return false;
- }
- }
- return true;
+ return a.length < 64 ? _shortArrayEncoding : _arrayEncoding;
}
public void write(long[] a)
@@ -383,19 +401,7 @@ public class ArrayType implements PrimitiveType<Object[]>
private ArrayEncoding getEncoding(final long[] a)
{
- return a.length < 31 || (a.length < 254 && allSmallLongs(a)) ? _shortArrayEncoding : _arrayEncoding;
- }
-
- private boolean allSmallLongs(final long[] a)
- {
- for(int i = 0; i < a.length; i++)
- {
- if(a[i] < -128L || a[i] > 127L)
- {
- return false;
- }
- }
- return true;
+ return a.length < 32 ? _shortArrayEncoding : _arrayEncoding;
}
public void write(float[] a)
@@ -407,7 +413,7 @@ public class ArrayType implements PrimitiveType<Object[]>
private ArrayEncoding getEncoding(final float[] a)
{
- return a.length < 63 ? _shortArrayEncoding : _arrayEncoding;
+ return a.length < 64 ? _shortArrayEncoding : _arrayEncoding;
}
public void write(double[] a)
@@ -419,7 +425,7 @@ public class ArrayType implements PrimitiveType<Object[]>
private ArrayEncoding getEncoding(final double[] a)
{
- return a.length < 31 ? _shortArrayEncoding : _arrayEncoding;
+ return a.length < 32 ? _shortArrayEncoding : _arrayEncoding;
}
public void write(char[] a)
@@ -431,7 +437,7 @@ public class ArrayType implements PrimitiveType<Object[]>
private ArrayEncoding getEncoding(final char[] a)
{
- return a.length < 63 ? _shortArrayEncoding : _arrayEncoding;
+ return a.length < 64 ? _shortArrayEncoding : _arrayEncoding;
}
private class AllArrayEncoding
@@ -648,18 +654,82 @@ public class ArrayType implements PrimitiveType<Object[]>
public Object[] readValue()
{
DecoderImpl decoder = getDecoder();
- int size = decoder.readRawInt();
- int count = decoder.readRawInt();
- return decodeArray(decoder, count);
+ ReadableBuffer buffer = decoder.getBuffer();
+
+ decoder.increaseDecodeDepth();
+ try
+ {
+ int size = decoder.readRawInt();
+ validateSizeInitial(size, decoder);
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
+
+ try
+ {
+ buffer.limit(newLimit);
+
+ int count = decoder.readRawInt();
+ validateCountInitial(count);
+
+ final Object[] decodedArray = decodeArray(decoder, count, size);
+
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return decodedArray;
+ }
+ finally
+ {
+ buffer.limit(oldLimit);
+ }
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
public Object readValueArray()
{
DecoderImpl decoder = getDecoder();
- int size = decoder.readRawInt();
- int count = decoder.readRawInt();
- return decodeArrayAsObject(decoder, count);
+ ReadableBuffer buffer = decoder.getBuffer();
+
+ decoder.increaseDecodeDepth();
+ try
+ {
+ int size = decoder.readRawInt();
+ validateSizeInitial(size, decoder);
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
+
+ try
+ {
+ buffer.limit(newLimit);
+
+ int count = decoder.readRawInt();
+ validateCountInitial(count);
+
+ final Object decodedArray = decodeArrayAsObject(decoder, count, size);
+
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return decodedArray;
+ }
+ finally
+ {
+ buffer.limit(oldLimit);
+ }
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
@@ -668,6 +738,7 @@ public class ArrayType implements PrimitiveType<Object[]>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = decoder.readRawInt();
+ validateSizeInitial(size, decoder);
buffer.position(buffer.position() + size);
}
}
@@ -941,18 +1012,80 @@ public class ArrayType implements PrimitiveType<Object[]>
public Object[] readValue()
{
DecoderImpl decoder = getDecoder();
- int size = ((int)decoder.readRawByte()) & 0xFF;
- int count = ((int)decoder.readRawByte()) & 0xFF;
- return decodeArray(decoder, count);
+ ReadableBuffer buffer = decoder.getBuffer();
+
+ decoder.increaseDecodeDepth();
+ try
+ {
+ int size = ((int)decoder.readRawByte()) & 0xFF;
+ validateSizeInitial(size, decoder);
+
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
+
+ try
+ {
+ buffer.limit(newLimit);
+ int count = ((int)decoder.readRawByte()) & 0xFF;
+ validateCountInitial(count);
+
+ Object[] decodedArray = decodeArray(decoder, count, size);
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return decodedArray;
+ }
+ finally
+ {
+ buffer.limit(oldLimit);
+ }
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
public Object readValueArray()
{
DecoderImpl decoder = getDecoder();
- int size = ((int)decoder.readRawByte()) & 0xFF;
- int count = ((int)decoder.readRawByte()) & 0xFF;
- return decodeArrayAsObject(decoder, count);
+ ReadableBuffer buffer = decoder.getBuffer();
+
+ decoder.increaseDecodeDepth();
+ try
+ {
+ int size = ((int)decoder.readRawByte()) & 0xFF;
+ validateSizeInitial(size, decoder);
+
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
+
+ try
+ {
+ buffer.limit(newLimit);
+ int count = ((int)decoder.readRawByte()) & 0xFF;
+ validateCountInitial(count);
+
+ Object decodedArray = decodeArrayAsObject(decoder, count, size);
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return decodedArray;
+ }
+ finally
+ {
+ buffer.limit(oldLimit);
+ }
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
@@ -961,28 +1094,14 @@ public class ArrayType implements PrimitiveType<Object[]>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = ((int)decoder.readRawByte()) & 0xFF;
+ validateSizeInitial(size, decoder);
buffer.position(buffer.position() + size);
}
}
private BooleanType.BooleanEncoding getUnderlyingEncoding(final boolean[] a)
{
- if(a.length == 0)
- {
- return _booleanType.getCanonicalEncoding();
- }
- else
- {
- boolean val = a[0];
- for(int i = 1; i < a.length; i++)
- {
- if(val != a[i])
- {
- return _booleanType.getCanonicalEncoding();
- }
- }
- return _booleanType.getEncoding(val);
- }
+ return _booleanType.getCanonicalEncoding();
}
private ByteType.ByteEncoding getUnderlyingEncoding(final byte[] a)
@@ -997,26 +1116,12 @@ public class ArrayType implements PrimitiveType<Object[]>
private IntegerType.IntegerEncoding getUnderlyingEncoding(final int[] a)
{
- if(a.length == 0 || !allSmallInts(a))
- {
- return _integerType.getCanonicalEncoding();
- }
- else
- {
- return _integerType.getEncoding(a[0]);
- }
+ return _integerType.getCanonicalEncoding();
}
private LongType.LongEncoding getUnderlyingEncoding(final long[] a)
{
- if(a.length == 0 || !allSmallLongs(a))
- {
- return _longType.getCanonicalEncoding();
- }
- else
- {
- return _longType.getEncoding(a[0]);
- }
+ return _longType.getCanonicalEncoding();
}
private FloatType.FloatEncoding getUnderlyingEncoding(final float[] a)
@@ -1034,20 +1139,18 @@ public class ArrayType implements PrimitiveType<Object[]>
return _characterType.getCanonicalEncoding();
}
- private static Object[] decodeArray(final DecoderImpl decoder, final int count)
+ private static Object[] decodeArray(final DecoderImpl decoder, final int count, final int size)
{
TypeConstructor constructor = decoder.readConstructor(true);
- return decodeNonPrimitive(decoder, constructor, count);
+ return decodeNonPrimitive(decoder, constructor, count, size);
}
private static Object[] decodeNonPrimitive(final DecoderImpl decoder,
final TypeConstructor constructor,
- final int count)
+ final int count,
+ final int size)
{
- if (count > decoder.getByteBufferRemaining()) {
- throw new IllegalArgumentException("Array element count "+count+" is specified to be greater than the amount of data available ("+
- decoder.getByteBufferRemaining()+")");
- }
+ checkSizeCountConstructor(decoder, constructor, count, size);
if(constructor instanceof ArrayEncoding)
{
@@ -1074,15 +1177,60 @@ public class ArrayType implements PrimitiveType<Object[]>
}
}
- private static Object decodeArrayAsObject(final DecoderImpl decoder, final int count)
+ private static void checkSizeCountConstructor(final DecoderImpl decoder, final TypeConstructor constructor, final int count, final int size)
+ {
+ TypeConstructor constructorToCheck = constructor;
+
+ if(!constructor.encodesJavaPrimitive() && constructor instanceof DynamicTypeConstructor)
+ {
+ constructorToCheck = ((DynamicTypeConstructor) constructor).getUnderlyingEncoding();
+ }
+
+ if(constructorToCheck instanceof PrimitiveTypeEncoding<?>)
+ {
+ PrimitiveTypeEncoding<?> primitiveType = (PrimitiveTypeEncoding<?>) constructorToCheck;
+
+ final byte encodingCode = primitiveType.getEncodingCode();
+ if (isZeroWidthEncoding(encodingCode))
+ {
+ if (count > decoder.getZeroWidthArrayElementLimit())
+ {
+ throw new DecodeException("Array element count " + count + " is specified to be greater than the allowed 0-width-element limit for arrays ("
+ + decoder.getZeroWidthArrayElementLimit() + ")");
+ }
+ else
+ {
+ // Remaining checks are not valid for these cases, return
+ return;
+ }
+ }
+ }
+
+ if (count > size)
+ {
+ throw new DecodeException("Array element count " + count + " is specified to be greater than the size of array (" + size +")");
+ }
+
+ if (count > decoder.getByteBufferRemaining())
+ {
+ throw new DecodeException("Array element count " + count + " is specified to be greater than the amount of data available ("+
+ decoder.getByteBufferRemaining()+")");
+ }
+ }
+
+ private static boolean isZeroWidthEncoding(final byte encodingCode)
+ {
+ return encodingCode == EncodingCodes.LIST0 || encodingCode == EncodingCodes.ULONG0
+ || encodingCode == EncodingCodes.UINT0 || encodingCode == EncodingCodes.NULL
+ || encodingCode == EncodingCodes.BOOLEAN_FALSE || encodingCode == EncodingCodes.BOOLEAN_TRUE;
+ }
+
+ private static Object decodeArrayAsObject(final DecoderImpl decoder, final int count, final int size)
{
TypeConstructor constructor = decoder.readConstructor(true);
if(constructor.encodesJavaPrimitive())
{
- if (count > decoder.getByteBufferRemaining()) {
- throw new IllegalArgumentException("Array element count "+count+" is specified to be greater than the amount of data available ("+
- decoder.getByteBufferRemaining()+")");
- }
+ checkSizeCountConstructor(decoder, constructor, count, size);
if(constructor instanceof BooleanType.BooleanEncoding)
{
@@ -1123,7 +1271,7 @@ public class ArrayType implements PrimitiveType<Object[]>
}
else
{
- return decodeNonPrimitive(decoder, constructor, count);
+ return decodeNonPrimitive(decoder, constructor, count, size);
}
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java
index c9f6d49f..3498996b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java
@@ -27,6 +27,8 @@ import org.apache.qpid.proton.amqp.Binary;
public class BinaryType extends AbstractPrimitiveType<Binary>
{
+ private static final String TYPE_NAME = "binary";
+
private final BinaryEncoding _binaryEncoding;
private final BinaryEncoding _shortBinaryEncoding;
@@ -132,12 +134,11 @@ public class BinaryType extends AbstractPrimitiveType<Binary>
public Binary readValue()
{
final DecoderImpl decoder = getDecoder();
- int size = decoder.readRawInt();
- if (size > decoder.getByteBufferRemaining()) {
- throw new IllegalArgumentException("Binary data size "+size+" is specified to be greater than the amount of data available ("+
- decoder.getByteBufferRemaining()+")");
- }
- byte[] data = new byte[size];
+ final int size = decoder.readRawInt();
+
+ ValidationSupport.validateLength(TYPE_NAME, size, decoder.getByteBufferRemaining());
+
+ final byte[] data = new byte[size];
decoder.readRaw(data, 0, size);
return new Binary(data);
}
@@ -145,9 +146,12 @@ public class BinaryType extends AbstractPrimitiveType<Binary>
@Override
public void skipValue()
{
- DecoderImpl decoder = getDecoder();
- ReadableBuffer buffer = decoder.getBuffer();
- int size = decoder.readRawInt();
+ final DecoderImpl decoder = getDecoder();
+ final ReadableBuffer buffer = decoder.getBuffer();
+ final int size = decoder.readRawInt();
+
+ ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
+
buffer.position(buffer.position() + size);
}
}
@@ -196,18 +200,25 @@ public class BinaryType extends AbstractPrimitiveType<Binary>
@Override
public Binary readValue()
{
- int size = ((int)getDecoder().readRawByte()) & 0xff;
- byte[] data = new byte[size];
- getDecoder().readRaw(data, 0, size);
+ final DecoderImpl decoder = getDecoder();
+ final int size = ((int) decoder.readRawByte()) & 0xff;
+
+ ValidationSupport.validateLength(TYPE_NAME, size, decoder.getByteBufferRemaining());
+
+ final byte[] data = new byte[size];
+ decoder.readRaw(data, 0, size);
return new Binary(data);
}
@Override
public void skipValue()
{
- DecoderImpl decoder = getDecoder();
- ReadableBuffer buffer = decoder.getBuffer();
- int size = ((int)getDecoder().readRawByte()) & 0xff;
+ final DecoderImpl decoder = getDecoder();
+ final ReadableBuffer buffer = decoder.getBuffer();
+ final int size = ((int) decoder.readRawByte()) & 0xff;
+
+ ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
+
buffer.position(buffer.position() + size);
}
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java
index c3fc1e50..27de50ec 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java
@@ -161,4 +161,7 @@ public interface Data
DescribedType getDescribedType();
String format();
+
+ int getZeroWidthElementLimit();
+ void setZeroWidthElementLimit(int zeroWidthElementLimit);
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java
index b1f47e08..751221f5 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java
@@ -143,6 +143,35 @@ public interface Decoder
Object readObject();
Object readObject(Object defaultValue);
+ /**
+ * Returns the maximum number of array elements allowed while decoding arrays of zero-width
+ * elements (ulong0, uint0, list0, boolean-true, boolean-false, null) before a decode exception.
+ *
+ * @return the maximum number of zero-width elements allowed in an array.
+ */
+ int getZeroWidthArrayElementLimit();
+
+ /**
+ * Configures the maximum number of array elements allowed while decoding arrays of zero-width
+ * elements (ulong0, uint0, list0, boolean-true, boolean-false, null) before a decode exception.
+ * Default is 0, meaning only the empty array is permitted.
+ */
+ void setZeroWidthArrayElementLimit(int zeroWidthElementLimit);
+
+ /**
+ * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload.
+ *
+ * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+ */
+ int getMaxDecodeDepth();
+
+ /**
+ * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload. Default is 50.
+ */
+ void setMaxDecodeDepth(int maxDecodeDepth);
+
void register(final Object descriptor, final DescribedTypeConstructor dtc);
void register(final Object descriptor, final FastPathDescribedTypeConstructor<?> dtc);
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java
index 58dcfd1e..8447e984 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java
@@ -25,7 +25,6 @@ import java.nio.ByteBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -45,6 +44,15 @@ import org.apache.qpid.proton.amqp.UnsignedShort;
public class DecoderImpl implements ByteBufferDecoder
{
+ private static final int UNKNOWN_DT_CONSTRUCTOR_CACHE_SIZE = 16;
+ private static final int DT_DESCRIPTOR_MAX_CACHED_SYMBOL_LENGTH = 64;
+
+ /**
+ * The default maximum depth the decoder will allow before triggering a {@link DecodeException}
+ * when the depth is increased during decode of complex types that can nest objects.
+ */
+ private static final int DEFAULT_MAX_DECODE_DEPTH = 32;
+
private ReadableBuffer _buffer;
private final CharsetDecoder _charsetDecoder = StandardCharsets.UTF_8.newDecoder();
@@ -56,15 +64,68 @@ public class DecoderImpl implements ByteBufferDecoder
private final Map<Object, FastPathDescribedTypeConstructor<?>> _fastPathTypeConstructors =
new HashMap<Object, FastPathDescribedTypeConstructor<?>>();
+ private Map<Object, UnknownDescribedTypeConstructor> _unknownDescribedTypeConstructors = new HashMap<>();
+
+ private int _zeroWidthElementLimit = 0;
+ private boolean _sasl;
+
+ private int _maxDecodeDepth = DEFAULT_MAX_DECODE_DEPTH;
+ private int _decodeDepth;
+
public DecoderImpl()
{
+ this(false);
+ }
+
+ public DecoderImpl(boolean sasl)
+ {
+ _sasl = sasl;
}
DecoderImpl(final ByteBuffer buffer)
+ {
+ this(buffer, false);
+ }
+
+ DecoderImpl(final ByteBuffer buffer, boolean sasl)
{
_buffer = new ReadableBuffer.ByteBufferReader(buffer);
}
+ /**
+ * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload.
+ *
+ * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+ */
+ @Override
+ public int getMaxDecodeDepth() {
+ return _maxDecodeDepth;
+ }
+
+ /**
+ * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload. Default is 32.
+ */
+ @Override
+ public void setMaxDecodeDepth(int maxDecodeDepth) {
+ this._maxDecodeDepth = maxDecodeDepth;
+ }
+
+ public void increaseDecodeDepth() {
+ if (++_decodeDepth > _maxDecodeDepth) {
+ --_decodeDepth; // Reset so the unwind goes back to zero
+ throw new DecodeException("The nesting of types in the object being decoded exceeded the configured limit: " + _maxDecodeDepth);
+ }
+ }
+
+ public void decreaseDecodeDepth() {
+ if(--_decodeDepth < 0) {
+ ++_decodeDepth; // Reset so the unwind goes back to zero
+ throw new IllegalStateException("Decode depth cannot go negative");
+ }
+ }
+
public TypeConstructor<?> peekConstructor()
{
_buffer.mark();
@@ -81,17 +142,28 @@ public class DecoderImpl implements ByteBufferDecoder
@SuppressWarnings("rawtypes")
public TypeConstructor readConstructor()
{
- return readConstructor(false);
+ return readConstructor(false, false);
}
@SuppressWarnings("rawtypes")
public TypeConstructor readConstructor(boolean excludeFastPathConstructors)
+ {
+ return readConstructor(excludeFastPathConstructors, false);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private TypeConstructor readConstructor(boolean excludeFastPathConstructors, boolean decodingDescribedTypePrimitiveNestedEncoding)
{
int code = ((int)readRawByte()) & 0xff;
if(code == EncodingCodes.DESCRIBED_TYPE_INDICATOR)
{
+ if(decodingDescribedTypePrimitiveNestedEncoding) {
+ throw new DecodeException("DescribedType value encoding cannot be another DescribedType, must be a primitive type");
+ }
+
final byte encoding = _buffer.get(_buffer.position());
final Object descriptor;
+ boolean cachable = true;
if (EncodingCodes.SMALLULONG == encoding || EncodingCodes.ULONG == encoding)
{
@@ -99,11 +171,22 @@ public class DecoderImpl implements ByteBufferDecoder
}
else if (EncodingCodes.SYM8 == encoding || EncodingCodes.SYM32 == encoding)
{
- descriptor = readSymbol(null);
+ final Symbol sym = readSymbol(null);
+ if (sym.length() > DT_DESCRIPTOR_MAX_CACHED_SYMBOL_LENGTH)
+ {
+ cachable = false;
+ }
+ descriptor = sym;
}
else
{
- descriptor = readObject();
+ if (_sasl)
+ {
+ throw new DecodeException("Unexpected described type descriptor encoding during SASL: " + EncodingCodes.toString(encoding));
+ }
+
+ descriptor = readReservedDescriptor();
+ cachable = false;
}
if (!excludeFastPathConstructors)
@@ -115,26 +198,13 @@ public class DecoderImpl implements ByteBufferDecoder
}
}
- TypeConstructor<?> nestedEncoding = readConstructor(false);
+ TypeConstructor<?> nestedEncoding = readConstructor(false, true);
DescribedTypeConstructor<?> dtc = _dynamicTypeConstructors.get(descriptor);
if(dtc == null)
{
- dtc = new DescribedTypeConstructor()
- {
- @Override
- public DescribedType newInstance(final Object described)
- {
- return new UnknownDescribedType(descriptor, described);
- }
-
- @Override
- public Class<?> getTypeClass()
- {
- return UnknownDescribedType.class;
- }
- };
- register(descriptor, dtc);
+ dtc = handleUnknownDescribedType(descriptor, cachable);
}
+
return new DynamicTypeConstructor(dtc, nestedEncoding);
}
else
@@ -143,6 +213,25 @@ public class DecoderImpl implements ByteBufferDecoder
}
}
+ private UnknownDescribedTypeConstructor handleUnknownDescribedType(final Object descriptor, final boolean cacheAllowed) {
+ if (_sasl)
+ {
+ throw new DecodeException("Cannot decode unknown described types during SASL");
+ }
+
+ UnknownDescribedTypeConstructor udtc = cacheAllowed ? _unknownDescribedTypeConstructors.get(descriptor): null;
+
+ if (udtc == null)
+ {
+ udtc = new UnknownDescribedTypeConstructor(descriptor);
+ if (cacheAllowed && _unknownDescribedTypeConstructors.size() < UNKNOWN_DT_CONSTRUCTOR_CACHE_SIZE)
+ {
+ _unknownDescribedTypeConstructors.put(descriptor, udtc);
+ }
+ }
+ return udtc;
+ }
+
@Override
public void register(final Object descriptor, final FastPathDescribedTypeConstructor<?> btc)
{
@@ -236,22 +325,15 @@ public class DecoderImpl implements ByteBufferDecoder
@Override
public byte readByte(final byte defaultVal)
{
- TypeConstructor<?> constructor = readConstructor();
- if(constructor instanceof ByteType.ByteEncoding)
- {
- return ((ByteType.ByteEncoding)constructor).readPrimitiveValue();
- }
- else
- {
- Object val = constructor.readValue();
- if(val == null)
- {
+ byte encodingCode = _buffer.get();
+
+ switch (encodingCode) {
+ case EncodingCodes.BYTE:
+ return readRawByte();
+ case EncodingCodes.NULL:
return defaultVal;
- }
- else
- {
- throw unexpectedType(val, Byte.class);
- }
+ default:
+ throw new DecodeException("Expected Byte type but found encoding: " + EncodingCodes.toString(encodingCode));
}
}
@@ -280,22 +362,16 @@ public class DecoderImpl implements ByteBufferDecoder
@Override
public short readShort(final short defaultVal)
{
- TypeConstructor<?> constructor = readConstructor();
- if(constructor instanceof ShortType.ShortEncoding)
- {
- return ((ShortType.ShortEncoding)constructor).readPrimitiveValue();
- }
- else
+ byte encodingCode = _buffer.get();
+
+ switch (encodingCode)
{
- Object val = constructor.readValue();
- if(val == null)
- {
+ case EncodingCodes.SHORT:
+ return readRawShort();
+ case EncodingCodes.NULL:
return defaultVal;
- }
- else
- {
- throw unexpectedType(val, Short.class);
- }
+ default:
+ throw new DecodeException("Expected Short type but found encoding: " + EncodingCodes.toString(encodingCode));
}
}
@@ -326,22 +402,18 @@ public class DecoderImpl implements ByteBufferDecoder
@Override
public int readInteger(final int defaultVal)
{
- TypeConstructor<?> constructor = readConstructor();
- if(constructor instanceof IntegerType.IntegerEncoding)
- {
- return ((IntegerType.IntegerEncoding)constructor).readPrimitiveValue();
- }
- else
+ byte encodingCode = _buffer.get();
+
+ switch (encodingCode)
{
- Object val = constructor.readValue();
- if(val == null)
- {
+ case EncodingCodes.SMALLINT:
+ return readRawByte();
+ case EncodingCodes.INT:
+ return readRawInt();
+ case EncodingCodes.NULL:
return defaultVal;
- }
- else
- {
- throw unexpectedType(val, Integer.class);
- }
+ default:
+ throw new DecodeException("Expected Integer type but found encoding: " + EncodingCodes.toString(encodingCode));
}
}
@@ -372,22 +444,18 @@ public class DecoderImpl implements ByteBufferDecoder
@Override
public long readLong(final long defaultVal)
{
- TypeConstructor<?> constructor = readConstructor();
- if(constructor instanceof LongType.LongEncoding)
- {
- return ((LongType.LongEncoding)constructor).readPrimitiveValue();
- }
- else
+ byte encodingCode = _buffer.get();
+
+ switch (encodingCode)
{
- Object val = constructor.readValue();
- if(val == null)
- {
+ case EncodingCodes.SMALLLONG:
+ return readRawByte();
+ case EncodingCodes.LONG:
+ return readRawLong();
+ case EncodingCodes.NULL:
return defaultVal;
- }
- else
- {
- throw unexpectedType(val, Long.class);
- }
+ default:
+ throw new DecodeException("Expected Long type but found encoding: " + EncodingCodes.toString(encodingCode));
}
}
@@ -550,22 +618,16 @@ public class DecoderImpl implements ByteBufferDecoder
@Override
public float readFloat(final float defaultVal)
{
- TypeConstructor<?> constructor = readConstructor();
- if(constructor instanceof FloatType.FloatEncoding)
- {
- return ((FloatType.FloatEncoding)constructor).readPrimitiveValue();
- }
- else
+ byte encodingCode = _buffer.get();
+
+ switch (encodingCode)
{
- Object val = constructor.readValue();
- if(val == null)
- {
+ case EncodingCodes.FLOAT:
+ return readRawFloat();
+ case EncodingCodes.NULL:
return defaultVal;
- }
- else
- {
- throw unexpectedType(val, Float.class);
- }
+ default:
+ throw new DecodeException("Expected Float type but found encoding: " + EncodingCodes.toString(encodingCode));
}
}
@@ -594,22 +656,16 @@ public class DecoderImpl implements ByteBufferDecoder
@Override
public double readDouble(final double defaultVal)
{
- TypeConstructor<?> constructor = readConstructor();
- if(constructor instanceof DoubleType.DoubleEncoding)
- {
- return ((DoubleType.DoubleEncoding)constructor).readPrimitiveValue();
- }
- else
+ byte encodingCode = _buffer.get();
+
+ switch (encodingCode)
{
- Object val = constructor.readValue();
- if(val == null)
- {
+ case EncodingCodes.DOUBLE:
+ return readRawDouble();
+ case EncodingCodes.NULL:
return defaultVal;
- }
- else
- {
- throw unexpectedType(val, Double.class);
- }
+ default:
+ throw new DecodeException("Expected Double type but found encoding: " + EncodingCodes.toString(encodingCode));
}
}
@@ -804,7 +860,7 @@ public class DecoderImpl implements ByteBufferDecoder
switch (encodingCode)
{
case EncodingCodes.LIST0:
- return Collections.EMPTY_LIST;
+ return (List) _constructors[EncodingCodes.LIST0 & 0xff].readValue();
case EncodingCodes.LIST8:
return (List) _constructors[EncodingCodes.LIST8 & 0xff].readValue();
case EncodingCodes.LIST32:
@@ -1027,6 +1083,11 @@ public class DecoderImpl implements ByteBufferDecoder
return val == null ? defaultValue : val;
}
+ protected Object readReservedDescriptor()
+ {
+ throw new DecodeException("Reserved non-ulong/non-symbol described-type descriptors not supported by this decoder");
+ }
+
<V> void register(PrimitiveType<V> type)
{
Collection<? extends PrimitiveTypeEncoding<V>> encodings = type.getAllEncodings();
@@ -1073,17 +1134,26 @@ public class DecoderImpl implements ByteBufferDecoder
_buffer.get(data, offset, length);
}
- <V> V readRaw(TypeDecoder<V> decoder, int size)
+ @Deprecated
+ <V> V readRaw(final TypeDecoder<V> decoder, final int size)
+ {
+ return readRaw(decoder, size, "unspecified-type");
+ }
+
+ <V> V readRaw(final TypeDecoder<V> decoder, final int size, final String typeName)
{
+ ValidationSupport.validateLength(typeName, size, _buffer.remaining());
+
final int oldLimit = _buffer.limit();
- final int oldPosition = _buffer.position();
+ final int newLimit = _buffer.position() + size;
final V decode;
-
try {
- decode = decoder.decode(this, _buffer.limit(_buffer.position() + size));
+ decode = decoder.decode(this, _buffer.limit(newLimit));
+ if (_buffer.position() != newLimit) {
+ throw new DecodeException(typeName + " decode unexpectedly left " + (newLimit - _buffer.position()) + " bytes from size indicated " + size);
+ }
} finally {
- _buffer.position(oldPosition + size);
_buffer.limit(oldLimit);
}
@@ -1121,6 +1191,26 @@ public class DecoderImpl implements ByteBufferDecoder
V decode(DecoderImpl decoder, ReadableBuffer buf);
}
+ private static final class UnknownDescribedTypeConstructor implements DescribedTypeConstructor<Object> {
+ private final Object descriptor;
+
+ private UnknownDescribedTypeConstructor(Object descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public DescribedType newInstance(final Object described)
+ {
+ return new UnknownDescribedType(descriptor, described);
+ }
+
+ @Override
+ public Class<?> getTypeClass()
+ {
+ return UnknownDescribedType.class;
+ }
+ }
+
private static class UnknownDescribedType implements DescribedType
{
private final Object _descriptor;
@@ -1163,4 +1253,14 @@ public class DecoderImpl implements ByteBufferDecoder
public int getByteBufferRemaining() {
return _buffer.remaining();
}
+
+ @Override
+ public int getZeroWidthArrayElementLimit() {
+ return _zeroWidthElementLimit;
+ }
+
+ @Override
+ public void setZeroWidthArrayElementLimit(int zeroWidthElementLimit) {
+ this._zeroWidthElementLimit = zeroWidthElementLimit;
+ }
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java
index 2f8b9f91..873d8dc1 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java
@@ -62,4 +62,15 @@ public class DynamicTypeConstructor implements TypeConstructor
{
return _describedTypeConstructor.getTypeClass();
}
+
+ public TypeConstructor getUnderlyingEncoding()
+ {
+ return _underlyingEncoding;
+ }
+
+ public DescribedTypeConstructor getDescribedTypeConstructor()
+ {
+ return _describedTypeConstructor;
+ }
+
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
index 164a77d6..76f475cd 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
@@ -80,7 +80,12 @@ public final class EncoderImpl implements ByteBufferEncoder
private final ArrayType _arrayType;
- public EncoderImpl(DecoderImpl decoder)
+ public EncoderImpl(final DecoderImpl decoder)
+ {
+ this(decoder, false);
+ }
+
+ public EncoderImpl(final DecoderImpl decoder, final boolean sasl)
{
_decoder = decoder;
_nullType = new NullType(this, decoder);
@@ -107,7 +112,7 @@ public final class EncoderImpl implements ByteBufferEncoder
_binaryType = new BinaryType(this, decoder);
- _symbolType = new SymbolType(this, decoder);
+ _symbolType = new SymbolType(this, decoder, sasl);
_stringType = new StringType(this, decoder);
_listType = new ListType(this, decoder);
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java
index 265063cb..14e02584 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java
@@ -20,6 +20,9 @@
*/
package org.apache.qpid.proton.codec;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -160,76 +163,98 @@ public class ListType extends AbstractPrimitiveType<List>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
- int size = decoder.readRawInt();
- // todo - limit the decoder with size
- int count = decoder.readRawInt();
- // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
- if (count > decoder.getByteBufferRemaining()) {
- throw new IllegalArgumentException("List element count "+count+" is specified to be greater than the amount of data available ("+
- decoder.getByteBufferRemaining()+")");
- }
+ decoder.increaseDecodeDepth();
+ try
+ {
+ final int size = decoder.readRawInt();
+ validateListSize(size, decoder.getByteBufferRemaining());
- TypeConstructor<?> typeConstructor = null;
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
- List<Object> list = new ArrayList<>(count);
- for (int i = 0; i < count; i++)
- {
- boolean arrayType = false;
- byte encodingCode = buffer.get(buffer.position());
- switch (encodingCode)
+ List<Object> list = null;
+ try
{
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
- }
+ buffer.limit(newLimit);
- // Whenever we can just reuse the previously used TypeDecoder instead
- // of spending time looking up the same one again.
- if (typeConstructor == null)
- {
- typeConstructor = getDecoder().readConstructor();
- }
- else
- {
- if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
- {
- typeConstructor = getDecoder().readConstructor();
- }
- else
+ final int count = decoder.readRawInt();
+ validateListCount(size, count, decoder.getByteBufferRemaining());
+
+ TypeConstructor<?> typeConstructor = null;
+
+ list = new ArrayList<>(count);
+ for (int i = 0; i < count; i++)
{
- PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
- if (encodingCode != primitiveConstructor.getEncodingCode())
+ boolean arrayType = false;
+ byte encodingCode = buffer.get(buffer.position());
+ switch (encodingCode)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
+
+ // Whenever we can just reuse the previously used TypeDecoder instead
+ // of spending time looking up the same one again.
+ if (typeConstructor == null)
{
typeConstructor = getDecoder().readConstructor();
}
else
{
- // consume the encoding code byte for real
- encodingCode = buffer.get();
+ if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
+ {
+ typeConstructor = getDecoder().readConstructor();
+ }
+ else
+ {
+ PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
+ if (encodingCode != primitiveConstructor.getEncodingCode())
+ {
+ typeConstructor = getDecoder().readConstructor();
+ }
+ else
+ {
+ // consume the encoding code byte for real
+ encodingCode = buffer.get();
+ }
+ }
}
- }
- }
- if(typeConstructor == null)
- {
- throw new DecodeException("Unknown constructor");
- }
+ if(typeConstructor == null)
+ {
+ throw new DecodeException("Unknown constructor");
+ }
- final Object value;
+ final Object value;
- if (arrayType)
- {
- value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+ if (arrayType)
+ {
+ value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+ }
+ else
+ {
+ value = typeConstructor.readValue();
+ }
+
+ list.add(value);
+ }
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("List incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
}
- else
+ finally
{
- value = typeConstructor.readValue();
+ buffer.limit(oldLimit);
}
- list.add(value);
+ return list;
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
}
-
- return list;
}
@Override
@@ -238,6 +263,7 @@ public class ListType extends AbstractPrimitiveType<List>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = decoder.readRawInt();
+ validateListSize(size, decoder.getByteBufferRemaining());
buffer.position(buffer.position() + size);
}
@@ -310,71 +336,99 @@ public class ListType extends AbstractPrimitiveType<List>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
- int size = ((int)decoder.readRawByte()) & 0xff;
- // todo - limit the decoder with size
- int count = ((int)decoder.readRawByte()) & 0xff;
+ decoder.increaseDecodeDepth();
+ try
+ {
+ int size = ((int)decoder.readRawByte()) & 0xff;
+ validateListSize(size, decoder.getByteBufferRemaining());
- TypeConstructor<?> typeConstructor = null;
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
- List<Object> list = new ArrayList<>(count);
- for (int i = 0; i < count; i++)
- {
- boolean arrayType = false;
- byte encodingCode = buffer.get(buffer.position());
- switch (encodingCode)
+ List<Object> list = null;
+ try
{
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
- }
+ buffer.limit(newLimit);
- // Whenever we can just reuse the previously used TypeDecoder instead
- // of spending time looking up the same one again.
- if (typeConstructor == null)
- {
- typeConstructor = getDecoder().readConstructor();
- }
- else
- {
- if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
- {
- typeConstructor = getDecoder().readConstructor();
- }
- else
+ int count = ((int)decoder.readRawByte()) & 0xff;
+ validateListCount(size, count, decoder.getByteBufferRemaining());
+
+ TypeConstructor<?> typeConstructor = null;
+
+ list = new ArrayList<>(count);
+ for (int i = 0; i < count; i++)
{
- PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
- if (encodingCode != primitiveConstructor.getEncodingCode())
+ boolean arrayType = false;
+ byte encodingCode = buffer.get(buffer.position());
+ switch (encodingCode)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
+
+ // Whenever we can just reuse the previously used TypeDecoder instead
+ // of spending time looking up the same one again.
+ if (typeConstructor == null)
{
typeConstructor = getDecoder().readConstructor();
}
else
{
- // consume the encoding code byte for real
- encodingCode = buffer.get();
+ if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
+ {
+ typeConstructor = getDecoder().readConstructor();
+ }
+ else
+ {
+ PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
+ if (encodingCode != primitiveConstructor.getEncodingCode())
+ {
+ typeConstructor = getDecoder().readConstructor();
+ }
+ else
+ {
+ // consume the encoding code byte for real
+ encodingCode = buffer.get();
+ }
+ }
}
- }
- }
- if (typeConstructor == null)
- {
- throw new DecodeException("Unknown constructor");
- }
+ if (typeConstructor == null)
+ {
+ throw new DecodeException("Unknown constructor");
+ }
- final Object value;
+ final Object value;
- if (arrayType)
- {
- value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+ if (arrayType)
+ {
+ value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+ }
+ else
+ {
+ value = typeConstructor.readValue();
+ }
+
+ list.add(value);
+ }
+
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("List incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
}
- else
+ finally
{
- value = typeConstructor.readValue();
+ buffer.limit(oldLimit);
}
- list.add(value);
+ return list;
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
}
-
- return list;
}
@Override
@@ -383,6 +437,7 @@ public class ListType extends AbstractPrimitiveType<List>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = ((int)decoder.readRawByte()) & 0xff;
+ validateListSize(size, decoder.getByteBufferRemaining());
buffer.position(buffer.position() + size);
}
@@ -441,7 +496,16 @@ public class ListType extends AbstractPrimitiveType<List>
@Override
public List readValue()
{
- return Collections.EMPTY_LIST;
+ DecoderImpl decoder = getDecoder();
+ decoder.increaseDecodeDepth();
+ try
+ {
+ return Collections.EMPTY_LIST;
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
+ }
}
}
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java
index d73351e0..ed996585 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java
@@ -20,6 +20,9 @@
*/
package org.apache.qpid.proton.codec;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@@ -246,58 +249,82 @@ public class MapType extends AbstractPrimitiveType<Map>
final DecoderImpl decoder = getDecoder();
final ReadableBuffer buffer = decoder.getBuffer();
- final int size = decoder.readRawInt();
- // todo - limit the decoder with size
- final int count = decoder.readRawInt();
- if (count > decoder.getByteBufferRemaining()) {
- throw new IllegalArgumentException("Map element count "+count+" is specified to be greater than the amount of data available ("+
- decoder.getByteBufferRemaining()+")");
- }
-
- TypeConstructor<?> keyConstructor = null;
- TypeConstructor<?> valueConstructor = null;
-
- Map<Object, Object> map = new LinkedHashMap<>(count);
- for(int i = 0; i < count / 2; i++)
+ decoder.increaseDecodeDepth();
+ try
{
- keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
- if(keyConstructor == null)
- {
- throw new DecodeException("Unknown constructor");
- }
+ final int size = decoder.readRawInt();
+ validateMapSize(size, decoder.getByteBufferRemaining());
- final Object key = keyConstructor.readValue();
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
- boolean arrayType = false;
- final byte code = buffer.get(buffer.position());
- switch (code)
+ Map<Object, Object> map = null;
+ try
{
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
- }
+ buffer.limit(newLimit);
- valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
- if (valueConstructor == null)
- {
- throw new DecodeException("Unknown constructor");
- }
+ final int count = decoder.readRawInt();
+ validateMapCount(size, count, decoder.getByteBufferRemaining());
- final Object value;
+ TypeConstructor<?> keyConstructor = null;
+ TypeConstructor<?> valueConstructor = null;
- if (arrayType)
- {
- value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ map = new LinkedHashMap<>(count);
+ for(int i = 0; i < count / 2; i++)
+ {
+ keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
+ if(keyConstructor == null)
+ {
+ throw new DecodeException("Unknown constructor");
+ }
+
+ final Object key = keyConstructor.readValue();
+
+ boolean arrayType = false;
+ final byte code = buffer.get(buffer.position());
+ switch (code)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
+
+ valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+ if (valueConstructor == null)
+ {
+ throw new DecodeException("Unknown constructor");
+ }
+
+ final Object value;
+
+ if (arrayType)
+ {
+ value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ }
+ else
+ {
+ value = valueConstructor.readValue();
+ }
+
+ map.put(key, value);
+ }
+
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("Map incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
}
- else
+ finally
{
- value = valueConstructor.readValue();
+ buffer.limit(oldLimit);
}
- map.put(key, value);
+ return map;
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
}
-
- return map;
}
@Override
@@ -306,6 +333,7 @@ public class MapType extends AbstractPrimitiveType<Map>
final DecoderImpl decoder = getDecoder();
final ReadableBuffer buffer = decoder.getBuffer();
final int size = decoder.readRawInt();
+ validateMapSize(size, decoder.getByteBufferRemaining());
buffer.position(buffer.position() + size);
}
@@ -398,54 +426,82 @@ public class MapType extends AbstractPrimitiveType<Map>
final DecoderImpl decoder = getDecoder();
final ReadableBuffer buffer = decoder.getBuffer();
- final int size = (decoder.readRawByte()) & 0xff;
- // todo - limit the decoder with size
- final int count = (decoder.readRawByte()) & 0xff;
-
- TypeConstructor<?> keyConstructor = null;
- TypeConstructor<?> valueConstructor = null;
-
- final Map<Object, Object> map = new LinkedHashMap<>(count);
- for(int i = 0; i < count / 2; i++)
+ decoder.increaseDecodeDepth();
+ try
{
- keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
- if(keyConstructor == null)
- {
- throw new DecodeException("Unknown constructor");
- }
+ final int size = (decoder.readRawByte()) & 0xff;
+ validateMapSize(size, decoder.getByteBufferRemaining());
- final Object key = keyConstructor.readValue();
+ final int oldLimit = buffer.limit();
+ final int newLimit = buffer.position() + size;
- boolean arrayType = false;
- final byte code = buffer.get(buffer.position());
- switch (code)
+ Map<Object, Object> map = null;
+ try
{
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
- }
+ buffer.limit(newLimit);
- valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
- if(valueConstructor== null)
- {
- throw new DecodeException("Unknown constructor");
- }
+ final int count = (decoder.readRawByte()) & 0xff;
+ validateMapCount(size, count, decoder.getByteBufferRemaining());
- final Object value;
+ TypeConstructor<?> keyConstructor = null;
+ TypeConstructor<?> valueConstructor = null;
- if (arrayType)
- {
- value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ map = new LinkedHashMap<>(count);
+ for(int i = 0; i < count / 2; i++)
+ {
+ keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
+ if(keyConstructor == null)
+ {
+ throw new DecodeException("Unknown constructor");
+ }
+
+ final Object key = keyConstructor.readValue();
+
+ boolean arrayType = false;
+ final byte code = buffer.get(buffer.position());
+ switch (code)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
+
+ valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+ if(valueConstructor== null)
+ {
+ throw new DecodeException("Unknown constructor");
+ }
+
+ final Object value;
+
+ if (arrayType)
+ {
+ value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ }
+ else
+ {
+ value = valueConstructor.readValue();
+ }
+
+ map.put(key, value);
+ }
+
+ if (buffer.position() != newLimit)
+ {
+ throw new DecodeException("Map incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
}
- else
+ finally
{
- value = valueConstructor.readValue();
+ buffer.limit(oldLimit);
}
- map.put(key, value);
+ return map;
+ }
+ finally
+ {
+ decoder.decreaseDecodeDepth();
}
-
- return map;
}
@Override
@@ -454,6 +510,7 @@ public class MapType extends AbstractPrimitiveType<Map>
final DecoderImpl decoder = getDecoder();
final ReadableBuffer buffer = decoder.getBuffer();
final int size = ((int)decoder.readRawByte()) & 0xff;
+ validateMapSize(size, decoder.getByteBufferRemaining());
buffer.position(buffer.position() + size);
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java
index e6184b4e..20fe23bf 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java
@@ -27,6 +27,8 @@ import java.util.Collection;
public class StringType extends AbstractPrimitiveType<String>
{
+ private static final String TYPE_NAME = "string";
+
private static final DecoderImpl.TypeDecoder<String> _stringCreator =
new DecoderImpl.TypeDecoder<String>()
{
@@ -180,9 +182,10 @@ public class StringType extends AbstractPrimitiveType<String>
@Override
public String readValue()
{
- DecoderImpl decoder = getDecoder();
- int size = decoder.readRawInt();
- return size == 0 ? "" : decoder.readRaw(_stringCreator, size);
+ final DecoderImpl decoder = getDecoder();
+ final int size = decoder.readRawInt();
+
+ return size == 0 ? "" : decoder.readRaw(_stringCreator, size, TYPE_NAME);
}
@Override
@@ -198,6 +201,7 @@ public class StringType extends AbstractPrimitiveType<String>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = decoder.readRawInt();
+ ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
buffer.position(buffer.position() + size);
}
}
@@ -250,7 +254,8 @@ public class StringType extends AbstractPrimitiveType<String>
{
DecoderImpl decoder = getDecoder();
int size = ((int)decoder.readRawByte()) & 0xff;
- return size == 0 ? "" : decoder.readRaw(_stringCreator, size);
+
+ return size == 0 ? "" : decoder.readRaw(_stringCreator, size, TYPE_NAME);
}
@Override
@@ -266,6 +271,7 @@ public class StringType extends AbstractPrimitiveType<String>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = ((int)decoder.readRawByte()) & 0xff;
+ ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
buffer.position(buffer.position() + size);
}
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java
index faee7b0b..84546260 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java
@@ -20,44 +20,48 @@
*/
package org.apache.qpid.proton.codec;
-import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import org.apache.qpid.proton.amqp.Symbol;
+
public class SymbolType extends AbstractPrimitiveType<Symbol>
{
- private static final Charset ASCII_CHARSET = Charset.forName("US-ASCII");
+ private static final int MAX_SYMBOL_CACHE_SIZE = 4096;
+ private static final int MAX_CACHED_SYMBOL_LENGTH = 64;
+
+ private static final int MAX_SASL_SYMBOL_CACHE_SIZE = 64;
+ private static final int MAX_CACHED_SASL_SYMBOL_LENGTH = 32;
+
+ private static final ConcurrentMap<ReadableBuffer, Symbol> _symbolCache = new ConcurrentHashMap<ReadableBuffer, Symbol>(2048);
+ private static final ConcurrentMap<ReadableBuffer, Symbol> _saslSymbolCache = new ConcurrentHashMap<ReadableBuffer, Symbol>(32);
+
+ private static final String TYPE_NAME = "symbol";
+
+ private static final DecoderImpl.TypeDecoder<Symbol> _symbolDecoder = new AbstractSymbolDecoder(MAX_SYMBOL_CACHE_SIZE, MAX_CACHED_SYMBOL_LENGTH, _symbolCache)
+ {
+ @Override
+ public Symbol createSymbol(final String symbolVal)
+ {
+ return Symbol.getSymbol(symbolVal);
+ }
+ };
+
+ private static final DecoderImpl.TypeDecoder<Symbol> _saslSymbolDecoder = new AbstractSymbolDecoder(MAX_SASL_SYMBOL_CACHE_SIZE, MAX_CACHED_SASL_SYMBOL_LENGTH, _saslSymbolCache)
+ {
+ @Override
+ public Symbol createSymbol(final String symbolVal)
+ {
+ return Symbol.getSaslSymbol(symbolVal);
+ }
+ };
+
private final SymbolEncoding _symbolEncoding;
private final SymbolEncoding _shortSymbolEncoding;
-
- private final Map<ReadableBuffer, Symbol> _symbolCache = new HashMap<ReadableBuffer, Symbol>();
- private DecoderImpl.TypeDecoder<Symbol> _symbolCreator =
- new DecoderImpl.TypeDecoder<Symbol>()
- {
- @Override
- public Symbol decode(DecoderImpl decoder, ReadableBuffer buffer)
- {
- Symbol symbol = _symbolCache.get(buffer);
- if (symbol == null)
- {
- final byte[] bytes = new byte[buffer.remaining()];
- buffer.get(bytes);
-
- symbol = Symbol.getSymbol(new String(bytes, ASCII_CHARSET));
-
- _symbolCache.put(ReadableBuffer.ByteBufferReader.wrap(bytes), symbol);
- }
- else
- {
- buffer.position(buffer.limit());
- }
- return symbol;
- }
- };
+ private DecoderImpl.TypeDecoder<Symbol> _symbolCreator;
public static interface SymbolEncoding extends PrimitiveTypeEncoding<Symbol>
{
@@ -65,13 +69,20 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
}
SymbolType(final EncoderImpl encoder, final DecoderImpl decoder)
+ {
+ this(encoder, decoder, false);
+ }
+
+ SymbolType(final EncoderImpl encoder, final DecoderImpl decoder, final boolean sasl)
{
_symbolEncoding = new LongSymbolEncoding(encoder, decoder);
_shortSymbolEncoding = new ShortSymbolEncoding(encoder, decoder);
+ _symbolCreator = sasl ? _saslSymbolDecoder : _symbolDecoder;
encoder.register(Symbol.class, this);
decoder.register(this);
}
+
@Override
public Class<Symbol> getTypeClass()
{
@@ -162,7 +173,8 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
{
DecoderImpl decoder = getDecoder();
int size = decoder.readRawInt();
- return decoder.readRaw(_symbolCreator, size);
+
+ return decoder.readRaw(_symbolCreator, size, TYPE_NAME);
}
@Override
@@ -171,6 +183,7 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = decoder.readRawInt();
+ ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
buffer.position(buffer.position() + size);
}
}
@@ -221,7 +234,8 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
{
DecoderImpl decoder = getDecoder();
int size = ((int)decoder.readRawByte()) & 0xff;
- return decoder.readRaw(_symbolCreator, size);
+
+ return decoder.readRaw(_symbolCreator, size, TYPE_NAME);
}
@Override
@@ -230,6 +244,7 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
int size = ((int)decoder.readRawByte()) & 0xff;
+ ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
buffer.position(buffer.position() + size);
}
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/ValidationSupport.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/ValidationSupport.java
new file mode 100644
index 00000000..9a958882
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/ValidationSupport.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.proton.codec;
+
+public class ValidationSupport {
+
+ private ValidationSupport()
+ {
+ // No instances
+ }
+
+ public static void validateMapSize(final int size, final int remaining)
+ {
+ if (size < 0)
+ {
+ throw new DecodeException("Map size " + size + " cannot be negative");
+ }
+
+ if (size > remaining)
+ {
+ throw new DecodeException("Map size " + size + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+
+ public static void validateMapCount(final int size, final int count, final int remaining)
+ {
+ if (count < 0)
+ {
+ throw new DecodeException("Map element count " + count + " cannot be negative");
+ }
+
+ if (count >= size)
+ {
+ throw new DecodeException("Map element count " + count + " must be less than size (" + size + ")");
+ }
+
+ if (count > remaining)
+ {
+ throw new DecodeException("Map element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+ }
+
+ if (count % 2 != 0)
+ {
+ throw new DecodeException("Map element count " + count + " cannot be odd");
+ }
+ }
+
+
+ public static void validateListSize(final int size, final int remaining)
+ {
+ if (size < 0)
+ {
+ throw new DecodeException("List size " + size + " cannot be negative");
+ }
+
+ if (size > remaining)
+ {
+ throw new DecodeException("List size " + size + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+
+ public static void validateListCount(final int size, final int count, final int remaining)
+ {
+ if (count < 0)
+ {
+ throw new DecodeException("List element count " + count + " cannot be negative");
+ }
+
+ if (count >= size)
+ {
+ throw new DecodeException("List element count " + count + " must be less than size (" + size + ")");
+ }
+
+ if (count > remaining)
+ {
+ throw new DecodeException("List element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+ }
+ }
+
+ public static void validateLength(final String type, final int length, final int remaining)
+ {
+ if (length < 0)
+ {
+ throw new DecodeException(type + " length " + length + " cannot be negative");
+ }
+
+ if (length > remaining)
+ {
+ throw new DecodeException(type + " length " + length + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java
index 71199bb3..e03097b1 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java
@@ -21,13 +21,23 @@
package org.apache.qpid.proton.codec.impl;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateConstructorSize;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateListSize;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateMapSize;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateSizeInitial;
+
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import org.apache.qpid.proton.amqp.*;
import org.apache.qpid.proton.codec.Data;
+import org.apache.qpid.proton.codec.EncodingCodes;
class DataDecoder
{
@@ -35,6 +45,13 @@ class DataDecoder
private static final Charset ASCII = Charset.forName("US-ASCII");
private static final Charset UTF_8 = Charset.forName("UTF-8");
+ private static final long AMQP_VALUE_NUMERIC_CODE = 0x0000000000000077L;
+ private static final String AMQP_VALUE_SYMBOLIC_NAME = "amqp:amqp-value:*";
+ private static final int AMQP_VALUE_SYMBOLIC_NAME_LENGTH = AMQP_VALUE_SYMBOLIC_NAME.length();
+ private static final byte[] AMQP_VALUE_SYMBOLIC_NAME_BYTES =AMQP_VALUE_SYMBOLIC_NAME.getBytes(StandardCharsets.US_ASCII);
+ private static final Symbol AMQP_VALUE_SYMBOLIC_DESCRIPTOR = Symbol.valueOf(AMQP_VALUE_SYMBOLIC_NAME);
+ private static final UnsignedLong AMQP_VALUE_NUMERIC_DESCRIPTOR = UnsignedLong.valueOf(AMQP_VALUE_NUMERIC_CODE);
+
private static final TypeConstructor[] _constructors = new TypeConstructor[256];
static
@@ -99,19 +116,20 @@ class DataDecoder
{
Data.DataType getType();
- int size(ByteBuffer b);
+ int size(ByteBuffer b, DataImpl data);
- void parse(ByteBuffer b, Data data);
+ void parse(ByteBuffer b, DataImpl data);
}
- static int decode(ByteBuffer b, Data data)
+ static int decode(ByteBuffer b, DataImpl data)
{
if(b.hasRemaining())
{
int position = b.position();
TypeConstructor c = readConstructor(b);
- final int size = c.size(b);
+ final int size = c.size(b, data);
+
if(b.remaining() >= size)
{
c.parse(b, data);
@@ -120,12 +138,44 @@ class DataDecoder
else
{
b.position(position);
- return -4;
+ return -4; // PN_UNDERFLOW
}
}
return 0;
}
+ private static TypeConstructor readDescriptorConstructor(ByteBuffer b)
+ {
+ final byte type = b.get();
+ if (EncodingCodes.SMALLULONG == type || EncodingCodes.ULONG == type || EncodingCodes.SYM8 == type || EncodingCodes.SYM32 == type)
+ {
+ final int index = type & 0xff;
+ TypeConstructor tc = _constructors[index];
+ if(tc == null)
+ {
+ throw new IllegalArgumentException("No constructor for type " + index);
+ }
+
+ return tc;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Reserved non-ulong/non-symbol described-type descriptors not supported by this decoder");
+ }
+ }
+
+ private static TypeConstructor readDescribedConstructor(ByteBuffer buf, boolean isAmqpValue)
+ {
+ final TypeConstructor constructor = readConstructor(buf);
+
+ if (!isAmqpValue && Data.DataType.DESCRIBED == constructor.getType())
+ {
+ throw new IllegalArgumentException("DescribedType value encoding cannot be another DescribedType, must be a primitive type");
+ }
+
+ return constructor;
+ }
+
private static TypeConstructor readConstructor(ByteBuffer b)
{
int index = b.get() & 0xff;
@@ -137,8 +187,7 @@ class DataDecoder
return tc;
}
-
- private static class NullConstructor implements TypeConstructor
+ private static class NullConstructor extends Fixed0SizeConstructor
{
@Override
public Data.DataType getType()
@@ -147,19 +196,13 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
- {
- return 0;
- }
-
- @Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putNull();
}
}
- private static class TrueConstructor implements TypeConstructor
+ private static class TrueConstructor extends Fixed0SizeConstructor
{
@Override
public Data.DataType getType()
@@ -168,20 +211,14 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
- {
- return 0;
- }
-
- @Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putBoolean(true);
}
}
- private static class FalseConstructor implements TypeConstructor
+ private static class FalseConstructor extends Fixed0SizeConstructor
{
@Override
@@ -191,19 +228,13 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
- {
- return 0;
- }
-
- @Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putBoolean(false);
}
}
- private static class UInt0Constructor implements TypeConstructor
+ private static class UInt0Constructor extends Fixed0SizeConstructor
{
@Override
@@ -213,19 +244,13 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
- {
- return 0;
- }
-
- @Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedInteger(UnsignedInteger.ZERO);
}
}
- private static class ULong0Constructor implements TypeConstructor
+ private static class ULong0Constructor extends Fixed0SizeConstructor
{
@Override
@@ -235,20 +260,14 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
- {
- return 0;
- }
-
- @Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedLong(UnsignedLong.ZERO);
}
}
- private static class EmptyListConstructor implements TypeConstructor
+ private static class EmptyListConstructor extends Fixed0SizeConstructor
{
@Override
@@ -258,15 +277,31 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
+ public void parse(ByteBuffer b, DataImpl data)
{
- return 0;
+ data.increaseDecodeDepth();
+ try
+ {
+ data.putList();
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public int size(ByteBuffer b, DataImpl data)
{
- data.putList();
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
}
}
@@ -274,7 +309,7 @@ class DataDecoder
private static abstract class Fixed0SizeConstructor implements TypeConstructor
{
@Override
- public final int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
return 0;
}
@@ -283,7 +318,7 @@ class DataDecoder
private static abstract class Fixed1SizeConstructor implements TypeConstructor
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
return 1;
}
@@ -292,7 +327,7 @@ class DataDecoder
private static abstract class Fixed2SizeConstructor implements TypeConstructor
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
return 2;
}
@@ -301,7 +336,7 @@ class DataDecoder
private static abstract class Fixed4SizeConstructor implements TypeConstructor
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
return 4;
}
@@ -310,7 +345,7 @@ class DataDecoder
private static abstract class Fixed8SizeConstructor implements TypeConstructor
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
return 8;
}
@@ -319,7 +354,7 @@ class DataDecoder
private static abstract class Fixed16SizeConstructor implements TypeConstructor
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
return 16;
}
@@ -335,7 +370,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedByte(UnsignedByte.valueOf(b.get()));
}
@@ -351,7 +386,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putByte(b.get());
}
@@ -367,7 +402,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedInteger(UnsignedInteger.valueOf(((int) b.get()) & 0xff));
}
@@ -383,7 +418,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putInt(b.get());
}
@@ -399,7 +434,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedLong(UnsignedLong.valueOf(((int) b.get()) & 0xff));
}
@@ -415,7 +450,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putLong(b.get());
}
@@ -431,7 +466,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int i = b.get();
if(i != 0 && i != 1)
@@ -452,7 +487,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedShort(UnsignedShort.valueOf(b.getShort()));
}
@@ -468,7 +503,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putShort(b.getShort());
}
@@ -484,7 +519,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedInteger(UnsignedInteger.valueOf(b.getInt()));
}
@@ -500,7 +535,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putInt(b.getInt());
}
@@ -516,7 +551,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putFloat(b.getFloat());
}
@@ -532,7 +567,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putChar(b.getInt());
}
@@ -548,7 +583,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putDecimal32(new Decimal32(b.getInt()));
}
@@ -564,7 +599,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUnsignedLong(UnsignedLong.valueOf(b.getLong()));
}
@@ -580,7 +615,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putLong(b.getLong());
}
@@ -596,7 +631,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putDouble(b.getDouble());
}
@@ -612,7 +647,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putTimestamp(new Date(b.getLong()));
}
@@ -628,7 +663,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putDecimal64(new Decimal64(b.getLong()));
}
@@ -644,7 +679,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putDecimal128(new Decimal128(b.getLong(), b.getLong()));
}
@@ -660,7 +695,7 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putUUID(new UUID(b.getLong(), b.getLong()));
}
@@ -670,15 +705,16 @@ class DataDecoder
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
int position = b.position();
if(b.hasRemaining())
{
int size = b.get() & 0xff;
+ validateTypeSize(size);
b.position(position);
- return size+1;
+ return validateConstructorSize(size+1);
}
else
{
@@ -686,21 +722,23 @@ class DataDecoder
}
}
+ public abstract void validateTypeSize(int size);
}
private static abstract class VariableConstructor implements TypeConstructor
{
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
int position = b.position();
if(b.remaining()>=4)
{
int size = b.getInt();
+ validateTypeSize(size);
b.position(position);
- return size+4;
+ return validateConstructorSize(size+4);
}
else
{
@@ -708,11 +746,12 @@ class DataDecoder
}
}
+ public abstract void validateTypeSize(int size);
}
-
private static class SmallBinaryConstructor extends SmallVariableConstructor
{
+ private static final String TYPE = "Binary";
@Override
public Data.DataType getType()
@@ -721,17 +760,24 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int size = b.get() & 0xff;
+ DataValidationSupport.validateSize(TYPE, size, b.remaining());
byte[] bytes = new byte[size];
b.get(bytes);
data.putBinary(bytes);
}
+
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
+ }
}
private static class SmallSymbolConstructor extends SmallVariableConstructor
{
+ private static final String TYPE = "Symbol";
@Override
public Data.DataType getType()
@@ -740,18 +786,25 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int size = b.get() & 0xff;
+ DataValidationSupport.validateSize(TYPE, size, b.remaining());
byte[] bytes = new byte[size];
b.get(bytes);
data.putSymbol(Symbol.valueOf(new String(bytes, ASCII)));
}
+
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
+ }
}
private static class SmallStringConstructor extends SmallVariableConstructor
{
+ private static final String TYPE = "String";
@Override
public Data.DataType getType()
@@ -760,17 +813,24 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int size = b.get() & 0xff;
+ DataValidationSupport.validateSize(TYPE, size, b.remaining());
byte[] bytes = new byte[size];
b.get(bytes);
data.putString(new String(bytes, UTF_8));
}
+
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
+ }
}
private static class BinaryConstructor extends VariableConstructor
{
+ private static final String TYPE = "Binary";
@Override
public Data.DataType getType()
@@ -779,17 +839,24 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int size = b.getInt();
+ DataValidationSupport.validateSize(TYPE, size, b.remaining());
byte[] bytes = new byte[size];
b.get(bytes);
data.putBinary(bytes);
}
+
+ @Override
+ public void validateTypeSize(int size) {
+ DataValidationSupport.validateSizeInitial(TYPE, size);
+ }
}
private static class SymbolConstructor extends VariableConstructor
{
+ private static final String TYPE = "Symbol";
@Override
public Data.DataType getType()
@@ -798,18 +865,25 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int size = b.getInt();
+ DataValidationSupport.validateSize(TYPE, size, b.remaining());
byte[] bytes = new byte[size];
b.get(bytes);
data.putSymbol(Symbol.valueOf(new String(bytes, ASCII)));
}
+
+ @Override
+ public void validateTypeSize(int size) {
+ DataValidationSupport.validateSizeInitial(TYPE, size);
+ }
}
private static class StringConstructor extends VariableConstructor
{
+ private static final String TYPE = "String";
@Override
public Data.DataType getType()
@@ -818,18 +892,25 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
int size = b.getInt();
+ DataValidationSupport.validateSize(TYPE, size, b.remaining());
byte[] bytes = new byte[size];
b.get(bytes);
data.putString(new String(bytes, UTF_8));
}
+
+ @Override
+ public void validateTypeSize(int size) {
+ DataValidationSupport.validateSizeInitial(TYPE, size);
+ }
}
private static class SmallListConstructor extends SmallVariableConstructor
{
+ private static final String TYPE = "List";
@Override
public Data.DataType getType()
@@ -838,21 +919,51 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
- int size = b.get() & 0xff;
- ByteBuffer buf = b.slice();
- buf.limit(size);
- b.position(b.position()+size);
- int count = buf.get() & 0xff;
- data.putList();
- parseChildren(data, buf, count);
+ data.increaseDecodeDepth();
+ try
+ {
+ int size = b.get() & 0xff;
+ validateListSize(size, b.remaining());
+ ByteBuffer buf = b.slice();
+ buf.limit(size);
+ b.position(b.position()+size);
+ int count = buf.get() & 0xff;
+ validateListCount(size, count, buf.remaining());
+ data.putList();
+ parseChildren(data, buf, count, TYPE, size);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public int size(ByteBuffer b, DataImpl data)
+ {
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
}
}
private static class SmallMapConstructor extends SmallVariableConstructor
{
+ private static final String TYPE = "Map";
@Override
public Data.DataType getType()
@@ -861,21 +972,51 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
- int size = b.get() & 0xff;
- ByteBuffer buf = b.slice();
- buf.limit(size);
- b.position(b.position()+size);
- int count = buf.get() & 0xff;
- data.putMap();
- parseChildren(data, buf, count);
+ data.increaseDecodeDepth();
+ try
+ {
+ int size = b.get() & 0xff;
+ validateMapSize(size, b.remaining());
+ ByteBuffer buf = b.slice();
+ buf.limit(size);
+ b.position(b.position()+size);
+ int count = buf.get() & 0xff;
+ validateMapCount(size, count, buf.remaining());
+ data.putMap();
+ parseChildren(data, buf, count, TYPE, size);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public int size(ByteBuffer b, DataImpl data)
+ {
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
}
}
private static class ListConstructor extends VariableConstructor
{
+ private static final String TYPE = "List";
@Override
public Data.DataType getType()
@@ -884,21 +1025,51 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
- int size = b.getInt();
- ByteBuffer buf = b.slice();
- buf.limit(size);
- b.position(b.position()+size);
- int count = buf.getInt();
- data.putList();
- parseChildren(data, buf, count);
+ data.increaseDecodeDepth();
+ try
+ {
+ int size = b.getInt();
+ validateListSize(size, b.remaining());
+ ByteBuffer buf = b.slice();
+ buf.limit(size);
+ b.position(b.position()+size);
+ int count = buf.getInt();
+ validateListCount(size, count, buf.remaining());
+ data.putList();
+ parseChildren(data, buf, count, TYPE, size);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public int size(ByteBuffer b, DataImpl data)
+ {
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public void validateTypeSize(int size) {
+ DataValidationSupport.validateSizeInitial(TYPE, size);
}
}
private static class MapConstructor extends VariableConstructor
{
+ private static final String TYPE = "Map";
@Override
public Data.DataType getType()
@@ -907,26 +1078,55 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
- int size = b.getInt();
- ByteBuffer buf = b.slice();
- buf.limit(size);
- b.position(b.position()+size);
- int count = buf.getInt();
- data.putMap();
- parseChildren(data, buf, count);
+ data.increaseDecodeDepth();
+ try
+ {
+ int size = b.getInt();
+ validateMapSize(size, b.remaining());
+ ByteBuffer buf = b.slice();
+ buf.limit(size);
+ b.position(b.position()+size);
+ int count = buf.getInt();
+ validateMapCount(size, count, buf.remaining());
+ data.putMap();
+ parseChildren(data, buf, count, TYPE, size);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public int size(ByteBuffer b, DataImpl data)
+ {
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public void validateTypeSize(int size) {
+ DataValidationSupport.validateSizeInitial(TYPE, size);
}
}
- private static void parseChildren(Data data, ByteBuffer buf, int count)
+ private static void parseChildren(DataImpl data, ByteBuffer buf, int count, String type, int parentSize)
{
data.enter();
for(int i = 0; i < count; i++)
{
TypeConstructor c = readConstructor(buf);
- final int size = c.size(buf);
+ final int size = c.size(buf, data);
final int remaining = buf.remaining();
if(size <= remaining)
{
@@ -936,14 +1136,18 @@ class DataDecoder
{
throw new IllegalArgumentException("Malformed data");
}
+ }
+ if (buf.position() != parentSize)
+ {
+ throw new IllegalArgumentException(type + " incorrectly encoded, left " + (buf.remaining()) + " bytes from size indicated " + parentSize);
}
+
data.exit();
}
private static class DescribedTypeConstructor implements TypeConstructor
{
-
@Override
public Data.DataType getType()
{
@@ -951,46 +1155,97 @@ class DataDecoder
}
@Override
- public int size(ByteBuffer b)
+ public int size(ByteBuffer b, DataImpl data)
{
ByteBuffer buf = b.slice();
if(buf.hasRemaining())
{
- TypeConstructor c = readConstructor(buf);
- int size = c.size(buf);
- if(buf.remaining()>size)
+ TypeConstructor c = readDescriptorConstructor(buf);
+ int descriptorConstructorSize = c.size(buf, data);
+ long dtConstructorSize = 1L + descriptorConstructorSize; // 1 more for the type code in readDescriptorConstructor
+
+ if(buf.remaining() > descriptorConstructorSize)
{
- buf.position(size + 1);
- c = readConstructor(buf);
- return size + 2 + c.size(buf);
+ buf.position(buf.position() + descriptorConstructorSize);
+
+ final ByteBuffer descriptorCheckBuf = b.slice(); // slice of original buffer b, not buf!
+ final boolean isAmqpValue = isAmqpValueDescriptor(descriptorCheckBuf);
+
+ c = readDescribedConstructor(buf, isAmqpValue);
+
+ final boolean countOwnDepth = isAmqpValue && Data.DataType.DESCRIBED.equals(c.getType());
+ if (countOwnDepth)
+ {
+ data.increaseDecodeDepth();
+ }
+ try
+ {
+ int describedConstructorSize = c.size(buf, data);
+ dtConstructorSize += 1L + describedConstructorSize; // 1 more for the type code in readDescribedConstructor
+ }
+ finally
+ {
+ if (countOwnDepth)
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
}
else
{
- return size + 2;
+ dtConstructorSize += 1; // another 1 more for the described constructor type code not yet read
+ }
+
+ if (dtConstructorSize > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("DescribedTypeConstructor total size cannot exceed " + Integer.MAX_VALUE + " but was: " + dtConstructorSize);
}
+
+ return validateConstructorSize((int) dtConstructorSize);
}
else
{
return 1;
}
-
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
data.putDescribed();
data.enter();
- TypeConstructor c = readConstructor(b);
- c.parse(b, data);
- c = readConstructor(b);
+
+ TypeConstructor c = readDescriptorConstructor(b);
+
c.parse(b, data);
- data.exit();
+
+ Object current = data.getObject();
+ boolean isAmqpValue = AMQP_VALUE_NUMERIC_DESCRIPTOR.equals(current) || AMQP_VALUE_SYMBOLIC_DESCRIPTOR.equals(current);
+
+ c = readDescribedConstructor(b, isAmqpValue);
+
+ boolean countOwnDepth = isAmqpValue && Data.DataType.DESCRIBED.equals(c.getType());
+ if (countOwnDepth)
+ {
+ data.increaseDecodeDepth();
+ }
+ try
+ {
+ c.parse(b, data);
+ data.exit();
+ }
+ finally
+ {
+ if (countOwnDepth)
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
}
}
private static class SmallArrayConstructor extends SmallVariableConstructor
{
+ private static final String TYPE = "Array";
@Override
public Data.DataType getType()
@@ -999,21 +1254,50 @@ class DataDecoder
}
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
{
+ data.increaseDecodeDepth();
+ try
+ {
+ int size = b.get() & 0xff;
+ validateArraySizeInitial(size, b.remaining());
+ ByteBuffer buf = b.slice();
+ buf.limit(size);
+ b.position(b.position()+size);
+ int count = buf.get() & 0xff;
+ validateArrayCountInitial(count);
+
+ parseArray(data, buf, count, size);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
- int size = b.get() & 0xff;
- ByteBuffer buf = b.slice();
- buf.limit(size);
- b.position(b.position()+size);
- int count = buf.get() & 0xff;
- parseArray(data, buf, count);
+ @Override
+ public int size(ByteBuffer b, DataImpl data)
+ {
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
}
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
+ }
}
private static class ArrayConstructor extends VariableConstructor
{
+ private static final String TYPE = "Array";
@Override
public Data.DataType getType()
@@ -1023,52 +1307,198 @@ class DataDecoder
@Override
- public void parse(ByteBuffer b, Data data)
+ public void parse(ByteBuffer b, DataImpl data)
+ {
+ data.increaseDecodeDepth();
+ try
+ {
+ int size = b.getInt();
+ validateArraySizeInitial(size, b.remaining());
+ ByteBuffer buf = b.slice();
+ buf.limit(size);
+ b.position(b.position()+size);
+ int count = buf.getInt();
+ validateArrayCountInitial(count);
+
+ parseArray(data, buf, count, size);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
+
+ @Override
+ public int size(ByteBuffer b, DataImpl data)
{
+ data.increaseDecodeDepth();
+ try
+ {
+ return super.size(b, data);
+ }
+ finally
+ {
+ data.decreaseDecodeDepth();
+ }
+ }
- int size = b.getInt();
- ByteBuffer buf = b.slice();
- buf.limit(size);
- b.position(b.position()+size);
- int count = buf.getInt();
- parseArray(data, buf, count);
+ @Override
+ public void validateTypeSize(int size) {
+ validateSizeInitial(TYPE, size);
}
}
- private static void parseArray(Data data, ByteBuffer buf, int count)
+ private static void parseArray(DataImpl data, ByteBuffer buf, int count, int size)
{
+ // element constructor
byte type = buf.get();
- boolean isDescribed = type == (byte)0x00;
+ boolean isDescribed = isTypeCodeDescribedTypeIndicator(type);
int descriptorPosition = buf.position();
if(isDescribed)
{
- TypeConstructor descriptorTc = readConstructor(buf);
- buf.position(buf.position()+descriptorTc.size(buf));
+ TypeConstructor descriptorTc = readDescriptorConstructor(buf);
+ buf.position(buf.position() + descriptorTc.size(buf, data));
type = buf.get();
- if(type == (byte)0x00)
+ if(isTypeCodeDescribedTypeIndicator(type))
{
- throw new IllegalArgumentException("Malformed array data");
+ throw new IllegalArgumentException("DescribedType value encoding cannot be another DescribedType, must be a primitive type");
}
-
}
- TypeConstructor tc = _constructors[type&0xff];
+ // read array / elements
+ TypeConstructor tc = _constructors[type&0xff];
+ checkArraySizeCountConstructor(data, buf.remaining(), tc, count, size);
data.putArray(isDescribed, tc.getType());
data.enter();
if(isDescribed)
{
int position = buf.position();
buf.position(descriptorPosition);
- TypeConstructor descriptorTc = readConstructor(buf);
- descriptorTc.parse(buf,data);
+ TypeConstructor descriptorTc = readDescriptorConstructor(buf);
+ descriptorTc.parse(buf, data);
buf.position(position);
}
for(int i = 0; i<count; i++)
{
- tc.parse(buf,data);
+ tc.parse(buf, data);
+ }
+
+ if (buf.position() != size)
+ {
+ throw new IllegalArgumentException("Array incorrectly encoded, left " + (buf.remaining()) + " bytes from size indicated " + size);
}
data.exit();
}
+ private static void validateArraySizeInitial(int size, int remaining)
+ {
+ if (size < 0)
+ {
+ throw new IllegalArgumentException("Array size " + size + " cannot be negative");
+ }
+
+ if (size > remaining)
+ {
+ throw new IllegalArgumentException("Array size " + size + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+
+ private static void validateArrayCountInitial(int count)
+ {
+ if (count < 0)
+ {
+ throw new IllegalArgumentException("Array element count " + count + " cannot be negative");
+ }
+ }
+
+ private static void checkArraySizeCountConstructor(Data data, int remaining, final TypeConstructor constructor, final int count, final int size)
+ {
+ if(constructor instanceof Fixed0SizeConstructor)
+ {
+ if (count > data.getZeroWidthElementLimit())
+ {
+ throw new IllegalArgumentException("Array element count " + count + " is specified to be greater than the allowed 0-width-element limit for arrays ("
+ + data.getZeroWidthElementLimit() + ")");
+ }
+ else
+ {
+ // Remaining checks are not valid for these cases, return
+ return;
+ }
+ }
+
+ if (count > size)
+ {
+ throw new IllegalArgumentException("Array element count " + count + " is specified to be greater than the size of array (" + size + ")");
+ }
+
+ if (count > remaining)
+ {
+ throw new IllegalArgumentException("Array element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+ }
+ }
+
+ private static boolean isTypeCodeDescribedTypeIndicator(byte type)
+ {
+ return type == (byte)0x00;
+ }
+
+ private static boolean isAmqpValueDescriptor(ByteBuffer buf)
+ {
+ if(buf.hasRemaining())
+ {
+ byte type = buf.get();
+ switch (type) {
+ case EncodingCodes.SMALLULONG:
+ if(buf.hasRemaining())
+ {
+ byte value = buf.get();
+
+ return value == AMQP_VALUE_NUMERIC_CODE;
+ }
+ break;
+ case EncodingCodes.ULONG:
+ if (buf.remaining() >= 8)
+ {
+ long value = buf.getLong();
+
+ return value == AMQP_VALUE_NUMERIC_CODE;
+ }
+ break;
+ case EncodingCodes.SYM8:
+ if(buf.hasRemaining())
+ {
+ int length = buf.get() & 0xFF;
+
+ return checkSymbolContentIsAmqpValue(buf, length);
+ }
+ break;
+ case EncodingCodes.SYM32:
+ if (buf.remaining() >= 4)
+ {
+ int length = buf.getInt();
+ validateSizeInitial("Symbol", length);
+
+ return checkSymbolContentIsAmqpValue(buf, length);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Reserved non-ulong/non-symbol described-type descriptors not supported by this decoder");
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean checkSymbolContentIsAmqpValue(ByteBuffer buf, int length) {
+ if (length == AMQP_VALUE_SYMBOLIC_NAME_LENGTH && length <= buf.remaining()) {
+ byte[] bytes = new byte[AMQP_VALUE_SYMBOLIC_NAME_LENGTH];
+ buf.get(bytes);
+
+ return Arrays.equals(AMQP_VALUE_SYMBOLIC_NAME_BYTES, bytes);
+ }
+
+ return false;
+ }
}
\ No newline at end of file
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java
index 5c397297..509affb1 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java
@@ -30,11 +30,15 @@ import org.apache.qpid.proton.codec.Data;
public class DataImpl implements Data
{
+ private static final int DEFAULT_MAX_DECODE_DEPTH = 50;
private Element _first;
private Element _current;
private Element _parent;
+ private int zeroWidthElementLimit = 0;
+ private int maxDecodeDepth = DEFAULT_MAX_DECODE_DEPTH;
+ private int decodeDepth = 0;
public DataImpl()
{
@@ -867,4 +871,49 @@ public class DataImpl implements Data
System.identityHashCode(_parent),
sb);
}
+
+ @Override
+ public int getZeroWidthElementLimit() {
+ return zeroWidthElementLimit;
+ }
+
+ @Override
+ public void setZeroWidthElementLimit(int zeroWidthElementLimit) {
+ this.zeroWidthElementLimit = zeroWidthElementLimit;
+ }
+
+ /**
+ * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload.
+ *
+ * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+ */
+ public int getMaxDecodeDepth() {
+ return maxDecodeDepth;
+ }
+
+ /**
+ * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload. Default is 50.
+ *
+ * It would be unusual to set this to less than 3 due to nesting of types that make up
+ * the base protocol itself.
+ */
+ public void setMaxDecodeDepth(int maxDecodeDepth) {
+ this.maxDecodeDepth = maxDecodeDepth;
+ }
+
+ void increaseDecodeDepth() {
+ if (++decodeDepth > maxDecodeDepth) {
+ --decodeDepth; // Reset so the unwind goes back to zero
+ throw new IllegalArgumentException("The nesting of types in the object being decoded exceeded the configured limit: " + maxDecodeDepth);
+ }
+ }
+
+ void decreaseDecodeDepth() {
+ if(--decodeDepth < 0) {
+ ++decodeDepth; // Reset so the unwind goes back to zero
+ throw new IllegalStateException("Decode depth cannot go negative");
+ }
+ }
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataValidationSupport.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataValidationSupport.java
new file mode 100644
index 00000000..b37a757c
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataValidationSupport.java
@@ -0,0 +1,128 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.proton.codec.impl;
+
+public class DataValidationSupport {
+
+ private DataValidationSupport()
+ {
+ // No instances
+ }
+
+ public static void validateMapSize(final int size, final int remaining)
+ {
+ if (size < 0)
+ {
+ throw new IllegalArgumentException("Map size " + size + " cannot be negative");
+ }
+
+ if (size > remaining)
+ {
+ throw new IllegalArgumentException("Map size " + size + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+
+ public static void validateMapCount(final int size, final int count, final int remaining)
+ {
+ if (count < 0)
+ {
+ throw new IllegalArgumentException("Map element count " + count + " cannot be negative");
+ }
+
+ if (count >= size)
+ {
+ throw new IllegalArgumentException("Map element count " + count + " must be less than size (" + size + ")");
+ }
+
+ if (count > remaining)
+ {
+ throw new IllegalArgumentException("Map element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+ }
+
+ if (count % 2 != 0)
+ {
+ throw new IllegalArgumentException("Map element count " + count + " cannot be odd");
+ }
+ }
+
+
+ public static void validateListSize(final int size, final int remaining)
+ {
+ if (size < 0)
+ {
+ throw new IllegalArgumentException("List size " + size + " cannot be negative");
+ }
+
+ if (size > remaining)
+ {
+ throw new IllegalArgumentException("List size " + size + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+
+ public static void validateListCount(final int size, final int count, final int remaining)
+ {
+ if (count < 0)
+ {
+ throw new IllegalArgumentException("List element count " + count + " cannot be negative");
+ }
+
+ if (count >= size)
+ {
+ throw new IllegalArgumentException("List element count " + count + " must be less than size (" + size + ")");
+ }
+
+ if (count > remaining)
+ {
+ throw new IllegalArgumentException("List element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+ }
+ }
+
+ public static void validateSize(final String type, final int size, final int remaining)
+ {
+ if (size < 0)
+ {
+ throw new IllegalArgumentException(type + " length " + size + " cannot be negative");
+ }
+
+ if (size > remaining)
+ {
+ throw new IllegalArgumentException(type + " length " + size + " cannot be more than remaining bytes " + remaining);
+ }
+ }
+
+ public static void validateSizeInitial(final String type, final int size)
+ {
+ if (size < 0)
+ {
+ throw new IllegalArgumentException(type + " size " + size + " cannot be negative");
+ }
+ }
+
+ public static int validateConstructorSize(final int size)
+ {
+ if (size < 0)
+ {
+ throw new IllegalArgumentException("constructor size cannot be negative: " + size);
+ }
+
+ return size;
+ }
+}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java
index 9e5e96db..a29c9d0b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java
@@ -89,9 +89,12 @@ public class FastPathAcceptedType implements AMQPType<Accepted>, FastPathDescrib
@Override
public Accepted readValue() {
DecoderImpl decoder = getDecoder();
- byte typeCode = decoder.getBuffer().get();
+ decoder.increaseDecodeDepth();
- switch (typeCode) {
+ try {
+ byte typeCode = decoder.getBuffer().get();
+
+ switch (typeCode) {
case EncodingCodes.LIST0:
break;
case EncodingCodes.LIST8:
@@ -104,14 +107,25 @@ public class FastPathAcceptedType implements AMQPType<Accepted>, FastPathDescrib
break;
default:
throw new DecodeException("Incorrect type found in Accepted type encoding: " + typeCode);
- }
+ }
- return Accepted.getInstance();
+ return Accepted.getInstance();
+ } finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java
index 6a4ebd4e..6f2aad85 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java
@@ -80,12 +80,21 @@ public class FastPathAmqpSequenceType implements AMQPType<AmqpSequence>, FastPat
@Override
public AmqpSequence readValue() {
+ // Depth handled by list type readValue
return new AmqpSequence(getDecoder().readList());
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java
index b9d4ee78..74c28f6b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java
@@ -27,6 +27,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.EncodingCodes;
import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.codec.TypeEncoding;
import org.apache.qpid.proton.codec.WritableBuffer;
@@ -80,12 +81,36 @@ public class FastPathAmqpValueType implements AMQPType<AmqpValue>, FastPathDescr
@Override
public AmqpValue readValue() {
- return new AmqpValue(getDecoder().readObject());
+ final DecoderImpl decoder = getDecoder();
+ final ReadableBuffer buffer = decoder.getBuffer();
+
+ // Peeks ahead, doesnt read the byte
+ final byte nestedEncodingCode = buffer.get(buffer.position());
+ final boolean countOwnDepth = nestedEncodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR;
+
+ if(countOwnDepth) {
+ decoder.increaseDecodeDepth();
+ }
+ try {
+ return new AmqpValue(decoder.readObject());
+ } finally {
+ if(countOwnDepth) {
+ decoder.decreaseDecodeDepth();
+ }
+ }
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java
index 82ed7a22..0622027e 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.messaging;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -94,68 +97,96 @@ public class FastPathApplicationPropertiesType implements AMQPType<ApplicationPr
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
- final int size;
- final int count;
-
- byte encodingCode = buffer.get();
-
- switch (encodingCode) {
- case EncodingCodes.MAP8:
- size = buffer.get() & 0xFF;
- count = buffer.get() & 0xFF;
- break;
- case EncodingCodes.MAP32:
- size = buffer.getInt();
- count = buffer.getInt();
- break;
- case EncodingCodes.NULL:
- return new ApplicationProperties(null);
- default:
- throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
- }
+ decoder.increaseDecodeDepth();
+ try {
+ final int size;
+ final int count;
+
+ byte encodingCode = buffer.get();
+
+ final int oldLimit = buffer.limit();
+ final int newLimit;
+
+ switch (encodingCode) {
+ case EncodingCodes.MAP8:
+ size = buffer.get() & 0xFF;
+ validateMapSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.get() & 0xFF;
+ validateMapCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.MAP32:
+ size = buffer.getInt();
+ validateMapSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateMapCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.NULL:
+ return new ApplicationProperties(null);
+ default:
+ throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
+ }
- if (count > buffer.remaining()) {
- throw new IllegalArgumentException("Map element count " + count + " is specified to be greater than the " +
- "amount of data available ("+ buffer.remaining() + ")");
- }
+ final Map<String, Object> map;
+ try {
+ buffer.limit(newLimit);
- TypeConstructor<?> valueConstructor = null;
+ TypeConstructor<?> valueConstructor = null;
- Map<String, Object> map = new LinkedHashMap<>(count);
- for (int i = 0; i < count / 2; i++) {
- String key = decoder.readString(null);
- if (key == null) {
- throw new DecodeException("String key in ApplicationProperties cannot be null");
- }
+ map = new LinkedHashMap<>(count);
+ for (int i = 0; i < count / 2; i++) {
+ String key = decoder.readString(null);
+ if (key == null) {
+ throw new DecodeException("String key in ApplicationProperties cannot be null");
+ }
- boolean arrayType = false;
- byte code = buffer.get(buffer.position());
- switch (code)
- {
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
- }
+ boolean arrayType = false;
+ byte code = buffer.get(buffer.position());
+ switch (code)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
- valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+ valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
- final Object value;
+ final Object value;
- if (arrayType) {
- value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
- } else {
- value = valueConstructor.readValue();
+ if (arrayType) {
+ value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ } else {
+ value = valueConstructor.readValue();
+ }
+
+ map.put(key, value);
+ }
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("ApplicationProperties incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+ } finally {
+ buffer.limit(oldLimit);
}
- map.put(key, value);
+ return new ApplicationProperties(map);
+ } finally {
+ decoder.decreaseDecodeDepth();
}
-
- return new ApplicationProperties(map);
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java
index acc6d77d..06bc268f 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java
@@ -83,6 +83,7 @@ public class FastPathDataType implements AMQPType<Data>, FastPathDescribedTypeCo
@Override
public Data readValue() {
+ // No depth as this is a [described] binary primitive read with fixed decoding
ReadableBuffer buffer = getDecoder().getBuffer();
byte encodingCode = buffer.get();
@@ -114,7 +115,15 @@ public class FastPathDataType implements AMQPType<Data>, FastPathDescribedTypeCo
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java
index 778f30e4..08abb496 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.messaging;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -94,68 +97,95 @@ public class FastPathDeliveryAnnotationsType implements AMQPType<DeliveryAnnotat
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
- final int size;
- final int count;
-
- byte encodingCode = buffer.get();
-
- switch (encodingCode) {
- case EncodingCodes.MAP8:
- size = buffer.get() & 0xFF;
- count = buffer.get() & 0xFF;
- break;
- case EncodingCodes.MAP32:
- size = buffer.getInt();
- count = buffer.getInt();
- break;
- case EncodingCodes.NULL:
- return new DeliveryAnnotations(null);
- default:
- throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
- }
-
- if (count > buffer.remaining()) {
- throw new IllegalArgumentException("Map element count " + count + " is specified to be greater than the " +
- "amount of data available (" + buffer.remaining() + ")");
- }
-
- TypeConstructor<?> valueConstructor = null;
-
- Map<Symbol, Object> map = new LinkedHashMap<>(count);
- for(int i = 0; i < count / 2; i++) {
- Symbol key = decoder.readSymbol(null);
- if (key == null) {
- throw new DecodeException("String key in DeliveryAnnotations cannot be null");
- }
-
- boolean arrayType = false;
- byte code = buffer.get(buffer.position());
- switch (code)
- {
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
+ decoder.increaseDecodeDepth();
+ try {
+ final int size;
+ final int count;
+
+ byte encodingCode = buffer.get();
+
+ final int oldLimit = buffer.limit();
+ final int newLimit;
+
+ switch (encodingCode) {
+ case EncodingCodes.MAP8:
+ size = buffer.get() & 0xFF;
+ validateMapSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.get() & 0xFF;
+ validateMapCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.MAP32:
+ size = buffer.getInt();
+ validateMapSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateMapCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.NULL:
+ return new DeliveryAnnotations(null);
+ default:
+ throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
}
- valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
-
- final Object value;
+ final Map<Symbol, Object> map;
+ try {
+ buffer.limit(newLimit);
+ TypeConstructor<?> valueConstructor = null;
+
+ map = new LinkedHashMap<>(count);
+ for(int i = 0; i < count / 2; i++) {
+ Symbol key = decoder.readSymbol(null);
+ if (key == null) {
+ throw new DecodeException("Symbol key in DeliveryAnnotations cannot be null");
+ }
+
+ boolean arrayType = false;
+ byte code = buffer.get(buffer.position());
+ switch (code)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
+
+ valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+
+ final Object value;
+
+ if (arrayType) {
+ value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ } else {
+ value = valueConstructor.readValue();
+ }
+
+ map.put(key, value);
+ }
- if (arrayType) {
- value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
- } else {
- value = valueConstructor.readValue();
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("DeliveryAnnotations incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+ } finally {
+ buffer.limit(oldLimit);
}
- map.put(key, value);
+ return new DeliveryAnnotations(map);
+ } finally {
+ decoder.decreaseDecodeDepth();
}
-
- return new DeliveryAnnotations(map);
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java
index 88f690f4..e46e6ae4 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java
@@ -81,12 +81,21 @@ public class FastPathFooterType implements AMQPType<Footer>, FastPathDescribedTy
@Override
public Footer readValue() {
+ // Depth handled by map type readValue
return new Footer(getDecoder().readMap());
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java
index b60d53e5..f46718fd 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.messaging;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
import java.util.Collection;
import org.apache.qpid.proton.amqp.Symbol;
@@ -59,57 +62,89 @@ public class FastPathHeaderType implements AMQPType<Header>, FastPathDescribedTy
public Header readValue() {
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
- byte typeCode = decoder.getBuffer().get();
- @SuppressWarnings("unused")
- int size = 0;
- int count = 0;
+ decoder.increaseDecodeDepth();
+ try {
+ byte typeCode = decoder.getBuffer().get();
- switch (typeCode) {
- case EncodingCodes.LIST0:
- break;
- case EncodingCodes.LIST8:
- size = buffer.get() & 0xff;
- count = buffer.get() & 0xff;
- break;
- case EncodingCodes.LIST32:
- size = buffer.getInt();
- count = buffer.getInt();
- break;
- default:
- throw new DecodeException("Incorrect type found in Header encoding: " + typeCode);
- }
+ int size = 0;
+ int count = 0;
- Header header = new Header();
+ final int oldLimit = buffer.limit();
+ final int newLimit;
- for (int index = 0; index < count; ++index) {
- switch (index) {
- case 0:
- header.setDurable(decoder.readBoolean(null));
- break;
- case 1:
- header.setPriority(decoder.readUnsignedByte(null));
- break;
- case 2:
- header.setTtl(decoder.readUnsignedInteger(null));
+ switch (typeCode) {
+ case EncodingCodes.LIST0:
+ newLimit = buffer.position();
break;
- case 3:
- header.setFirstAcquirer(decoder.readBoolean(null));
+ case EncodingCodes.LIST8:
+ size = buffer.get() & 0xff;
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.get() & 0xff;
+ validateListCount(size, count, buffer.remaining());
break;
- case 4:
- header.setDeliveryCount(decoder.readUnsignedInteger(null));
+ case EncodingCodes.LIST32:
+ size = buffer.getInt();
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateListCount(size, count, buffer.remaining());
break;
default:
- throw new IllegalStateException("To many entries in Header encoding");
+ throw new DecodeException("Incorrect type found in Header encoding: " + typeCode);
+ }
+
+ Header header = new Header();
+ try {
+ buffer.limit(newLimit);
+
+ for (int index = 0; index < count; ++index) {
+ switch (index) {
+ case 0:
+ header.setDurable(decoder.readBoolean(null));
+ break;
+ case 1:
+ header.setPriority(decoder.readUnsignedByte(null));
+ break;
+ case 2:
+ header.setTtl(decoder.readUnsignedInteger(null));
+ break;
+ case 3:
+ header.setFirstAcquirer(decoder.readBoolean(null));
+ break;
+ case 4:
+ header.setDeliveryCount(decoder.readUnsignedInteger(null));
+ break;
+ default:
+ throw new IllegalStateException("To many entries in Header encoding");
+ }
+ }
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("Header incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+ } finally {
+ buffer.limit(oldLimit);
}
- }
- return header;
+ return header;
+ } finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java
index d9148333..15b59bfe 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.messaging;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -94,68 +97,95 @@ public class FastPathMessageAnnotationsType implements AMQPType<MessageAnnotatio
DecoderImpl decoder = getDecoder();
ReadableBuffer buffer = decoder.getBuffer();
- final int size;
- final int count;
-
- byte encodingCode = buffer.get();
-
- switch (encodingCode) {
- case EncodingCodes.MAP8:
- size = buffer.get() & 0xFF;
- count = buffer.get() & 0xFF;
- break;
- case EncodingCodes.MAP32:
- size = buffer.getInt();
- count = buffer.getInt();
- break;
- case EncodingCodes.NULL:
- return new MessageAnnotations(null);
- default:
- throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
- }
-
- if (count > buffer.remaining()) {
- throw new IllegalArgumentException("Map element count "+count+" is specified to be greater than the amount of data available ("+
- decoder.getByteBufferRemaining()+")");
- }
-
- TypeConstructor<?> valueConstructor = null;
-
- Map<Symbol, Object> map = new LinkedHashMap<>(count);
- for(int i = 0; i < count / 2; i++) {
- Symbol key = decoder.readSymbol(null);
- if (key == null) {
- throw new DecodeException("String key in DeliveryAnnotations cannot be null");
- }
-
- boolean arrayType = false;
- byte code = buffer.get(buffer.position());
- switch (code)
- {
- case EncodingCodes.ARRAY8:
- case EncodingCodes.ARRAY32:
- arrayType = true;
+ decoder.increaseDecodeDepth();
+ try {
+ final int size;
+ final int count;
+
+ byte encodingCode = buffer.get();
+
+ final int oldLimit = buffer.limit();
+ final int newLimit;
+
+ switch (encodingCode) {
+ case EncodingCodes.MAP8:
+ size = buffer.get() & 0xFF;
+ validateMapSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.get() & 0xFF;
+ validateMapCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.MAP32:
+ size = buffer.getInt();
+ validateMapSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateMapCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.NULL:
+ return new MessageAnnotations(null);
+ default:
+ throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
}
- valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
-
- final Object value;
+ final Map<Symbol, Object> map;
+ try {
+ buffer.limit(newLimit);
+ TypeConstructor<?> valueConstructor = null;
+
+ map = new LinkedHashMap<>(count);
+ for(int i = 0; i < count / 2; i++) {
+ Symbol key = decoder.readSymbol(null);
+ if (key == null) {
+ throw new DecodeException("Symbol key in MessageAnnotations cannot be null");
+ }
+
+ boolean arrayType = false;
+ byte code = buffer.get(buffer.position());
+ switch (code)
+ {
+ case EncodingCodes.ARRAY8:
+ case EncodingCodes.ARRAY32:
+ arrayType = true;
+ }
+
+ valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+
+ final Object value;
+
+ if (arrayType) {
+ value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+ } else {
+ value = valueConstructor.readValue();
+ }
+
+ map.put(key, value);
+ }
- if (arrayType) {
- value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
- } else {
- value = valueConstructor.readValue();
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("MessageAnnotations incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+ } finally {
+ buffer.limit(oldLimit);
}
- map.put(key, value);
+ return new MessageAnnotations(map);
+ } finally {
+ decoder.decreaseDecodeDepth();
}
-
- return new MessageAnnotations(map);
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java
index 22b30d14..b72e302e 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.messaging;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
import java.util.Collection;
import org.apache.qpid.proton.amqp.Symbol;
@@ -61,79 +64,106 @@ public class FastPathPropertiesType implements AMQPType<Properties>, FastPathDes
ReadableBuffer buffer = decoder.getBuffer();
byte typeCode = decoder.getBuffer().get();
- @SuppressWarnings("unused")
int size = 0;
int count = 0;
+ final int oldLimit = buffer.limit();
+ final int newLimit;
+
switch (typeCode) {
case EncodingCodes.LIST0:
+ newLimit = buffer.position();
break;
case EncodingCodes.LIST8:
size = buffer.get() & 0xff;
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
count = buffer.get() & 0xff;
+ validateListCount(size, count, buffer.remaining());
break;
case EncodingCodes.LIST32:
size = buffer.getInt();
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
count = buffer.getInt();
+ validateListCount(size, count, buffer.remaining());
break;
default:
throw new DecodeException("Incorrect type found in Properties encoding: " + typeCode);
}
- Properties properties = new Properties();
-
- for (int index = 0; index < count; ++index) {
- switch (index) {
- case 0:
- properties.setMessageId(decoder.readObject());
- break;
- case 1:
- properties.setUserId(decoder.readBinary(null));
- break;
- case 2:
- properties.setTo(decoder.readString(null));
- break;
- case 3:
- properties.setSubject(decoder.readString(null));
- break;
- case 4:
- properties.setReplyTo(decoder.readString(null));
- break;
- case 5:
- properties.setCorrelationId(decoder.readObject());
- break;
- case 6:
- properties.setContentType(decoder.readSymbol(null));
- break;
- case 7:
- properties.setContentEncoding(decoder.readSymbol(null));
- break;
- case 8:
- properties.setAbsoluteExpiryTime(decoder.readTimestamp(null));
- break;
- case 9:
- properties.setCreationTime(decoder.readTimestamp(null));
- break;
- case 10:
- properties.setGroupId(decoder.readString(null));
- break;
- case 11:
- properties.setGroupSequence(decoder.readUnsignedInteger(null));
- break;
- case 12:
- properties.setReplyToGroupId(decoder.readString(null));
- break;
- default:
- throw new IllegalStateException("To many entries in Properties encoding");
+ try {
+ buffer.limit(newLimit);
+
+ Properties properties = new Properties();
+
+ for (int index = 0; index < count; ++index) {
+ switch (index) {
+ case 0:
+ properties.setMessageId(decoder.readObject());
+ break;
+ case 1:
+ properties.setUserId(decoder.readBinary(null));
+ break;
+ case 2:
+ properties.setTo(decoder.readString(null));
+ break;
+ case 3:
+ properties.setSubject(decoder.readString(null));
+ break;
+ case 4:
+ properties.setReplyTo(decoder.readString(null));
+ break;
+ case 5:
+ properties.setCorrelationId(decoder.readObject());
+ break;
+ case 6:
+ properties.setContentType(decoder.readSymbol(null));
+ break;
+ case 7:
+ properties.setContentEncoding(decoder.readSymbol(null));
+ break;
+ case 8:
+ properties.setAbsoluteExpiryTime(decoder.readTimestamp(null));
+ break;
+ case 9:
+ properties.setCreationTime(decoder.readTimestamp(null));
+ break;
+ case 10:
+ properties.setGroupId(decoder.readString(null));
+ break;
+ case 11:
+ properties.setGroupSequence(decoder.readUnsignedInteger(null));
+ break;
+ case 12:
+ properties.setReplyToGroupId(decoder.readString(null));
+ break;
+ default:
+ throw new IllegalStateException("To many entries in Properties encoding");
+ }
+ }
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("Properties incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
}
- }
- return properties;
+ return properties;
+ } finally {
+ buffer.limit(oldLimit);
+ }
}
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java
index e2ed8d79..38137e2d 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.transport;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
import java.util.Collection;
import org.apache.qpid.proton.amqp.Symbol;
@@ -32,6 +35,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.EncodingCodes;
import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.codec.TypeEncoding;
import org.apache.qpid.proton.codec.WritableBuffer;
@@ -94,36 +98,62 @@ public class FastPathDispositionType implements AMQPType<Disposition>, FastPathD
@Override
public Disposition readValue() {
DecoderImpl decoder = getDecoder();
- byte typeCode = decoder.getBuffer().get();
+ ReadableBuffer buffer = decoder.getBuffer();
- @SuppressWarnings("unused")
- int size = 0;
- int count = 0;
+ decoder.increaseDecodeDepth();
+ try {
+ byte typeCode = buffer.get();
- switch (typeCode) {
- case EncodingCodes.LIST0:
- // TODO - Technically invalid however old decoder also allowed this.
- break;
- case EncodingCodes.LIST8:
- size = ((int)decoder.getBuffer().get()) & 0xff;
- count = ((int)decoder.getBuffer().get()) & 0xff;
- break;
- case EncodingCodes.LIST32:
- size = decoder.getBuffer().getInt();
- count = decoder.getBuffer().getInt();
- break;
- default:
- throw new DecodeException("Incorrect type found in Disposition encoding: " + typeCode);
- }
+ int size = 0;
+ int count = 0;
- if (count < 2) {
- throw new DecodeException("The 'first' field cannot be omitted");
- }
+ final int oldLimit = buffer.limit();
+ final int newLimit;
- try {
- return readFields(decoder, count);
- } catch (NullPointerException npe) {
- throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+ switch (typeCode) {
+ case EncodingCodes.LIST0:
+ // Technically invalid however old decoder also allowed this. Is rejected by later field check.
+ newLimit = buffer.position();
+ break;
+ case EncodingCodes.LIST8:
+ size = ((int) buffer.get()) & 0xff;
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = ((int) buffer.get()) & 0xff;
+ validateListCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.LIST32:
+ size = buffer.getInt();
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateListCount(size, count, buffer.remaining());
+ break;
+ default:
+ throw new DecodeException("Incorrect type found in Disposition encoding: " + typeCode);
+ }
+
+ if (count < 2) {
+ throw new DecodeException("The 'first' field cannot be omitted");
+ }
+
+ try {
+ buffer.limit(newLimit);
+
+ final Disposition disposition = readFields(decoder, count);
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("Disposition incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return disposition;
+ } catch (NullPointerException npe) {
+ throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+ } finally {
+ buffer.limit(oldLimit);
+ }
+ } finally {
+ decoder.decreaseDecodeDepth();
}
}
@@ -160,7 +190,15 @@ public class FastPathDispositionType implements AMQPType<Disposition>, FastPathD
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java
index 4d17f2e8..9f9b5c54 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.transport;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
import java.util.Collection;
import org.apache.qpid.proton.amqp.Symbol;
@@ -28,6 +31,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.EncodingCodes;
import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.codec.TypeEncoding;
import org.apache.qpid.proton.codec.WritableBuffer;
@@ -82,36 +86,62 @@ public class FastPathFlowType implements AMQPType<Flow>, FastPathDescribedTypeCo
@Override
public Flow readValue() {
DecoderImpl decoder = getDecoder();
- byte typeCode = decoder.getBuffer().get();
+ ReadableBuffer buffer = decoder.getBuffer();
- @SuppressWarnings("unused")
- int size = 0;
- int count = 0;
+ decoder.increaseDecodeDepth();
+ try {
+ byte typeCode = buffer.get();
- switch (typeCode) {
- case EncodingCodes.LIST0:
- // TODO - Technically invalid however old decoder also allowed this.
- break;
- case EncodingCodes.LIST8:
- size = ((int)decoder.getBuffer().get()) & 0xff;
- count = ((int)decoder.getBuffer().get()) & 0xff;
- break;
- case EncodingCodes.LIST32:
- size = decoder.getBuffer().getInt();
- count = decoder.getBuffer().getInt();
- break;
- default:
- throw new DecodeException("Incorrect type found in Flow encoding: " + typeCode);
- }
+ int size = 0;
+ int count = 0;
- if (count < 4) {
- throw new DecodeException("The outgoing-window field cannot be omitted");
- }
+ final int oldLimit = buffer.limit();
+ final int newLimit;
- try {
- return readFields(decoder, count);
- } catch (NullPointerException npe) {
- throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+ switch (typeCode) {
+ case EncodingCodes.LIST0:
+ // Technically invalid however old decoder also allowed this. Is rejected by later field check.
+ newLimit = buffer.position();
+ break;
+ case EncodingCodes.LIST8:
+ size = ((int)buffer.get()) & 0xff;
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = ((int)buffer.get()) & 0xff;
+ validateListCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.LIST32:
+ size = buffer.getInt();
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateListCount(size, count, buffer.remaining());
+ break;
+ default:
+ throw new DecodeException("Incorrect type found in Flow encoding: " + typeCode);
+ }
+
+ if (count < 4) {
+ throw new DecodeException("The outgoing-window field cannot be omitted");
+ }
+
+ try {
+ buffer.limit(newLimit);
+
+ final Flow flow = readFields(decoder, count);
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("Flow incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return flow;
+ } catch (NullPointerException npe) {
+ throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+ } finally {
+ buffer.limit(oldLimit);
+ }
+ } finally {
+ decoder.decreaseDecodeDepth();
}
}
@@ -163,7 +193,15 @@ public class FastPathFlowType implements AMQPType<Flow>, FastPathDescribedTypeCo
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java
index b5408853..04d54b66 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java
@@ -16,6 +16,9 @@
*/
package org.apache.qpid.proton.codec.transport;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
import java.util.Collection;
import org.apache.qpid.proton.amqp.Symbol;
@@ -31,6 +34,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.EncodingCodes;
import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.codec.TypeEncoding;
import org.apache.qpid.proton.codec.WritableBuffer;
@@ -63,36 +67,62 @@ public class FastPathTransferType implements AMQPType<Transfer>, FastPathDescrib
@Override
public Transfer readValue() {
DecoderImpl decoder = getDecoder();
- byte typeCode = decoder.getBuffer().get();
+ ReadableBuffer buffer = decoder.getBuffer();
- @SuppressWarnings("unused")
- int size = 0;
- int count = 0;
+ decoder.increaseDecodeDepth();
+ try {
+ byte typeCode = buffer.get();
- switch (typeCode) {
- case EncodingCodes.LIST0:
- // TODO - Technically invalid however old decoder also allowed this.
- break;
- case EncodingCodes.LIST8:
- size = ((int)decoder.getBuffer().get()) & 0xff;
- count = ((int)decoder.getBuffer().get()) & 0xff;
- break;
- case EncodingCodes.LIST32:
- size = decoder.getBuffer().getInt();
- count = decoder.getBuffer().getInt();
- break;
- default:
- throw new DecodeException("Incorrect type found in Transfer encoding: " + typeCode);
- }
+ int size = 0;
+ int count = 0;
- if (count < 1) {
- throw new DecodeException("The handle field cannot be omitted");
- }
+ final int oldLimit = buffer.limit();
+ final int newLimit;
- try {
- return readFields(decoder, count);
- } catch (NullPointerException npe) {
- throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+ switch (typeCode) {
+ case EncodingCodes.LIST0:
+ // Technically invalid however old decoder also allowed this. Is rejected by later field check.
+ newLimit = buffer.position();
+ break;
+ case EncodingCodes.LIST8:
+ size = ((int) buffer.get()) & 0xff;
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = ((int) buffer.get()) & 0xff;
+ validateListCount(size, count, buffer.remaining());
+ break;
+ case EncodingCodes.LIST32:
+ size = buffer.getInt();
+ validateListSize(size, buffer.remaining());
+ newLimit = buffer.position() + size;
+ count = buffer.getInt();
+ validateListCount(size, count, buffer.remaining());
+ break;
+ default:
+ throw new DecodeException("Incorrect type found in Transfer encoding: " + typeCode);
+ }
+
+ if (count < 1) {
+ throw new DecodeException("The handle field cannot be omitted");
+ }
+
+ try {
+ buffer.limit(newLimit);
+
+ final Transfer transfer = readFields(decoder, count);
+
+ if (buffer.position() != newLimit) {
+ throw new DecodeException("Transfer incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+ }
+
+ return transfer;
+ } catch (NullPointerException npe) {
+ throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+ } finally {
+ buffer.limit(oldLimit);
+ }
+ } finally {
+ decoder.decreaseDecodeDepth();
}
}
@@ -145,7 +175,15 @@ public class FastPathTransferType implements AMQPType<Transfer>, FastPathDescrib
@Override
public void skipValue() {
- getDecoder().readConstructor().skipValue();
+ final DecoderImpl decoder = getDecoder();
+
+ decoder.increaseDecodeDepth();
+ try {
+ decoder.readConstructor().skipValue();
+ }
+ finally {
+ decoder.decreaseDecodeDepth();
+ }
}
@Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java
index 66cde843..86d189f8 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java
@@ -327,4 +327,14 @@ public interface Transport extends Endpoint
void setOutboundFrameSizeLimit(int size);
int getOutboundFrameSizeLimit();
+
+ /**
+ * Set an upper bound on the number of transfer frames per incoming delivery that
+ * are permitted. Defaults to 65535. 0 means no limit.
+ *
+ * @param maxTransfersPerDelivery the max number of transfers per delivery
+ */
+ void setMaxTransfersPerDelivery(int maxTransfersPerDelivery);
+
+ int getMaxTransfersPerDelivery();
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/TransportConditionException.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/TransportConditionException.java
new file mode 100644
index 00000000..73277abd
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/TransportConditionException.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.proton.engine;
+
+import java.util.Objects;
+
+import org.apache.qpid.proton.amqp.Symbol;
+
+public class TransportConditionException extends TransportException {
+
+ private static final long serialVersionUID = 828725563575892233L;
+ private Symbol _condition;
+
+ public TransportConditionException(String message, Symbol condition) {
+ this(message, condition, null);
+ }
+
+ public TransportConditionException(String message, Symbol condition, Throwable cause) {
+ super(message, cause);
+ Objects.requireNonNull(message, "A message must be provided");
+ Objects.requireNonNull(condition, "A condition must be provided");
+ _condition = condition;
+ }
+
+ public Symbol getCondition() {
+ return _condition;
+ }
+}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java
index d0df4994..02ec3bbf 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java
@@ -73,6 +73,7 @@ class FrameParser implements TransportInput
private ByteBuffer _inputBuffer = null;
private boolean _tail_closed = false;
+ private boolean _tail_closed_seen = false;
private State _state = State.HEADER0;
@@ -114,7 +115,7 @@ class FrameParser implements TransportInput
State state = _state;
ByteBuffer oldIn = null;
- boolean transportAccepting = true;
+ boolean transportAccepting = !_tail_closed_seen;
while(in.hasRemaining() && state != State.ERROR && transportAccepting)
{
@@ -392,8 +393,21 @@ class FrameParser implements TransportInput
if (frameBodySize > 0)
{
_decoder.setByteBuffer(in);
- val = _decoder.readObject();
- _decoder.setByteBuffer(null);
+ try
+ {
+ val = _decoder.readObject();
+ }
+ catch(DecodeException de)
+ {
+ throw de;
+ }
+ catch (RuntimeException re)
+ {
+ throw new DecodeException("Exception decoding frame performative: " + re.getMessage(), re);
+ }
+ finally {
+ _decoder.setByteBuffer(null);
+ }
if(in.hasRemaining())
{
@@ -423,6 +437,9 @@ class FrameParser implements TransportInput
if(_frameHandler.isHandlingFrames())
{
_tail_closed = _frameHandler.handleFrame(frame);
+ if (_tail_closed) {
+ transportAccepting = false;
+ }
}
else
{
@@ -457,8 +474,9 @@ class FrameParser implements TransportInput
}
- if (_tail_closed)
+ if (_tail_closed && !_tail_closed_seen)
{
+ _tail_closed_seen = true;
if (in.hasRemaining()) {
state = State.ERROR;
frameParsingError = new TransportException("framing error");
@@ -476,6 +494,7 @@ class FrameParser implements TransportInput
if(_state == State.ERROR)
{
_tail_closed = true;
+ _tail_closed_seen = true;
if(frameParsingError != null)
{
_parsingError = frameParsingError;
@@ -544,6 +563,10 @@ class FrameParser implements TransportInput
} else {
_inputBuffer.clear();
}
+
+ if(_tail_closed && _tail_closed_seen) {
+ _inputBuffer = null;
+ }
}
}
else
@@ -555,8 +578,10 @@ class FrameParser implements TransportInput
@Override
public void close_tail()
{
- _tail_closed = true;
- process();
+ if (!_tail_closed) {
+ _tail_closed = true;
+ process();
+ }
}
/**
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java
index 6b689f90..64ed4514 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java
@@ -73,6 +73,10 @@ class FrameWriter {
return frameBuffer.transferTo(dst);
}
+ int getFrameBufferPosition() {
+ return frameBuffer.position();
+ }
+
long getFramesOutput() {
return framesOutput;
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
index eea40536..01c34d12 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
@@ -53,8 +53,8 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
public static final byte SASL_FRAME_TYPE = (byte) 1;
private static final String HEADER_DESCRIPTION = "SASL";
- private final DecoderImpl _decoder = new DecoderImpl();
- private final EncoderImpl _encoder = new EncoderImpl(_decoder);
+ private final DecoderImpl _decoder = new DecoderImpl(true);
+ private final EncoderImpl _encoder = new EncoderImpl(_decoder, true);
private final TransportImpl _transport;
@@ -98,7 +98,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
_transport = transport;
_maxFrameSize = maxFrameSize;
- AMQPDefinedTypes.registerAllTypes(_decoder,_encoder);
+ _decoder.setZeroWidthArrayElementLimit(0);
+ _decoder.setMaxDecodeDepth(2);
+
+ AMQPDefinedTypes.registerSecurityTypes(_decoder,_encoder);
_frameParser = new SaslFrameParser(this, _decoder, maxFrameSize, _transport);
_frameWriter = new FrameWriter(_encoder, maxFrameSize, FrameWriter.SASL_FRAME_TYPE, _transport);
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java
index b9f07dc0..222ffd77 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java
@@ -17,6 +17,7 @@
package org.apache.qpid.proton.engine.impl;
+import static org.apache.qpid.proton.amqp.transport.ConnectionError.FRAMING_ERROR;
import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.pourArrayToBuffer;
import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.pourBufferToArray;
@@ -58,6 +59,7 @@ import org.apache.qpid.proton.engine.Sasl;
import org.apache.qpid.proton.engine.Ssl;
import org.apache.qpid.proton.engine.SslDomain;
import org.apache.qpid.proton.engine.SslPeerDetails;
+import org.apache.qpid.proton.engine.TransportConditionException;
import org.apache.qpid.proton.engine.TransportDecodeException;
import org.apache.qpid.proton.engine.TransportException;
import org.apache.qpid.proton.engine.TransportResult;
@@ -73,6 +75,7 @@ public class TransportImpl extends EndpointImpl
{
static final int BUFFER_RELEASE_THRESHOLD = Integer.getInteger("proton.transport_buffer_release_threshold", 2 * 1024 * 1024);
private static final int CHANNEL_MAX_LIMIT = 65535;
+ private static final int TRANSFERS_PER_DELIVERY_MAX_LIMIT = 65535;
private static final boolean getBooleanEnv(String name)
{
@@ -111,6 +114,7 @@ public class TransportImpl extends EndpointImpl
private int _outboundFrameSizeLimit = 0;
private int _channelMax = CHANNEL_MAX_LIMIT;
private int _remoteChannelMax = CHANNEL_MAX_LIMIT;
+ private int _maxTransfersPerDelivery = TRANSFERS_PER_DELIVERY_MAX_LIMIT;
private final FrameWriter _frameWriter;
@@ -171,6 +175,10 @@ public class TransportImpl extends EndpointImpl
{
AMQPDefinedTypes.registerAllTypes(_decoder, _encoder);
+ if (maxFrameSize > 0 && maxFrameSize < MIN_MAX_FRAME_SIZE)
+ {
+ throw new IllegalArgumentException("Cannot set max frame size under 512 (MIN_MAX_FRAME_SIZE)");
+ }
_maxFrameSize = maxFrameSize;
_frameWriter = new FrameWriter(_encoder, _remoteMaxFrameSize,
FrameWriter.AMQP_FRAME_TYPE,
@@ -213,12 +221,22 @@ public class TransportImpl extends EndpointImpl
throw new IllegalStateException("Cannot set initial remote max frame size after transport has been initialised");
}
+ if (remoteMaxFrameSize < MIN_MAX_FRAME_SIZE)
+ {
+ throw new IllegalArgumentException("Cannot set initial remote max frame size under 512 (MIN_MAX_FRAME_SIZE)");
+ }
+
_remoteMaxFrameSize = remoteMaxFrameSize;
}
@Override
public void setMaxFrameSize(int maxFrameSize)
{
+ if (maxFrameSize > 0 && maxFrameSize < MIN_MAX_FRAME_SIZE)
+ {
+ throw new IllegalArgumentException("Cannot set max frame size under 512 (MIN_MAX_FRAME_SIZE)");
+ }
+
if(_init)
{
throw new IllegalStateException("Cannot set max frame size after transport has been initialised");
@@ -360,22 +378,31 @@ public class TransportImpl extends EndpointImpl
{
processHeader();
processOpen();
- processBegin();
- processAttach();
- processReceiverFlow();
- // we process transport work twice intentionally, the first
- // pass may end up settling deliveries that the second pass
- // can clean up
- processTransportWork();
- processTransportWork();
- processSenderFlow();
- processDetach();
- processEnd();
+ if (!_conditionSet) {
+ processBegin();
+ processAttach();
+ processReceiverFlow();
+ // we process transport work twice intentionally, the first
+ // pass may end up settling deliveries that the second pass
+ // can clean up
+ processTransportWork();
+ processTransportWork();
+ processSenderFlow();
+ processDetach();
+ processEnd();
+ }
processClose();
_frameWriter.readBytes(outputBuffer);
- return _isCloseSent || _head_closed;
+ if(_frameWriter.getFrameBufferPosition() > 0)
+ {
+ return false;
+ }
+ else
+ {
+ return _isCloseSent || _head_closed;
+ }
}
@Override
@@ -783,7 +810,7 @@ public class TransportImpl extends EndpointImpl
if(session.getLocalState() == EndpointState.ACTIVE && transportSession.isLocalChannelSet())
{
- if(transportSession.getIncomingWindowSize().equals(UnsignedInteger.ZERO))
+ if(transportSession.getIncomingWindowSize().equals(UnsignedInteger.ZERO) && !_conditionSet)
{
writeFlow(transportSession, null);
}
@@ -1115,7 +1142,7 @@ public class TransportImpl extends EndpointImpl
(_connectionEndpoint != null &&
_connectionEndpoint.getLocalState() == EndpointState.CLOSED)) &&
!_isCloseSent) {
- if(!hasSendableMessages(null))
+ if(!hasSendableMessages(null) || _conditionSet)
{
Close close = new Close();
@@ -1171,25 +1198,21 @@ public class TransportImpl extends EndpointImpl
@Override
public void handleOpen(Open open, Binary payload, Integer channel)
{
- setRemoteState(EndpointState.ACTIVE);
- if(_connectionEndpoint != null)
- {
- _connectionEndpoint.handleOpen(open);
- }
- else
- {
- _open = open;
- }
+ int effectiveMaxFrameSize = MIN_MAX_FRAME_SIZE;
+ final UnsignedInteger openMaxFrameSize = open.getMaxFrameSize();
- int effectiveMaxFrameSize = _remoteMaxFrameSize;
- if(open.getMaxFrameSize().longValue() > 0)
+ if (openMaxFrameSize.longValue() >= MIN_MAX_FRAME_SIZE)
+ {
+ _remoteMaxFrameSize = openMaxFrameSize.intValue();
+ effectiveMaxFrameSize = (int) Math.min(openMaxFrameSize.longValue(), Integer.MAX_VALUE);
+ } else
{
- _remoteMaxFrameSize = (int) open.getMaxFrameSize().longValue();
- effectiveMaxFrameSize = (int) Math.min(open.getMaxFrameSize().longValue(), Integer.MAX_VALUE);
+ closed(new TransportConditionException("Requested max-frame-size " + openMaxFrameSize.longValue() + " is below the minimum of 512 (MIN-MAX-FRAME-SIZE)", FRAMING_ERROR));
+ return;
}
if(_outboundFrameSizeLimit > 0) {
- effectiveMaxFrameSize = (int) Math.min(open.getMaxFrameSize().longValue(), _outboundFrameSizeLimit);
+ effectiveMaxFrameSize = Math.min(effectiveMaxFrameSize, _outboundFrameSizeLimit);
}
_frameWriter.setMaxFrameSize(effectiveMaxFrameSize);
@@ -1203,6 +1226,16 @@ public class TransportImpl extends EndpointImpl
{
_remoteIdleTimeout = open.getIdleTimeOut().intValue();
}
+
+ setRemoteState(EndpointState.ACTIVE);
+ if(_connectionEndpoint != null)
+ {
+ _connectionEndpoint.handleOpen(open);
+ }
+ else
+ {
+ _open = open;
+ }
}
@Override
@@ -1470,7 +1503,7 @@ public class TransportImpl extends EndpointImpl
}
frame.getBody().invoke(this,frame.getPayload(), frame.getChannel());
- return _closeReceived;
+ return _closeReceived || _conditionSet;
}
void put(Event.Type type, Object context) {
@@ -1494,6 +1527,10 @@ public class TransportImpl extends EndpointImpl
if(!_conditionSet) {
if(error instanceof TransportDecodeException) {
setCondition(new ErrorCondition(AmqpError.DECODE_ERROR, error.getMessage()));
+ } else if (error instanceof TransportConditionException) {
+ final Symbol condition = ((TransportConditionException) error).getCondition();
+ ErrorCondition errorCondition = new ErrorCondition(condition, error.getMessage());
+ setCondition(errorCondition);
} else {
String description = error == null ? "connection aborted" : error.toString();
@@ -1510,6 +1547,7 @@ public class TransportImpl extends EndpointImpl
}
if (!postedTailClosed) {
+ // The FrameParser (_inputProcessor) sets _tail_closed when handleFrame returns _conditionSet.
put(Event.Type.TRANSPORT_TAIL_CLOSED, this);
postedTailClosed = true;
maybePostClosed();
@@ -1875,6 +1913,9 @@ public class TransportImpl extends EndpointImpl
@Override
public void setOutboundFrameSizeLimit(int limit) {
+ if (limit > 0 && limit < MIN_MAX_FRAME_SIZE) {
+ throw new IllegalArgumentException("Outbound frame size limit should be at least 512 (MIN_MAX_FRAME_SIZE)");
+ }
_outboundFrameSizeLimit = limit;
}
@@ -1882,4 +1923,18 @@ public class TransportImpl extends EndpointImpl
public int getOutboundFrameSizeLimit() {
return _outboundFrameSizeLimit;
}
+
+ @Override
+ public int getMaxTransfersPerDelivery() {
+ return _maxTransfersPerDelivery;
+ }
+
+ @Override
+ public void setMaxTransfersPerDelivery(final int maxTransfersPerDelivery) {
+ if (maxTransfersPerDelivery < 0) {
+ throw new IllegalArgumentException("maxTransfersPerDelivery must not be negative");
+ }
+
+ _maxTransfersPerDelivery = maxTransfersPerDelivery;
+ }
}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java
index f43d7e0b..c25d0d74 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java
@@ -56,7 +56,10 @@ class TransportOutputAdaptor implements TransportOutput
init_buffers();
}
- _output_done = _transportOutputWriter.writeInto(_outputBuffer);
+ if (!_output_done)
+ {
+ _output_done = _transportOutputWriter.writeInto(_outputBuffer);
+ }
_head.limit(_outputBuffer.position());
if (_outputBuffer.position() == 0 && _outputBuffer.capacity() > TransportImpl.BUFFER_RELEASE_THRESHOLD)
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java
index d657ba95..8f5ea6ae 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java
@@ -21,16 +21,23 @@
package org.apache.qpid.proton.engine.impl;
+import static org.apache.qpid.proton.amqp.transport.SessionError.WINDOW_VIOLATION;
+
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.UnsignedInteger;
+import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.Disposition;
import org.apache.qpid.proton.amqp.transport.Flow;
+import org.apache.qpid.proton.amqp.transport.LinkError;
import org.apache.qpid.proton.amqp.transport.Role;
import org.apache.qpid.proton.amqp.transport.Transfer;
import org.apache.qpid.proton.engine.Event;
+import org.apache.qpid.proton.engine.TransportConditionException;
class TransportSession
{
@@ -71,6 +78,7 @@ class TransportSession
private boolean _endReceived;
private boolean _beginSent;
private boolean _endSent;
+ private boolean _transferLimitBlown;
TransportSession(TransportImpl transport, SessionImpl session)
{
@@ -279,6 +287,10 @@ class TransportSession
public void handleTransfer(Transfer transfer, Binary payload)
{
+ if(checkIncomingWindow()) {
+ return;
+ }
+
DeliveryImpl delivery;
incrementNextIncomingId(); // The conceptual/non-wire transfer-id, for the session window.
@@ -290,6 +302,10 @@ class TransportSession
{
delivery = _unsettledIncomingDeliveriesById.get(linkIncomingDeliveryId);
delivery.getTransportDelivery().incrementSessionSize();
+
+ if(checkTransfersPerDeliveryLimit(delivery)) {
+ return;
+ }
}
else
{
@@ -298,6 +314,7 @@ class TransportSession
_incomingDeliveryId = deliveryId;
ReceiverImpl receiver = transportReceiver.getReceiver();
+
Binary deliveryTag = transfer.getDeliveryTag();
delivery = receiver.delivery(deliveryTag.getArray(), deliveryTag.getArrayOffset(),
deliveryTag.getLength());
@@ -355,6 +372,41 @@ class TransportSession
getSession().getConnection().put(Event.Type.DELIVERY, delivery);
}
+ private boolean checkTransfersPerDeliveryLimit(DeliveryImpl delivery) {
+ if (_transferLimitBlown)
+ {
+ return true;
+ }
+
+ if (_transport.getMaxTransfersPerDelivery() > 0)
+ {
+ if (delivery.getTransportDelivery().getSessionSize() > _transport.getMaxTransfersPerDelivery())
+ {
+ _transport.closed(new TransportConditionException("Max transfers per delivery limit exceeded", LinkError.TRANSFER_LIMIT_EXCEEDED));
+ _transferLimitBlown = true;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean checkIncomingWindow() {
+ if (_transferLimitBlown)
+ {
+ return true;
+ }
+
+ if (UnsignedInteger.ZERO.equals(_incomingWindowSize))
+ {
+ _transferLimitBlown = true;
+ _transport.closed(new TransportConditionException("Session incoming window exceeded", WINDOW_VIOLATION));
+ return true;
+ }
+
+ return false;
+ }
+
private void verifyNewDeliveryIdSequence(UnsignedInteger previousId, UnsignedInteger linkIncomingId, UnsignedInteger newDeliveryId) {
if(newDeliveryId == null) {
throw new IllegalStateException("No delivery-id specified on first Transfer of new delivery");
@@ -434,13 +486,96 @@ class TransportSession
void handleDisposition(Disposition disposition)
{
- UnsignedInteger id = disposition.getFirst();
- UnsignedInteger last = disposition.getLast() == null ? id : disposition.getLast();
+ final UnsignedInteger first = disposition.getFirst();
final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries =
disposition.getRole() == Role.RECEIVER ? _unsettledOutgoingDeliveriesById
: _unsettledIncomingDeliveriesById;
- while(id.compareTo(last)<=0)
+ if (disposition.getLast() == null || first.equals(disposition.getLast())) {
+ applyDispositionSingle(first, disposition, unsettledDeliveries);
+ return;
+ }
+
+ final UnsignedInteger last = disposition.getLast();
+
+ final long range = getSequenceRange(first, last);
+ if (range > Integer.MAX_VALUE) {
+ _transport.closed(new TransportConditionException("Invalid disposition sequence range: " + first.longValue() + " to " + last.longValue(), AmqpError.NOT_ALLOWED));
+ return;
+ }
+
+ final long count = range + 1;
+ if (count > unsettledDeliveries.size())
+ {
+ applyDispositionByEntries(first, range, disposition, unsettledDeliveries);
+ }
+ else
+ {
+ applyDispositionByRange(first, range, disposition, unsettledDeliveries);
+ }
+ }
+
+ private void applyDispositionSingle(final UnsignedInteger id, final Disposition disposition,
+ final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries)
+ {
+ DeliveryImpl delivery = unsettledDeliveries.get(id);
+ if(delivery != null)
+ {
+ if(disposition.getState() != null)
+ {
+ delivery.setRemoteDeliveryState(disposition.getState());
+ }
+ if(disposition.getSettled())
+ {
+ delivery.setRemoteSettled(true);
+ unsettledDeliveries.remove(id);
+ }
+ delivery.updateWork();
+
+ getSession().getConnection().put(Event.Type.DELIVERY, delivery);
+ }
+ }
+
+ private long getSequenceRange(final UnsignedInteger first, final UnsignedInteger last) {
+ return (last.intValue() - first.intValue()) & 0xFFFFFFFFL;
+ }
+
+ private void applyDispositionByEntries(final UnsignedInteger first, final long range, final Disposition disposition,
+ final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries)
+ {
+ Set<UnsignedInteger> deliveryIDs = unsettledDeliveries.keySet();
+ Iterator<UnsignedInteger> idIterator = deliveryIDs.iterator();
+
+ while (idIterator.hasNext())
+ {
+ final UnsignedInteger id = idIterator.next();
+
+ if (getSequenceRange(first, id) <= range)
+ {
+ final DeliveryImpl delivery = unsettledDeliveries.get(id);
+ if (disposition.getState() != null)
+ {
+ delivery.setRemoteDeliveryState(disposition.getState());
+ }
+ if (disposition.getSettled())
+ {
+ delivery.setRemoteSettled(true);
+
+ // Remove it from the map, via the key iterator
+ idIterator.remove();
+ }
+ delivery.updateWork();
+
+ getSession().getConnection().put(Event.Type.DELIVERY, delivery);
+ }
+ }
+ }
+
+ private void applyDispositionByRange(final UnsignedInteger first, final long range, final Disposition disposition,
+ final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries)
+ {
+ UnsignedInteger id = first;
+ for (long i = 0L; i <= range; i++)
{
DeliveryImpl delivery = unsettledDeliveries.get(id);
if(delivery != null)
@@ -449,7 +584,7 @@ class TransportSession
{
delivery.setRemoteDeliveryState(disposition.getState());
}
- if(Boolean.TRUE.equals(disposition.getSettled()))
+ if(disposition.getSettled())
{
delivery.setRemoteSettled(true);
unsettledDeliveries.remove(id);
@@ -458,9 +593,9 @@ class TransportSession
getSession().getConnection().put(Event.Type.DELIVERY, delivery);
}
+
id = id.add(UnsignedInteger.ONE);
}
- //TODO - Implement.
}
void addUnsettledOutgoing(UnsignedInteger deliveryId, DeliveryImpl delivery)
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java b/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java
index db290e3c..adbe76c4 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java
@@ -169,6 +169,8 @@ public interface Message
*/
int decode(byte[] data, int offset, int length);
+ int decode(byte[] data, int offset, int length, MessageDecodeOptions options);
+
/**
* Decodes the Message from the given {@link ReadableBuffer}.
* <p>
@@ -181,6 +183,20 @@ public interface Message
*/
void decode(ReadableBuffer buffer);
+ /**
+ * Decodes the Message from the given {@link ReadableBuffer}.
+ * <p>
+ * If the buffer given does not contain the fully encoded Message bytes for decode
+ * this method will throw an exception to indicate the buffer underflow condition and
+ * the message object will be left in an undefined state.
+ *
+ * @param buffer
+ * A {@link ReadableBuffer} that contains the complete message bytes.
+ * @param options
+ * A {@link MessageDecodeOptions} that configures certain decode settings.
+ */
+ void decode(ReadableBuffer buffer, MessageDecodeOptions options);
+
/**
* Encodes up to {@code length} bytes of the message into the provided byte array,
* starting at position {@code offset}.
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/message/MessageDecodeOptions.java b/proton-j/src/main/java/org/apache/qpid/proton/message/MessageDecodeOptions.java
new file mode 100644
index 00000000..e2c2b2c1
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/message/MessageDecodeOptions.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.proton.message;
+
+/**
+ * Configuration options that can be passed to the {@link Message} decode APIs
+ * to govern certain behaviours of the AMQP decoder.
+ */
+public final class MessageDecodeOptions {
+
+ private static final int DEFAULT_MAX_DECODE_DEPTH = 32;
+
+ private int _maxDecodeDepth = DEFAULT_MAX_DECODE_DEPTH;
+ private int _zeroWidthElementLimit = 0;
+
+ /**
+ * New decode options with default values.
+ */
+ public MessageDecodeOptions() {
+ }
+
+ /**
+ * Gets the configured maximum number of elements that can be decoded from an array
+ * encoded with the zero-width elements (ulong0, uint0, list0, boolean-true,
+ * boolean-false, null).
+ *
+ * @return the max number of zero-width array elements allowed.
+ */
+ public int getZeroWidthArrayElementLimit() {
+ return _zeroWidthElementLimit;
+ }
+
+ /**
+ * Configures the maximum number of array elements allowed while decoding arrays
+ * of zero-width elements (ulong0, uint0, list0, boolean-true, boolean-false, null)
+ * before a decode exception.
+ *
+ * Default is 0, meaning only the empty array is permitted.
+ *
+ * @param zeroWidthElementLimit The configured max zero-width elements allowed.
+ *
+ * @return this {@link MessageDecodeOptions} instance.
+ */
+ public MessageDecodeOptions setZeroWidthArrayElementLimit(int zeroWidthElementLimit) {
+ _zeroWidthElementLimit = zeroWidthElementLimit;
+ return this;
+ }
+
+ /**
+ * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload.
+ *
+ * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+ */
+ public int getMaxDecodeDepth() {
+ return _maxDecodeDepth;
+ }
+
+ /**
+ * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+ * Arrays, and certain related DescribedTypes from an encoded payload. Default is 32.
+ *
+ * @param maxDecodeDepth The configured max decode depth allowed.
+ *
+ * @return this {@link MessageDecodeOptions} instance.
+ */
+ public MessageDecodeOptions setMaxDecodeDepth(int maxDecodeDepth) {
+ this._maxDecodeDepth = maxDecodeDepth;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java
index a31c169c..76a52eb7 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java
@@ -23,6 +23,8 @@ package org.apache.qpid.proton.message.impl;
import java.nio.ByteBuffer;
import java.util.Date;
+import java.util.Objects;
+
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.UnsignedByte;
@@ -33,6 +35,8 @@ import org.apache.qpid.proton.message.*;
public class MessageImpl implements ProtonJMessage
{
+ private static final MessageDecodeOptions DEFAULT_OPTIONS = new MessageDecodeOptions();
+
private Header _header;
private DeliveryAnnotations _deliveryAnnotations;
private MessageAnnotations _messageAnnotations;
@@ -568,21 +572,47 @@ public class MessageImpl implements ProtonJMessage
@Override
public int decode(byte[] data, int offset, int length)
{
+ return decode(data, offset, length, DEFAULT_OPTIONS);
+ }
+
+ @Override
+ public int decode(byte[] data, int offset, int length, MessageDecodeOptions options)
+ {
+ Objects.requireNonNull(options, "Options must not be null");
+
final ByteBuffer buffer = ByteBuffer.wrap(data, offset, length);
- decode(buffer);
+ decode(buffer, options);
return length-buffer.remaining();
}
public void decode(ByteBuffer buffer)
{
- decode(ReadableBuffer.ByteBufferReader.wrap(buffer));
+ decode(ReadableBuffer.ByteBufferReader.wrap(buffer), DEFAULT_OPTIONS);
}
+ public void decode(ByteBuffer buffer, MessageDecodeOptions options)
+ {
+ Objects.requireNonNull(options, "Options must not be null");
+
+ decode(ReadableBuffer.ByteBufferReader.wrap(buffer), options);
+ }
+
+ @Override
public void decode(ReadableBuffer buffer)
{
+ decode(buffer, DEFAULT_OPTIONS);
+ }
+
+ @Override
+ public void decode(ReadableBuffer buffer, MessageDecodeOptions options)
+ {
+ Objects.requireNonNull(options, "Options must not be null");
+
DecoderImpl decoder = tlsCodec.get().decoder;
decoder.setBuffer(buffer);
+ decoder.setZeroWidthArrayElementLimit(options.getZeroWidthArrayElementLimit());
+ decoder.setMaxDecodeDepth(options.getMaxDecodeDepth());
_header = null;
_deliveryAnnotations = null;
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/amqp/SymbolTest.java b/proton-j/src/test/java/org/apache/qpid/proton/amqp/SymbolTest.java
new file mode 100644
index 00000000..2d210a41
--- /dev/null
+++ b/proton-j/src/test/java/org/apache/qpid/proton/amqp/SymbolTest.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.proton.amqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Test the {@link Symbol} values.
+ */
+public class SymbolTest {
+
+ private final String LARGE_SYMBOL_VALUE = "Large String: " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog. " +
+ "The quick brown fox jumps over the lazy dog.";
+
+ private final String MEDIUM_SYMBOL_VALUE = "Medium String: " +
+ "The quick brown fox jumps over the lazy dog.";
+
+ @Test
+ public void testGetSymbolWithNullString() {
+ assertNull(Symbol.getSymbol((String) null));
+ }
+
+ @Test
+ public void testGetSaslSymbolWithNullString() {
+ assertNull(Symbol.getSaslSymbol((String) null));
+ }
+
+ @Test
+ public void testGetSymbolWithEmptyString() {
+ assertNotNull(Symbol.getSymbol(""));
+ assertSame(Symbol.getSymbol(""), Symbol.getSymbol(""));
+ }
+
+ @Test
+ public void testGetSaslSymbolWithEmptyString() {
+ assertNotNull(Symbol.getSaslSymbol(""));
+ assertSame(Symbol.getSaslSymbol(""), Symbol.getSaslSymbol(""));
+ }
+
+ @Test
+ public void testCompareTo() {
+ String symbolString1 = "Symbol-1";
+ String symbolString2 = "Symbol-2";
+ String symbolString3 = "Symbol-3";
+
+ Symbol symbol1 = Symbol.getSymbol(symbolString1);
+ Symbol symbol2 = Symbol.getSymbol(symbolString2);
+ Symbol symbol3 = Symbol.getSymbol(symbolString3);
+
+ assertEquals(0, symbol1.compareTo(symbol1));
+ assertEquals(0, symbol2.compareTo(symbol2));
+ assertEquals(0, symbol3.compareTo(symbol3));
+
+ assertTrue(symbol2.compareTo(symbol1) > 0);
+ assertTrue(symbol3.compareTo(symbol1) > 0);
+ assertTrue(symbol3.compareTo(symbol2) > 0);
+
+ assertTrue(symbol1.compareTo(symbol2) < 0);
+ assertTrue(symbol1.compareTo(symbol3) < 0);
+ assertTrue(symbol2.compareTo(symbol3) < 0);
+ }
+
+ @Test
+ public void testEquals() {
+ final String symbolString1 = "Symbol-1";
+ final String symbolString2 = "Symbol-2";
+ final String symbolString3 = "Symbol-3";
+
+ final Symbol symbol1 = Symbol.getSymbol(symbolString1);
+ final Symbol symbol2 = Symbol.getSymbol(symbolString2);
+ final Symbol symbol3 = Symbol.getSymbol(symbolString3);
+
+ assertEquals(symbolString1, symbol1.toString());
+ assertEquals(symbolString2, symbol2.toString());
+ assertEquals(symbolString3, symbol3.toString());
+
+ assertNotEquals(symbol1, symbol2);
+ assertNotEquals(symbol2, symbol3);
+ assertNotEquals(symbol3, symbol1);
+
+ assertNotEquals(symbol1, symbolString1);
+ assertNotEquals(symbol2, symbolString2);
+ assertNotEquals(symbol3, symbolString3);
+
+ assertNotEquals(symbol1, null);
+ assertNotEquals(symbol2, null);
+ assertNotEquals(symbol3, null);
+
+ final Symbol symbol1B = Symbol.getSymbol(symbolString1);
+
+ assertEquals(symbol1, symbol1B);
+ assertEquals(symbol1, symbol1); // Itself
+
+ final Symbol largeSymbolA = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+ final Symbol largeSymbolB = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+ final Symbol largeSymbolC = Symbol.getSymbol(LARGE_SYMBOL_VALUE + "C");
+
+ assertNotEquals(symbol1, largeSymbolA);
+ assertNotEquals(largeSymbolA, symbol1);
+ assertNotEquals(largeSymbolA, largeSymbolC);
+ assertNotEquals(largeSymbolC, largeSymbolA);
+
+ assertEquals(largeSymbolA, largeSymbolB);
+ assertEquals(largeSymbolB, largeSymbolA);
+
+ assertEquals(largeSymbolA, largeSymbolA); // Itself
+
+ assertNotEquals(largeSymbolA, null);
+
+ final Symbol saslSymbol1 = Symbol.getSymbol(symbolString1);
+ final Symbol saslSymbol2 = Symbol.getSymbol(symbolString2);
+
+ assertEquals(symbol1, saslSymbol1);
+ assertEquals(saslSymbol1, symbol1);
+
+ assertNotEquals(symbol2, saslSymbol1);
+ assertNotEquals(saslSymbol1, symbol2);
+
+ assertNotEquals(saslSymbol1, saslSymbol2);
+ assertNotEquals(saslSymbol2, saslSymbol1);
+ }
+
+ @Test
+ public void testHashcode() {
+ final String symbolString1 = "Symbol-1";
+ final String symbolString2 = "Symbol-2";
+
+ final Symbol symbol1 = Symbol.getSymbol(symbolString1);
+ final Symbol symbol2 = Symbol.getSymbol(symbolString2);
+
+ assertNotEquals(symbol1, symbol2);
+ assertNotEquals(symbol1.hashCode(), symbol2.hashCode());
+
+ assertEquals(symbol1.hashCode(), Symbol.getSymbol(symbolString1).hashCode());
+ assertEquals(symbol2.hashCode(), Symbol.getSymbol(symbolString2).hashCode());
+ assertEquals(symbol1.hashCode(), Symbol.getSaslSymbol(symbolString1).hashCode());
+ assertEquals(symbol2.hashCode(), Symbol.getSaslSymbol(symbolString2).hashCode());
+
+ final Symbol largeSymbolA = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+ final Symbol largeSymbolB = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+
+ assertEquals(largeSymbolA, largeSymbolB);
+ assertNotSame(largeSymbolA, largeSymbolB);
+ assertEquals(largeSymbolA.hashCode(), largeSymbolB.hashCode());
+ }
+
+ @Test
+ public void testValueOf() {
+ String symbolString1 = "Symbol-1";
+ String symbolString2 = "Symbol-2";
+
+ Symbol symbol1 = Symbol.valueOf(symbolString1);
+ Symbol symbol2 = Symbol.getSymbol(symbolString2);
+
+ assertNotEquals(symbol1, symbol2);
+
+ assertEquals(symbolString1, symbol1.toString());
+ assertEquals(symbolString2, symbol2.toString());
+ }
+
+ @Test
+ public void testValueOfProducesCachedSingleton() {
+ String symbolString = "Symbol-String";
+
+ Symbol symbol1 = Symbol.valueOf(symbolString);
+ Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+ assertEquals(symbolString, symbol1.toString());
+ assertEquals(symbolString, symbol2.toString());
+
+ assertSame(symbol1, symbol2);
+ }
+
+ @Test
+ public void testGetSymbol() {
+ String symbolString1 = "Symbol-1";
+ String symbolString2 = "Symbol-2";
+
+ Symbol symbol1 = Symbol.getSymbol(symbolString1);
+ Symbol symbol2 = Symbol.getSymbol(symbolString2);
+
+ assertNotEquals(symbol1, symbol2);
+
+ assertEquals(symbolString1, symbol1.toString());
+ assertEquals(symbolString2, symbol2.toString());
+ }
+
+ @Test
+ public void testGetSymbolProducesCachedSingleton() {
+ String symbolString = "Symbol-String";
+
+ Symbol symbol1 = Symbol.getSymbol(symbolString);
+ Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+ assertEquals(symbolString, symbol1.toString());
+ assertEquals(symbolString, symbol2.toString());
+
+ assertSame(symbol1, symbol2);
+ }
+
+ @Test
+ public void testGetSymbolAndValueOfProduceCachedSingleton() {
+ String symbolString = "Symbol-String";
+
+ Symbol symbol1 = Symbol.valueOf(symbolString);
+ Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+ assertEquals(symbolString, symbol1.toString());
+ assertEquals(symbolString, symbol2.toString());
+
+ assertSame(symbol1, symbol2);
+ }
+
+ @Test
+ public void testToStringProducesCachedSingleton() {
+ String symbolString = "Symbol-String";
+
+ Symbol symbol1 = Symbol.getSymbol(symbolString);
+ Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+ assertEquals(symbolString, symbol1.toString());
+ assertEquals(symbolString, symbol2.toString());
+
+ assertSame(symbol1, symbol2);
+ assertSame(symbol1.toString(), symbol2.toString());
+ }
+
+ @Test
+ public void testLargeSymbolNotCached() {
+ Symbol symbol1 = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+ Symbol symbol2 = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+
+ assertNotSame(symbol1, symbol2);
+ }
+
+ @Test
+ public void testLargeSaslSymbolNotCached() {
+ Symbol symbol1 = Symbol.getSaslSymbol(LARGE_SYMBOL_VALUE);
+ Symbol symbol2 = Symbol.getSaslSymbol(LARGE_SYMBOL_VALUE);
+
+ assertNotSame(symbol1, symbol2);
+ }
+
+ @Test
+ @Ignore("Cache is static so running has side effects.")
+ public void testGetSymbolCachingMaxesOut() {
+ boolean notSame = false;
+ int attempts = 0;
+ final String prefix = "getSymbol";
+
+ for (attempts = 0; attempts <= Short.MAX_VALUE; attempts++) {
+ final String name = prefix + String.valueOf(attempts);
+
+ final Symbol symbol1 = Symbol.getSymbol(name);
+ final Symbol symbol2 = Symbol.getSymbol(name);
+
+ if (symbol1 != symbol2) {
+ notSame = true;
+ break;
+ }
+ }
+
+ assertTrue(notSame);
+ assertTrue(attempts > 500);
+ assertTrue(attempts < 8193);
+ }
+
+ @Test
+ @Ignore("Cache is static so running has side effects.")
+ public void testGetSaslSymbolCachingMaxesOut() {
+ boolean notSame = false;
+ int attempts = 0;
+ final String prefix = "getSaslSymbol";
+
+ for (attempts = 0; attempts <= Short.MAX_VALUE; attempts++) {
+ final String name = prefix + String.valueOf(attempts);
+
+ final Symbol symbol1 = Symbol.getSaslSymbol(name);
+ final Symbol symbol2 = Symbol.getSaslSymbol(name);
+
+ if (symbol1 != symbol2) {
+ notSame = true;
+ break;
+ }
+ }
+
+ assertTrue(notSame);
+ assertTrue(attempts > 5);
+ assertTrue(attempts < 256);
+ }
+
+ @Test
+ public void testGetSymbolAndGetSaslSymbolUseDifferentCachesBothCache() {
+ final String symbolString = "Test-Sasl-Symbol-String";
+
+ // Try SASL first, so it caches
+ final Symbol saslSymbol1 = Symbol.getSaslSymbol(symbolString);
+ final Symbol saslSymbol2 = Symbol.getSaslSymbol(symbolString);
+
+ assertEquals(symbolString, saslSymbol1.toString());
+ assertEquals(symbolString, saslSymbol2.toString());
+
+ assertSame(saslSymbol1, saslSymbol2);
+
+ // Then try main, which never checks SASL cache.
+ final Symbol symbol3 = Symbol.getSymbol(symbolString);
+ final Symbol symbol4 = Symbol.getSymbol(symbolString);
+
+ assertEquals(symbolString, symbol3.toString());
+ assertEquals(symbolString, symbol4.toString());
+
+ assertSame(symbol3, symbol4);
+
+ assertNotSame(saslSymbol1, symbol3);
+ assertNotSame(saslSymbol2, symbol4);
+ }
+
+ @Test
+ public void testGetSymbolAndGetSaslSymbolUseDifferentCachesButSaslCanReuse() {
+ final String symbolString = "Test-Symbol-String";
+
+ // Try main first, so it caches
+ final Symbol symbol1 = Symbol.getSymbol(symbolString);
+ final Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+ assertEquals(symbolString, symbol1.toString());
+ assertEquals(symbolString, symbol2.toString());
+
+ assertSame(symbol1, symbol2);
+
+ // Then try SASL, which will first check the main cache to avoid dup.
+ final Symbol saslSymbol3 = Symbol.getSaslSymbol(symbolString);
+ final Symbol saslSymbol4 = Symbol.getSaslSymbol(symbolString);
+
+ assertEquals(symbolString, saslSymbol3.toString());
+ assertEquals(symbolString, saslSymbol4.toString());
+
+ assertSame(saslSymbol3, saslSymbol4);
+
+ assertSame(symbol1, saslSymbol3);
+ assertSame(symbol2, saslSymbol4);
+ }
+
+ @Test
+ public void testGetSymbolAndGetSaslSymbolUseDifferentCachesOnlyMainCached() {
+ final Symbol saslSymbol1 = Symbol.getSaslSymbol(MEDIUM_SYMBOL_VALUE);
+ final Symbol saslSymbol2 = Symbol.getSaslSymbol(MEDIUM_SYMBOL_VALUE);
+
+ assertEquals(MEDIUM_SYMBOL_VALUE, saslSymbol1.toString());
+ assertEquals(MEDIUM_SYMBOL_VALUE, saslSymbol2.toString());
+
+ assertNotSame(saslSymbol1, saslSymbol2); // Exceeded length for SASL cache
+
+ final Symbol symbol3 = Symbol.getSymbol(MEDIUM_SYMBOL_VALUE);
+ final Symbol symbol4 = Symbol.getSymbol(MEDIUM_SYMBOL_VALUE);
+
+ assertEquals(MEDIUM_SYMBOL_VALUE, symbol3.toString());
+ assertEquals(MEDIUM_SYMBOL_VALUE, symbol4.toString());
+
+ assertSame(symbol3, symbol4);
+
+ assertNotSame(saslSymbol1, symbol3);
+ assertNotSame(saslSymbol2, symbol4);
+ }
+}
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java
index f2154e32..ddeaf993 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java
@@ -19,10 +19,14 @@ package org.apache.qpid.proton.codec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.UUID;
+import org.apache.qpid.proton.amqp.DescribedType;
+import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.junit.Test;
@@ -65,7 +69,7 @@ public class AmqpValueTypeCodecTest extends CodecTestSupport {
encoder.writeObject(value);
}
- buffer.clear();
+ buffer.flip();
for (int i = 0; i < size; ++i) {
final Object result = decoder.readObject();
@@ -78,4 +82,143 @@ public class AmqpValueTypeCodecTest extends CodecTestSupport {
assertEquals(value.getValue(), decoded.getValue());
}
}
+
+ @Test
+ public void testDecodeAmqpValueContainingDescribedType() throws IOException {
+ final String descriptorValue = "UNKNOWN";
+ final byte[] descriptorBytes = descriptorValue.getBytes(StandardCharsets.US_ASCII);
+ final String describedString = "testDecodeAmqpValueContainingDescribedType";
+ final byte[] describedStringBytes = describedString.getBytes(StandardCharsets.UTF_8);
+
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put(EncodingCodes.SYM8); // Symbol
+ buffer.put((byte) descriptorBytes.length); // descriptor length
+ buffer.put(descriptorBytes); // descriptor value
+ buffer.put(EncodingCodes.STR8); // String
+ buffer.put((byte) describedStringBytes.length); // string length
+ buffer.put(describedStringBytes); // string value
+
+ buffer.flip();
+
+ final Object result = decoder.readObject();
+
+ assertNotNull(result);
+ assertTrue(result instanceof AmqpValue);
+
+ AmqpValue decoded = (AmqpValue) result;
+
+ assertTrue(decoded.getValue() instanceof DescribedType);
+ final DescribedType decodedDescribed = (DescribedType) decoded.getValue();
+ assertEquals(Symbol.valueOf(descriptorValue), decodedDescribed.getDescriptor());
+ assertEquals(describedString, decodedDescribed.getDescribed());
+ }
+
+ @Test
+ public void testDecodeAmqpValueContainingDescribedTypeNested() throws IOException {
+ final String descriptor1Value = "UNKNOWN1";
+ final byte[] descriptor1Bytes = descriptor1Value.getBytes(StandardCharsets.US_ASCII);
+ final String descriptor2Value = "UNKNOWN2";
+ final byte[] descriptor2Bytes = descriptor2Value.getBytes(StandardCharsets.US_ASCII);
+
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put(EncodingCodes.SYM8); // Symbol
+ buffer.put((byte) descriptor1Bytes.length); // descriptor length
+ buffer.put(descriptor1Bytes); // descriptor value
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put(EncodingCodes.SYM8); // Symbol
+ buffer.put((byte) descriptor2Bytes.length); // descriptor2 length
+ buffer.put(descriptor2Bytes); // descriptor2 value
+ buffer.put((byte) 0x45); // list0 primitive type constructor
+
+ buffer.flip();
+
+ try {
+ decoder.readObject();
+ fail("Expected exception");
+ } catch(DecodeException de) {
+ assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+ //expected
+ }
+ }
+
+ @Test
+ public void testDecodeAmqpValueContainingDescribedTypeNestedAmqpValue() throws IOException {
+ final String descriptorValue = "UNKNOWN1";
+ final byte[] descriptorBytes = descriptorValue.getBytes(StandardCharsets.US_ASCII);
+
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put(EncodingCodes.SYM8); // Symbol
+ buffer.put((byte) descriptorBytes.length); // descriptor length
+ buffer.put(descriptorBytes); // descriptor value
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put((byte) 0x45); // list0 primitive type constructor
+
+ buffer.flip();
+
+ try {
+ decoder.readObject();
+ fail("Expected exception");
+ } catch(DecodeException de) {
+ assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+ //expected
+ }
+ }
+
+ @Test
+ public void testDecodeAmqpValueContainingAmqpValueNested() throws IOException {
+ final String describedString = "testDecodeAmqpValueContainingAmqpValueNested";
+ final byte[] describedStringBytes = describedString.getBytes(StandardCharsets.UTF_8);
+
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put((byte) 0x00); // DescribedType
+ buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+ buffer.put((byte) 0x77); // amqp-value ulong descriptor
+ buffer.put(EncodingCodes.STR8); // String
+ buffer.put((byte) describedStringBytes.length); // string length
+ buffer.put(describedStringBytes); // string value
+
+ buffer.flip();
+ int limit = buffer.limit();
+
+ // We count amqp-value containing another described-type as having depth, so this is depth 2
+ decoder.setMaxDecodeDepth(1);
+ try {
+ decoder.readObject();
+ fail("Expected exception");
+ } catch(DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+ //expected
+ }
+
+ buffer.clear();
+ buffer.limit(limit);
+
+ // Try again with permissive depth limit
+ decoder.setMaxDecodeDepth(2);
+ Object result = decoder.readObject();
+
+ assertNotNull(result);
+ assertTrue(result instanceof AmqpValue);
+ final Object resultNested1 = ((AmqpValue)result).getValue();
+ assertTrue(resultNested1 instanceof AmqpValue);
+ final Object resultNested2 = ((AmqpValue)resultNested1).getValue();
+ assertTrue(resultNested2 instanceof AmqpValue);
+ assertEquals(describedString, ((AmqpValue) resultNested2).getValue());
+ }
}
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java
index 778b5090..6a8493ef 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java
@@ -19,12 +19,18 @@ package org.apache.qpid.proton.codec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
import java.util.Date;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Map.Entry;
+import org.apache.qpid.proton.amqp.UnsignedInteger;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.junit.Test;
import org.mockito.Mockito;
@@ -109,4 +115,197 @@ public class ApplicationPropertiesTypeTest extends CodecTestSupport {
// Each key should also try and reserve space for the String data
Mockito.verify(spy, Mockito.times(ENTRIES + 1)).ensureRemaining(Mockito.anyInt());
}
+
+ @Test
+ public void testDecodeApplicationProperties() throws Exception {
+ Object o = doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25, 6);
+ assertDecodedApplicationProperties(o);
+
+ o = doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, 6);
+ assertDecodedApplicationProperties(o);
+ }
+
+ @Test
+ public void testDecodeThrowsWithSizeBeyondAvailable() throws Exception {
+ try {
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25 + 1, 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map size 26 cannot be more than remaining bytes 25", de.getMessage());
+ }
+
+ try {
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28 + 1, 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map size 29 cannot be more than remaining bytes 28", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithNegativeSize() throws Exception {
+ try {
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, UnsignedInteger.MAX_VALUE.intValue(), 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map size -1 cannot be negative", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithNegativeCount() throws Exception {
+ try {
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, UnsignedInteger.MAX_VALUE.intValue());
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map element count -1 cannot be negative", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithCountLargerThanSize() throws Exception {
+ try {
+ // Deliberately tries not to decode the full map, so the 'remaining' checks dont foul
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 20, 22);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map element count 22 must be less than size (20)", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithOddCount() throws Exception {
+ try {
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25, 5);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map element count 5 cannot be odd", de.getMessage());
+ }
+
+ try {
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, 5);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Map element count 5 cannot be odd", de.getMessage());
+ }
+ }
+
+ private Object doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(final boolean map32, int size, int count) {
+ ByteBuffer tmpBuf = ByteBuffer.allocate(1024);
+ tmpBuf.put((byte) 0x00); // DescribedType
+ tmpBuf.put((byte) 0x53); // small-ulong
+ tmpBuf.put((byte) 0x74); // application-properties ulong descriptor
+ if (map32) {
+ tmpBuf.put((byte) (byte) 0xD1); // map32 primitive type constructor
+ tmpBuf.putInt(size); // size
+ tmpBuf.putInt(count); // count
+ } else {
+ tmpBuf.put((byte) (byte) 0xC1); // map8 primitive type constructor
+ tmpBuf.put((byte) size); // size
+ tmpBuf.put((byte) count); // count
+ }
+
+ tmpBuf.put((byte) 0xA1); // str8
+ tmpBuf.put((byte) 1); // length
+ tmpBuf.put((byte) 0x33); // "3"
+ tmpBuf.put((byte) 0x71); // int
+ tmpBuf.putInt(3);
+
+ tmpBuf.put((byte) 0xA1); // str8
+ tmpBuf.put((byte) 1); // length
+ tmpBuf.put((byte) 0x32); // "2"
+ tmpBuf.put((byte) 0x71); // int
+ tmpBuf.putInt(2);
+
+ tmpBuf.put((byte) 0xA1); // str8
+ tmpBuf.put((byte) 1); // length
+ tmpBuf.put((byte) 0x31); // "1"
+ tmpBuf.put((byte) 0x71); // int
+ tmpBuf.putInt(1);
+
+ tmpBuf.flip();
+
+ int oldLimit = tmpBuf.limit();
+
+ decoder.setByteBuffer(tmpBuf);
+
+ final Object decoded;
+ try
+ {
+ decoded = decoder.readObject();
+ }
+ finally
+ {
+ assertEquals(oldLimit, tmpBuf.limit());
+ }
+ assertEquals(oldLimit, tmpBuf.position());
+
+ return decoded;
+ }
+
+ private void assertDecodedApplicationProperties(Object o) {
+ assertTrue(o instanceof ApplicationProperties);
+ Map<String,Object> map = ((ApplicationProperties)o).getValue();
+ assertTrue(map instanceof LinkedHashMap);
+
+ assertEquals(3, map.size());
+ assertEquals(Integer.valueOf(3), map.get("3"));
+ assertEquals(Integer.valueOf(2), map.get("2"));
+ assertEquals(Integer.valueOf(1), map.get("1"));
+
+ Iterator<Map.Entry<String,Object>> iterator = map.entrySet().iterator();
+ assertTrue(iterator.hasNext());
+
+ Entry<String, Object> entry = iterator.next();
+ assertEquals("3", entry.getKey());
+ assertEquals(Integer.valueOf(3), entry.getValue());
+
+ assertTrue(iterator.hasNext());
+ entry = iterator.next();
+ assertEquals("2", entry.getKey());
+ assertEquals(Integer.valueOf(2), entry.getValue());
+
+ assertTrue(iterator.hasNext());
+ entry = iterator.next();
+ assertEquals("1", entry.getKey());
+ assertEquals(Integer.valueOf(1), entry.getValue());
+ }
+
+ @Test
+ public void testDecodeOverflowThrows() throws Exception {
+ try {
+ // Deliberately pass size for part map, but count for full map...will try to decode count, overflow size
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25 - 8, 6);
+ fail("Expected exception to be thrown");
+ } catch (BufferUnderflowException bue) {
+ // Expected
+ }
+
+ try {
+ // Deliberately pass size for part map, but count for full map...will try to decode count, overflow size
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28 - 8, 6);
+ fail("Expected exception to be thrown");
+ } catch (BufferUnderflowException bue) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testDecodeUnderflowThrows() throws Exception {
+ try {
+ // Deliberately pass size for full map, but count for part map...will try to decode count, underflow size
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25, 4);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("ApplicationProperties incorrectly encoded, left 8 bytes from size indicated 25", de.getMessage());
+ }
+
+ try {
+ // Deliberately pass size for full map, but count for part map...will try to decode count, underflow size
+ doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, 4);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("ApplicationProperties incorrectly encoded, left 8 bytes from size indicated 28", de.getMessage());
+ }
+ }
}
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java
index 9a85216e..5d31d9af 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java
@@ -21,18 +21,25 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
+import org.apache.qpid.proton.amqp.DescribedType;
import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedInteger;
+import org.apache.qpid.proton.amqp.UnsignedLong;
import org.junit.Test;
import org.mockito.Mockito;
@@ -319,9 +326,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
doEncodeDecodeByteArrayTestImpl(192);
}
+ @Test
+ public void testEncodeDecodeByteArray253() throws Throwable {
+ doEncodeDecodeByteArrayTestImpl(253);
+ }
+
+ @Test
+ public void testEncodeDecodeByteArray254() throws Throwable {
+ doEncodeDecodeByteArrayTestImpl(254);
+ }
+
@Test
public void testEncodeDecodeByteArray384() throws Throwable {
- // byte array32
doEncodeDecodeByteArrayTestImpl(384);
}
@@ -338,7 +354,7 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -397,38 +413,334 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
return payload;
}
+ @Test
+ public void testEncodeDecodeBooleanArray0() throws Throwable {
+ // boolean array8 size 0
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(0));
+ }
+
@Test
public void testEncodeDecodeBooleanArray100() throws Throwable {
// boolean array8 less than 128 bytes
- doEncodeDecodeBooleanArrayTestImpl(100);
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(100));
}
@Test
public void testEncodeDecodeBooleanArray192() throws Throwable {
// boolean array8 greater than 128 bytes
- doEncodeDecodeBooleanArrayTestImpl(192);
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(192));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArray253() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(253));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArray254() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(254));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArray255() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(255));
}
@Test
public void testEncodeDecodeBooleanArray384() throws Throwable {
- // boolean array32
- doEncodeDecodeBooleanArrayTestImpl(384);
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(384));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayTrue100() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 100));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayTrue192() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 192));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayTrue253() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 253));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayTrue254() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 254));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayTrue255() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 255));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayTrue384() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 384));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayFalse100() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 100));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayFalse192() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 192));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayFalse253() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 253));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayFalse254() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 254));
}
- private void doEncodeDecodeBooleanArrayTestImpl(int count) throws Throwable {
- boolean[] source = createPayloadArrayBooleans(count);
+ @Test
+ public void testEncodeDecodeBooleanArrayFalse255() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 255));
+ }
+
+ @Test
+ public void testEncodeDecodeBooleanArrayFalse384() throws Throwable {
+ doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 384));
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse0() throws Throwable {
+ Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 0, false);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(boolean.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new boolean[0], (boolean[]) decoded);
+ }
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse0Array32() throws Throwable {
+ Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 0, false);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(boolean.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new boolean[0], (boolean[]) decoded);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse255() throws Throwable {
try {
- assertEquals("Unexpected source array length", count, source.length);
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 255, false);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
- int encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse255WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(true, 255, false);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse255Array32() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 255, false);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse255Array32WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 255, false);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse256() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 256, false);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse256WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 256, false);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyFalse384() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 384, false);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue0() throws Throwable {
+ Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 0, true);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(boolean.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new boolean[0], (boolean[]) decoded);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue0Array32() throws Throwable {
+ Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 0, true);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(boolean.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new boolean[0], (boolean[]) decoded);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue10() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 10, true);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue10WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(true, 10, true);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue10Array32() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 10, true);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue10Array32WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 10, true);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue255() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 255, true);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue255WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(true, 255, true);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue255Array32() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 255, true);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue255Array32WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 255, true);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue256() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 256, true);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue256WhenAllowed() throws Throwable {
+ doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 256, true);
+ }
+
+ @Test
+ public void testDecodeBooleanArrayExplicitlyTrue384() throws Throwable {
+ try {
+ doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 384, true);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ private void doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(boolean array8, final int count, final boolean type) throws Throwable {
+ decoder.setZeroWidthArrayElementLimit(count);
+ Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(array8, count, type);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(boolean.class, decoded.getClass().getComponentType());
+
+ final boolean[] expected = new boolean[count];
+ Arrays.fill(expected, type);
+
+ assertArrayEquals(expected, (boolean[]) decoded);
+ }
+
+ private Object doDecodeBooleanArrayTrueFalseOnlyTestImpl(boolean array8, final int count, final boolean type) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode boolean-true/boolean-false array8 with count > 255", count <= 255);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+ // Write the type descriptor
+ buffer.put(type ? (byte) EncodingCodes.BOOLEAN_TRUE : EncodingCodes.BOOLEAN_FALSE);
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ return decoder.readObject();
+ }
+
+ private void doEncodeDecodeBooleanArrayTestImpl(final boolean[] source) throws Throwable {
+ final int count = source.length;
+
+ try {
+ int encodingWidth = source.length < 254 ? 1 : 4; // less than 254 and not 256, since we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
int arrayPayloadSize = encodingWidth + 1 + count; // variable width for element count + byte type descriptor + number of elements
int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
byte[] expectedEncoding = new byte[expectedEncodedArraySize];
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -449,18 +761,15 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
- // Now verify against the actual encoding of the array
+ // Now produce actual encoding of the array for later comparison
assertEquals("Unexpected buffer position", 0, buffer.position());
encoder.writeArray(source);
- assertEquals("Unexpected encoded payload length", expectedEncodedArraySize, buffer.position());
- byte[] actualEncoding = new byte[expectedEncodedArraySize];
+ byte[] actualEncoding = new byte[buffer.position()];
buffer.flip();
buffer.get(actualEncoding);
assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
- assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
-
// Now verify against the decoding
buffer.flip();
Object decoded = decoder.readObject();
@@ -470,6 +779,10 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
assertEquals(boolean.class, decoded.getClass().getComponentType());
assertArrayEquals("Unexpected decoding", source, (boolean[]) decoded);
+
+ // Now verify expected encoding against the actual encoding of the array
+ assertEquals("Unexpected encoded payload length", expectedEncodedArraySize, actualEncoding.length);
+ assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
}
catch (Throwable t) {
System.err.println("Error during test, source array: " + Arrays.toString(source));
@@ -488,6 +801,15 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
return payload;
}
+ private static boolean[] createPayloadArrayBoolean(final boolean values, final int length) {
+ boolean[] payload = new boolean[length];
+ for (int i = 0; i < length; i++) {
+ payload[i] = values;
+ }
+
+ return payload;
+ }
+
@Test
public void testEncodeDecodeShortArray50() throws Throwable {
// short array8 less than 128 bytes
@@ -500,9 +822,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
doEncodeDecodeShortArrayTestImpl(100);
}
+ @Test
+ public void testEncodeDecodeShortArray126() throws Throwable {
+ doEncodeDecodeShortArrayTestImpl(126);
+ }
+
+ @Test
+ public void testEncodeDecodeShortArray127() throws Throwable {
+ doEncodeDecodeShortArrayTestImpl(127);
+ }
+
@Test
public void testEncodeDecodeShortArray384() throws Throwable {
- // short array32
doEncodeDecodeShortArrayTestImpl(384);
}
@@ -519,7 +850,7 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -590,45 +921,126 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
doEncodeDecodeIntArrayTestImpl(50, false);
}
+ @Test
+ public void testEncodeDecodeIntArray63() throws Throwable {
+ doEncodeDecodeIntArrayTestImpl(63, false);
+ }
+
+ @Test
+ public void testEncodeDecodeIntArray64() throws Throwable {
+ doEncodeDecodeIntArrayTestImpl(64, false);
+ }
+
@Test
public void testEncodeDecodeIntArray384() throws Throwable {
- // int array32
doEncodeDecodeIntArrayTestImpl(384, false);
}
+ @Test
+ public void testEncodeDecodeSmallIntArray63() throws Throwable {
+ doEncodeDecodeIntArrayTestImpl(63, true);
+ }
+
+ @Test
+ public void testEncodeDecodeSmallIntArray64() throws Throwable {
+ doEncodeDecodeIntArrayTestImpl(64, true);
+ }
+
@Test
public void testEncodeDecodeSmallIntArray100() throws Throwable {
- // small-int array8 less than 128 bytes
doEncodeDecodeIntArrayTestImpl(100, true);
}
@Test
public void testEncodeDecodeSmallIntArray192() throws Throwable {
- // small-int array8 greater than 128 bytes
doEncodeDecodeIntArrayTestImpl(192, true);
}
@Test
public void testEncodeDecodeSmallIntArray384() throws Throwable {
- // small-int array32
doEncodeDecodeIntArrayTestImpl(384, true);
}
+ @Test
+ public void testDecodeSmallIntArray253() throws Throwable {
+ final int count = 253;
+ doDecodeSmallLongArrayTestImpl(true, count, createPayloadArrayInts(count, true));
+ buffer.clear();
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallIntArray254() throws Throwable {
+ final int count = 254;
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallIntArray255() throws Throwable {
+ final int count = 255;
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallIntArray384() throws Throwable {
+ final int count = 384;
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+ }
+
+ private void doDecodeSmallLongArrayTestImpl(final boolean array8, final int count, int[] smallInts) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode small-int array8 with count " + count, count <= 253);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+
+ if (array8) {
+ assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+ }
+
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x54); // 'small-int' type descriptor code
+ for(int element : smallInts) {
+ assertTrue(element <= 127 && element >= -128);
+
+ buffer.put((byte) element);
+ }
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ Object o = decoder.readObject();
+
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+ assertEquals(int.class, o.getClass().getComponentType());
+
+ int[] decoded = (int[]) o;
+ assertArrayEquals(smallInts, decoded);
+ }
+
private void doEncodeDecodeIntArrayTestImpl(int count, boolean smallInt) throws Throwable {
int[] source = createPayloadArrayInts(count, smallInt);
try {
assertEquals("Unexpected source array length", count, source.length);
- int encodingWidth;
- int elementWidth;
- if(smallInt) {
- elementWidth = 1;
- encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since each element is 1 byte, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
- } else {
- elementWidth = 4;
- encodingWidth = count < 63 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
- }
+ int elementWidth = 4;
+ int encodingWidth = count < 64 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
int arrayPayloadSize = encodingWidth + 1 + (count * elementWidth); // variable width for element count + byte type descriptor + (number of elements * size)
int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
@@ -636,7 +1048,7 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -647,25 +1059,15 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
}
// Write the type descriptor
- if(smallInt) {
- expectedEncodingWrapper.put((byte) 0x54); // 'small int' type descriptor code
- } else {
- expectedEncodingWrapper.put((byte) 0x71); // 'int' type descriptor code
- }
+ expectedEncodingWrapper.put((byte) 0x71); // 'int' type descriptor code
// Write the elements
for (int i = 0; i < count; i++) {
int j = source[i];
- if(smallInt) {
- // Using put as small int is 1 byte
- assertTrue("Unexpected value: + j", j <= 127 && j >=-128);
- expectedEncodingWrapper.put((byte) j);
- } else {
- expectedEncodingWrapper.putInt(j);
- }
+ expectedEncodingWrapper.putInt(j);
}
- assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
+ assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
// Now verify against the actual encoding of the array
assertEquals("Unexpected buffer position", 0, buffer.position());
@@ -712,66 +1114,140 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
@Test
public void testEncodeDecodeLongArray10() throws Throwable {
- // long array8 less than 128 bytes
doEncodeDecodeLongArrayTestImpl(10, false);
}
@Test
- public void testEncodeDecodeLongArray25() throws Throwable {
- // long array8 greater than 128 bytes
- doEncodeDecodeLongArrayTestImpl(25, false);
+ public void testEncodeDecodeLongArray31() throws Throwable {
+ doEncodeDecodeLongArrayTestImpl(31, false);
+ }
+
+ @Test
+ public void testEncodeDecodeLongArray32() throws Throwable {
+ doEncodeDecodeLongArrayTestImpl(32, false);
}
@Test
public void testEncodeDecodeLongArray384() throws Throwable {
- // long array32
doEncodeDecodeLongArrayTestImpl(384, false);
}
+ @Test
+ public void testEncodeDecodeSmallLongArray31() throws Throwable {
+ doEncodeDecodeLongArrayTestImpl(30, true);
+ }
+
+ @Test
+ public void testEncodeDecodeSmallLongArray32() throws Throwable {
+ doEncodeDecodeLongArrayTestImpl(30, true);
+ }
+
@Test
public void testEncodeDecodeSmallLongArray100() throws Throwable {
- // small-long array8 less than 128 bytes
doEncodeDecodeLongArrayTestImpl(100, true);
}
@Test
public void testEncodeDecodeSmallLongArray192() throws Throwable {
- // small-long array8 greater than 128 bytes
doEncodeDecodeLongArrayTestImpl(192, true);
}
@Test
public void testEncodeDecodeSmallLongArray384() throws Throwable {
- // small-long array32
doEncodeDecodeLongArrayTestImpl(384, true);
}
- private void doEncodeDecodeLongArrayTestImpl(int count, boolean smallLong) throws Throwable {
- long[] source = createPayloadArrayLongs(count, smallLong);
+ @Test
+ public void testDecodeSmallLongArray253() throws Throwable {
+ final int count = 253;
+ doDecodeSmallLongArrayTestImpl(true, count, createPayloadArrayLongs(count, true));
+ buffer.clear();
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+ }
- try {
- assertEquals("Unexpected source array length", count, source.length);
+ @Test
+ public void testDecodeSmallLongArray254() throws Throwable {
+ final int count = 254;
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+ }
- int encodingWidth;
- int elementWidth;
- if(smallLong) {
- elementWidth = 1;
- encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since each element is 1 byte, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
- } else {
- elementWidth = 8;
- encodingWidth = count < 31 ? 1 : 4; // less than 31, since each element is 8 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
- }
+ @Test
+ public void testDecodeSmallLongArray255() throws Throwable {
+ final int count = 255;
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+ }
- int arrayPayloadSize = encodingWidth + 1 + (count * elementWidth); // variable width for element count + byte type descriptor + (number of elements * size)
- int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
- byte[] expectedEncoding = new byte[expectedEncodedArraySize];
- ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+ @Test
+ public void testDecodeSmallLongArray384() throws Throwable {
+ final int count = 384;
+ doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+ }
- // Write the array encoding code, array size, and element count
- if(count < 254) {
- expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
- expectedEncodingWrapper.put((byte) arrayPayloadSize);
- expectedEncodingWrapper.put((byte) count);
+ private void doDecodeSmallLongArrayTestImpl(final boolean array8, final int count, long[] smallLongs) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode small-long array8 with count " + count, count <= 253);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+
+ if (array8) {
+ assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+ }
+
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x55); // 'small-long' type descriptor code
+ for(long element : smallLongs) {
+ assertTrue(element <= 127 && element >= -128);
+
+ buffer.put((byte) element);
+ }
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ Object o = decoder.readObject();
+
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+ assertEquals(long.class, o.getClass().getComponentType());
+
+ long[] decoded = (long[]) o;
+ assertArrayEquals(smallLongs, decoded);
+ }
+
+ private void doEncodeDecodeLongArrayTestImpl(int count, boolean smallLong) throws Throwable {
+ long[] source = createPayloadArrayLongs(count, smallLong);
+
+ try {
+ assertEquals("Unexpected source array length", count, source.length);
+
+ int elementWidth = 8;
+ int encodingWidth = count < 32 ? 1 : 4; // less than 32, since each element is 8 bytes, but we also need 1 for element count, and (in this case) 1 byte for primitive element type constructor.
+
+ int arrayPayloadSize = encodingWidth + 1 + (count * elementWidth); // variable width for element count + byte type descriptor + (number of elements * size)
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
+ byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+ ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+ // Write the array encoding code, array size, and element count
+ if(encodingWidth == 1) {
+ expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+ expectedEncodingWrapper.put((byte) arrayPayloadSize);
+ expectedEncodingWrapper.put((byte) count);
} else {
expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
expectedEncodingWrapper.putInt(arrayPayloadSize);
@@ -779,25 +1255,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
}
// Write the type descriptor
- if(smallLong) {
- expectedEncodingWrapper.put((byte) 0x55); // 'small long' type descriptor code
- } else {
- expectedEncodingWrapper.put((byte) 0x81); // 'long' type descriptor code
- }
+ expectedEncodingWrapper.put((byte) 0x81); // 'long' type descriptor code
// Write the elements
for (int i = 0; i < count; i++) {
- long j = source[i];
- if(smallLong) {
- // Using put as small long is 1 byte
- assertTrue("Unexpected value: + j", j <= 127 && j >=-128);
- expectedEncodingWrapper.put((byte) j);
- } else {
- expectedEncodingWrapper.putLong(j);
- }
+ expectedEncodingWrapper.putLong(source[i]);
}
- assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
+ assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
// Now verify against the actual encoding of the array
assertEquals("Unexpected buffer position", 0, buffer.position());
@@ -854,9 +1319,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
doEncodeDecodeFloatArrayTestImpl(50);
}
+ @Test
+ public void testEncodeDecodeFloatArray63() throws Throwable {
+ doEncodeDecodeFloatArrayTestImpl(63);
+ }
+
+ @Test
+ public void testEncodeDecodeFloatArray64() throws Throwable {
+ doEncodeDecodeFloatArrayTestImpl(64);
+ }
+
@Test
public void testEncodeDecodeFloatArray384() throws Throwable {
- // float array32
doEncodeDecodeFloatArrayTestImpl(384);
}
@@ -866,14 +1340,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
try {
assertEquals("Unexpected source array length", count, source.length);
- int encodingWidth = count < 63 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+ int encodingWidth = count < 64 ? 1 : 4; // less than 64, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
int arrayPayloadSize = encodingWidth + 1 + (count * 4); // variable width for element count + byte type descriptor + (number of elements * size)
int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
byte[] expectedEncoding = new byte[expectedEncodedArraySize];
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -944,9 +1418,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
doEncodeDecodeDoubleArrayTestImpl(25);
}
+ @Test
+ public void testEncodeDecodeDoubleArray31() throws Throwable {
+ doEncodeDecodeDoubleArrayTestImpl(31);
+ }
+
+ @Test
+ public void testEncodeDecodeDoubleArray32() throws Throwable {
+ doEncodeDecodeDoubleArrayTestImpl(32);
+ }
+
@Test
public void testEncodeDecodeDoubleArray384() throws Throwable {
- // double array32
doEncodeDecodeDoubleArrayTestImpl(384);
}
@@ -956,14 +1439,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
try {
assertEquals("Unexpected source array length", count, source.length);
- int encodingWidth = count < 31 ? 1 : 4; // less than 31, since each element is 8 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+ int encodingWidth = count < 32 ? 1 : 4; // less than 32, since each element is 8 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
int arrayPayloadSize = encodingWidth + 1 + (count * 8); // variable width for element count + byte type descriptor + (number of elements * size)
int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
byte[] expectedEncoding = new byte[expectedEncodedArraySize];
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -1034,9 +1517,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
doEncodeDecodeCharArrayTestImpl(50);
}
+ @Test
+ public void testEncodeDecodeCharArray63() throws Throwable {
+ doEncodeDecodeCharArrayTestImpl(63);
+ }
+
+ @Test
+ public void testEncodeDecodeCharArray64() throws Throwable {
+ doEncodeDecodeCharArrayTestImpl(64);
+ }
+
@Test
public void testEncodeDecodeCharArray384() throws Throwable {
- // char array32
doEncodeDecodeCharArrayTestImpl(384);
}
@@ -1046,14 +1538,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
try {
assertEquals("Unexpected source array length", count, source.length);
- int encodingWidth = count < 63 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+ int encodingWidth = count < 64 ? 1 : 4; // less than 64, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
int arrayPayloadSize = encodingWidth + 1 + (count * 4); // variable width for element count + byte type descriptor + (number of elements * size)
int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
byte[] expectedEncoding = new byte[expectedEncodedArraySize];
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -1113,37 +1605,125 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
}
@Test
- public void testEncodeDecodeSmallSymbolArray50() throws Throwable {
- // sym8 array8 less than 128 bytes
- doEncodeDecodeSmallSymbolArrayTestImpl(50);
+ public void testEncodeDecodeSymbolArray50() throws Throwable {
+ doEncodeDecodeSymbolArrayTestImpl(50);
+ }
+
+ @Test
+ public void testEncodeDecodeSymbolArray100() throws Throwable {
+ doEncodeDecodeSymbolArrayTestImpl(100);
+ }
+
+ @Test
+ public void testEncodeDecodeSymbolArray384() throws Throwable {
+ doEncodeDecodeSymbolArrayTestImpl(384);
+ }
+
+ @Test
+ public void testDecodeSmallSymbolArray126() throws Throwable {
+ //Using 126 with single-char symbol since element length bytes will use up the array size
+ final int count = 126;
+ doDecodeSmallSymbolArrayTestImpl(true, count, Symbol.valueOf("a"));
+ buffer.clear();
+ doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
+ }
+
+ @Test
+ public void testDecodeSmallSymbolArray253() throws Throwable {
+ //Using 253 with empty symbol since element length bytes will use up the array size
+ final int count = 253;
+ doDecodeSmallSymbolArrayTestImpl(true, count, Symbol.valueOf(""));
+ buffer.clear();
+ doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf(""));
+ }
+
+ @Test
+ public void testDecodeSmallSymbolArray254() throws Throwable {
+ final int count = 254;
+ doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
}
@Test
- public void testEncodeDecodeSmallSymbolArray100() throws Throwable {
- // sym8 array8 greater than 128 bytes
- doEncodeDecodeSmallSymbolArrayTestImpl(100);
+ public void testDecodeSmallSymbolArray255() throws Throwable {
+ final int count = 255;
+ doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
}
@Test
- public void testEncodeDecodeSmallSymbolArray384() throws Throwable {
- // sym8 array32
- doEncodeDecodeSmallSymbolArrayTestImpl(384);
+ public void testDecodeSmallSymbolArray384() throws Throwable {
+ final int count = 384;
+ doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
+ }
+
+ private void doDecodeSmallSymbolArrayTestImpl(final boolean array8, final int count, Symbol smallSymbol) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode sym8 array8 with count " + count, count <= 253);
+ assertTrue("Individual sym8 in array8 cant exceed 253 chars", smallSymbol.length() <= 253);
+ } else {
+ assertTrue("Individual sym8 in array32 cant exceed 255 chars", smallSymbol.length() <= 255);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1 + (count * (1 + smallSymbol.length())); // variable width for element count + byte type descriptor + (elements = count * (length-byte + characters))
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+
+ if (array8) {
+ assertTrue("Cant encode array8 with arrayPayloadSize " + arrayPayloadSize, arrayPayloadSize <= 256);
+ }
+
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0xA3); // 'sym8' type descriptor code
+ for (int i = 0; i < count; i++) {
+ buffer.put((byte) smallSymbol.length());
+
+ buffer.put(smallSymbol.toString().getBytes(StandardCharsets.US_ASCII));
+ }
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ Object o = decoder.readObject();
+
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+ assertEquals(Symbol.class, o.getClass().getComponentType());
+
+ Symbol[] decoded = (Symbol[]) o;
+
+ Symbol[] expected = new Symbol[count];
+ for(int i = 0; i < count; i++) {
+ expected[i] = smallSymbol;
+ }
+
+ assertArrayEquals(expected, decoded);
}
- private void doEncodeDecodeSmallSymbolArrayTestImpl(int count) throws Throwable {
+ private void doEncodeDecodeSymbolArrayTestImpl(int count) throws Throwable {
Symbol[] source = createPayloadArraySmallSymbols(count);
try {
assertEquals("Unexpected source array length", count, source.length);
- int encodingWidth = count < 127 ? 1 : 4; // less than 127, since each element is 2 bytes (1 length, 1 content char), but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
- int arrayPayloadSize = encodingWidth + 1 + (count * 2); // variable width for element count + byte type descriptor + (number of elements * size[=length+content-char])
+ int encodingWidth = count < 51 ? 1 : 4; // less than 51, since each element is 5 bytes (4 length, 1 content char), but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+ int arrayPayloadSize = encodingWidth + 1 + (count * 5); // variable width for element count + byte type descriptor + (number of elements * size[=length+content-char])
int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload
byte[] expectedEncoding = new byte[expectedEncodedArraySize];
ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
// Write the array encoding code, array size, and element count
- if(count < 254) {
+ if(encodingWidth == 1) {
expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
expectedEncodingWrapper.put((byte) arrayPayloadSize);
expectedEncodingWrapper.put((byte) count);
@@ -1154,18 +1734,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
}
// Write the type descriptor
- expectedEncodingWrapper.put((byte) 0xA3); // 'sym8' type descriptor code
+ expectedEncodingWrapper.put((byte) 0xB3); // 'sym32' type descriptor code
// Write the elements
for (int i = 0; i < count; i++) {
Symbol symbol = source[i];
assertEquals("Unexpected length", 1, symbol.length());
- expectedEncodingWrapper.put((byte) 1); // Length
+ expectedEncodingWrapper.putInt(1); // Length
expectedEncodingWrapper.put((byte) symbol.toString().charAt(0)); // Content
}
- assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
+ assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
// Now verify against the actual encoding of the array
assertEquals("Unexpected buffer position", 0, buffer.position());
@@ -1205,4 +1785,1722 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
return payload;
}
+
+ @Test
+ public void testEncodeDecodeULongArray0() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(0));
+ }
+
+ @Test
+ public void testEncodeDecodeULongArray31() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(31));
+ }
+
+ @Test
+ public void testEncodeDecodeULongArray32() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(32));
+ }
+
+ @Test
+ public void testEncodeDecodeULongArray100() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(100));
+ }
+
+ @Test
+ public void testEncodeDecodeULong0Array0() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULong(UnsignedLong.ZERO, 0));
+ }
+
+ @Test
+ public void testEncodeDecodeULong0Array100() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULong(UnsignedLong.ZERO, 100));
+ }
+
+ @Test
+ public void testEncodeDecodeULongArray384() throws Throwable {
+ doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(384));
+ }
+
+ private void doEncodeDecodeULongArrayTestImpl(final UnsignedLong[] source) throws Throwable {
+ final int count = source.length;
+
+ try {
+ final int encodingWidth = count < 32 ? 1 : 4;
+ final int arrayPayloadSize = encodingWidth + 1 + (8 * count); // count + element type descriptor + (element-width * number of elements)
+ final int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + size + encoded payload
+ final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+ ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+ // Write the array encoding code, array size, and element count
+ if(encodingWidth == 1) {
+ expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+ expectedEncodingWrapper.put((byte) arrayPayloadSize);
+ expectedEncodingWrapper.put((byte) count);
+ } else {
+ expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+ expectedEncodingWrapper.putInt(arrayPayloadSize);
+ expectedEncodingWrapper.putInt(count);
+ }
+
+ // Write the type descriptor
+ expectedEncodingWrapper.put((byte) 0x80); // 'ulong' type descriptor code
+
+ // Write the elements
+ for (int i = 0; i < count; i++) {
+ expectedEncodingWrapper.putLong(source[i].longValue());
+ }
+
+ assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
+
+ // Now produce actual encoding of the array for later comparison
+ assertEquals("Unexpected buffer position", 0, buffer.position());
+ encoder.writeArray(source);
+
+ byte[] actualEncoding = new byte[buffer.position()];
+ buffer.flip();
+ buffer.get(actualEncoding);
+ assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+ // Now verify against the decoding
+ buffer.flip();
+ Object decoded = decoder.readObject();
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertFalse(decoded.getClass().getComponentType().isPrimitive());
+ assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+
+ assertArrayEquals("Unexpected decoding", source, (UnsignedLong[]) decoded);
+
+ // Now verify expected encoding against the actual encoding of the array
+ assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+ assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+ }
+ catch (Throwable t) {
+ System.err.println("Error during test, source array: " + Arrays.toString(source));
+ throw t;
+ }
+ }
+
+ private static UnsignedLong[] createPayloadArrayULongs(int length) {
+ return createPayloadArrayULongs(length, false);
+ }
+
+ private static UnsignedLong[] createPayloadArrayULongs(int length, boolean smallUlongs) {
+ Random rand = new Random(System.currentTimeMillis());
+
+ UnsignedLong[] payload = new UnsignedLong[length];
+ for (int i = 0; i < length; i++) {
+ final long val;
+ if(smallUlongs) {
+ val = (long) rand.nextInt(256);
+ } else {
+ val = (long) rand.nextInt();
+ }
+
+ payload[i] = UnsignedLong.valueOf(val);
+ }
+
+ return payload;
+ }
+
+ private static UnsignedLong[] createPayloadArrayULong(final UnsignedLong value, final int length) {
+ UnsignedLong[] payload = new UnsignedLong[length];
+ for (int i = 0; i < length; i++) {
+ payload[i] = value;
+ }
+
+ return payload;
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only0() throws Throwable {
+ Object decoded = doDecodeULongArrayUlong0OnlyTestImpl(true, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new UnsignedLong[0], (UnsignedLong[]) decoded);
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only0Array32() throws Throwable {
+ Object decoded = doDecodeULongArrayUlong0OnlyTestImpl(false, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new UnsignedLong[0], (UnsignedLong[]) decoded);
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only255() throws Throwable {
+ try {
+ doDecodeULongArrayUlong0OnlyTestImpl(true, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only255WhenAllowed() throws Throwable {
+ doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(true, 255);
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only255Array32() throws Throwable {
+ try {
+ doDecodeULongArrayUlong0OnlyTestImpl(false, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only255Array32WhenAllowed() throws Throwable {
+ doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(false, 255);
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only256() throws Throwable {
+ try {
+ doDecodeULongArrayUlong0OnlyTestImpl(false, 256);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only256WhenAllowed() throws Throwable {
+ doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(false, 256);
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only384() throws Throwable {
+ try {
+ doDecodeULongArrayUlong0OnlyTestImpl(false, 384);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeULongArrayExplicitlyUlong0Only384WhenAllowed() throws Throwable {
+ doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(false, 256);
+ }
+
+ private Object doDecodeULongArrayUlong0OnlyTestImpl(final boolean array8, final int count) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode ulong0 array8 with count > 255", count <= 255);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x44); // 'ulong0' type descriptor code
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ return decoder.readObject();
+ }
+
+ private void doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(final boolean array8, final int count) throws Exception {
+ decoder.setZeroWidthArrayElementLimit(count);
+ Object decoded = doDecodeULongArrayUlong0OnlyTestImpl(array8, count);
+
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+
+ UnsignedLong[] expected = new UnsignedLong[count];
+ for (int i = 0; i < count; i++) {
+ expected[i] = UnsignedLong.ZERO;
+ }
+ assertArrayEquals(expected, (UnsignedLong[]) decoded);
+ }
+
+ @Test
+ public void testDecodeSmallULongArray253() throws Throwable {
+ final int count = 253;
+ doDecodeSmallULongArrayTestImpl(true, count, createPayloadArrayULongs(count, true));
+ buffer.clear();
+ doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallULongArray254() throws Throwable {
+ final int count = 254;
+ doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallULongArray255() throws Throwable {
+ final int count = 255;
+ doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallULongArray384() throws Throwable {
+ final int count = 384;
+ doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+ }
+
+ private void doDecodeSmallULongArrayTestImpl(final boolean array8, final int count, UnsignedLong[] smallULongs) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode small-ulong array8 with count " + count, count <= 253);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+
+ if (array8) {
+ assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+ }
+
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x53); // 'small-ulong' type descriptor code
+ for(UnsignedLong element : smallULongs) {
+ assertTrue(element.longValue() <= 255 && element.longValue() >= 0);
+
+ buffer.put((byte) element.longValue());
+ }
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ Object o = decoder.readObject();
+
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+ assertEquals(UnsignedLong.class, o.getClass().getComponentType());
+ final UnsignedLong[] decoded = (UnsignedLong[]) o;
+
+ assertArrayEquals(smallULongs, decoded);
+ }
+
+ @Test
+ public void testEncodeDecodeUIntArray0() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(0));
+ }
+
+ @Test
+ public void testEncodeDecodeUIntArray63() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(63));
+ }
+
+ @Test
+ public void testEncodeDecodeUIntArray64() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(64));
+ }
+
+ @Test
+ public void testEncodeDecodeUIntArray100() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(100));
+ }
+
+ @Test
+ public void testEncodeDecodeUInt0Array0() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInt(UnsignedInteger.ZERO, 0));
+ }
+
+ @Test
+ public void testEncodeDecodeUInt0Array100() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInt(UnsignedInteger.ZERO, 100));
+ }
+
+ @Test
+ public void testEncodeDecodeUIntArray384() throws Throwable {
+ doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(384));
+ }
+
+ @Test
+ public void testDecodeSmallUIntArray253() throws Throwable {
+ final int count = 253;
+ doDecodeSmallUIntArrayTestImpl(true, count, createPayloadArrayUInts(count, true));
+ buffer.clear();
+ doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallUIntArray254() throws Throwable {
+ final int count = 254;
+ doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallUIntArray255() throws Throwable {
+ final int count = 255;
+ doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+ }
+
+ @Test
+ public void testDecodeSmallUIntArray384() throws Throwable {
+ final int count = 384;
+ doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+ }
+
+ private void doDecodeSmallUIntArrayTestImpl(final boolean array8, final int count, UnsignedInteger[] smallUInts) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode small-uint array8 with count " + count, count <= 253);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+
+ if (array8) {
+ assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+ }
+
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x52); // 'small-uint' type descriptor code
+ for(UnsignedInteger element : smallUInts) {
+ assertTrue(element.intValue() <= 255 && element.intValue() >= 0);
+
+ buffer.put((byte) element.intValue());
+ }
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ Object o = decoder.readObject();
+
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+ assertEquals(UnsignedInteger.class, o.getClass().getComponentType());
+ final UnsignedInteger[] decoded = (UnsignedInteger[]) o;
+
+ assertArrayEquals(smallUInts, decoded);
+ }
+
+ private void doEncodeDecodeUIntArrayTestImpl(final UnsignedInteger[] source) throws Throwable {
+ final int count = source.length;
+
+ try {
+ final int encodingWidth = count < 64 ? 1 : 4; // less than 64, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+ final int arrayPayloadSize = encodingWidth + 1 + (4 * count); // count + element type descriptor + (element-width * number of elements)
+ final int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + size + encoded payload
+ final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+ ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+ // Write the array encoding code, array size, and element count
+ if(encodingWidth == 1) {
+ expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+ expectedEncodingWrapper.put((byte) arrayPayloadSize);
+ expectedEncodingWrapper.put((byte) count);
+ } else {
+ expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+ expectedEncodingWrapper.putInt(arrayPayloadSize);
+ expectedEncodingWrapper.putInt(count);
+ }
+
+ // Write the type descriptor
+ expectedEncodingWrapper.put((byte) 0x70); // 'uint' type descriptor code
+
+ // Write the elements
+ for (int i = 0; i < count; i++) {
+ expectedEncodingWrapper.putInt(source[i].intValue());
+ }
+
+ assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
+
+ // Now produce actual encoding of the array for later comparison
+ assertEquals("Unexpected buffer position", 0, buffer.position());
+ encoder.writeArray(source);
+
+ byte[] actualEncoding = new byte[buffer.position()];
+ buffer.flip();
+ buffer.get(actualEncoding);
+ assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+ // Now verify against the decoding
+ buffer.flip();
+ Object decoded = decoder.readObject();
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertFalse(decoded.getClass().getComponentType().isPrimitive());
+ assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+
+ assertArrayEquals("Unexpected decoding", source, (UnsignedInteger[]) decoded);
+
+ // Now verify expected encoding against the actual encoding of the array
+ assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+ assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+ }
+ catch (Throwable t) {
+ System.err.println("Error during test, source array: " + Arrays.toString(source));
+ throw t;
+ }
+ }
+
+ private static UnsignedInteger[] createPayloadArrayUInts(int length) {
+ return createPayloadArrayUInts(length, false);
+ }
+
+ private static UnsignedInteger[] createPayloadArrayUInts(int length, boolean smallUints) {
+ Random rand = new Random(System.currentTimeMillis());
+
+ UnsignedInteger[] payload = new UnsignedInteger[length];
+ for (int i = 0; i < length; i++) {
+ final int val;
+ if(smallUints) {
+ val = rand.nextInt(256);
+ } else {
+ val = rand.nextInt();
+ }
+
+ payload[i] = UnsignedInteger.valueOf(val);
+ }
+
+ return payload;
+ }
+
+ private static UnsignedInteger[] createPayloadArrayUInt(final UnsignedInteger value, final int length) {
+ UnsignedInteger[] payload = new UnsignedInteger[length];
+ for (int i = 0; i < length; i++) {
+ payload[i] = value;
+ }
+
+ return payload;
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only0() throws Throwable {
+ Object decoded = doDecodeUIntArrayUInt0OnlyTestImpl(true, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new UnsignedInteger[0], (UnsignedInteger[]) decoded);
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only0Array32() throws Throwable {
+ Object decoded = doDecodeUIntArrayUInt0OnlyTestImpl(false, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new UnsignedInteger[0], (UnsignedInteger[]) decoded);
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only255() throws Throwable {
+ try {
+ doDecodeUIntArrayUInt0OnlyTestImpl(true, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only255WhenAllowed() throws Throwable {
+ doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(true, 255);
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only255Array32() throws Throwable {
+ try {
+ doDecodeUIntArrayUInt0OnlyTestImpl(false, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only255Array32WhenAllowed() throws Throwable {
+ doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(false, 255);
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only256() throws Throwable {
+ try {
+ doDecodeUIntArrayUInt0OnlyTestImpl(false, 256);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only256WhenAllowed() throws Throwable {
+ doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(false, 256);
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only384() throws Throwable {
+ try {
+ doDecodeUIntArrayUInt0OnlyTestImpl(false, 384);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeUIntArrayExplicitlyUInt0Only384WhenAllowed() throws Throwable {
+ doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(false, 384);
+ }
+
+ private Object doDecodeUIntArrayUInt0OnlyTestImpl(final boolean array8, final int count) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode uint0 array8 with count > 255", count <= 255);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x43); // 'uint0' type descriptor code
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ return decoder.readObject();
+ }
+
+
+ private void doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(final boolean array8, final int count) throws Exception {
+ decoder.setZeroWidthArrayElementLimit(count);
+ Object decoded = doDecodeUIntArrayUInt0OnlyTestImpl(array8, count);
+
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+
+ UnsignedInteger[] expected = new UnsignedInteger[count];
+ for (int i = 0; i < count; i++) {
+ expected[i] = UnsignedInteger.ZERO;
+ }
+ assertArrayEquals(expected, (UnsignedInteger[]) decoded);
+ }
+
+ @Test
+ public void testEncodeDecodeListArray19() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(19, 1), 1);
+ }
+
+ @Test
+ public void testEncodeDecodeListArray20() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(20, 1), 1);
+ }
+
+ @Test
+ public void testEncodeDecodeListArray100() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(100, 1), 1);
+ }
+
+ @Test
+ public void testEncodeDecodeListArray384() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(384, 1), 1);
+ }
+
+ @Test
+ public void testEncodeDecodeListEmptyArray0() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(0), 0);
+ }
+
+ @Test
+ public void testEncodeDecodeListEmptyArray31() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(31), 0);
+ }
+
+ @Test
+ public void testEncodeDecodeListEmptyArray32() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(32), 0);
+ }
+
+ @Test
+ public void testEncodeDecodeListEmptyArray100() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(100), 0);
+ }
+
+ @Test
+ public void testEncodeDecodeListEmptyArray384() throws Throwable {
+ doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(384), 0);
+ }
+
+ private void doEncodeDecodeListArrayTestImpl(final List<Integer>[] source, final int expectedElementListLength) throws Throwable {
+ final int count = source.length;
+ final boolean emptyLists = expectedElementListLength == 0;
+
+ try {
+ final int encodingWidth;
+ if(emptyLists) {
+ encodingWidth = count < 32 ? 1 : 4; // less than 32, since each element is 8 bytes (list 4 size, 4 count), but we also need 1 for element count, and (in this case) 1 byte for primitive element type constructor.
+ } else {
+ encodingWidth = count < 20 ? 1 : 4; // less than 20, since each element is 13 bytes (list 4 size, 4 count, sub-value int with 1 element constructor, 4 value),
+ // but we also need 1 for element count, and (in this case) 1 byte for primitive element type constructor.
+ }
+
+ final int arrayPayloadSize = encodingWidth + 1 + ((4 + 4 + ((1+4) * expectedElementListLength)) * count); // count + element type descriptor + (element-width * number of elements)
+ final int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + size + encoded payload
+ final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+ ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+ // Write the array encoding code, array size, and element count
+ if(encodingWidth == 1) {
+ expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+ expectedEncodingWrapper.put((byte) arrayPayloadSize);
+ expectedEncodingWrapper.put((byte) count);
+ } else {
+ expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+ expectedEncodingWrapper.putInt(arrayPayloadSize);
+ expectedEncodingWrapper.putInt(count);
+ }
+
+ // Write the type descriptor
+ expectedEncodingWrapper.put((byte) 0xD0); // 'list32' type descriptor code
+
+ // Write the elements (lists)
+ for (int i = 0; i < count; i++) {
+ final List<Integer> list = source[i];
+ assertEquals(expectedElementListLength, list.size());
+
+ expectedEncodingWrapper.putInt(4 + ((1+4) * expectedElementListLength)); // size = count + elements
+ expectedEncodingWrapper.putInt(expectedElementListLength);
+ for(int j = 0; j < expectedElementListLength; j++) {
+ expectedEncodingWrapper.put((byte) 0x71); // 'int' type descriptor code
+ expectedEncodingWrapper.putInt(list.get(0));
+ }
+ }
+
+ assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
+
+ // Now produce actual encoding of the array for later comparison
+ assertEquals("Unexpected buffer position", 0, buffer.position());
+ encoder.writeArray(source);
+
+ byte[] actualEncoding = new byte[buffer.position()];
+ buffer.flip();
+ buffer.get(actualEncoding);
+ assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+ // Now verify against the decoding
+ buffer.flip();
+ Object decoded = decoder.readObject();
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertFalse(decoded.getClass().getComponentType().isPrimitive());
+ assertEquals(List.class, decoded.getClass().getComponentType());
+
+ assertArrayEquals("Unexpected decoding", source, (List<Integer>[]) decoded);
+
+ // Now verify expected encoding against the actual encoding of the array
+ assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+ assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+ }
+ catch (Throwable t) {
+ System.err.println("Error during test, source array: " + Arrays.toString(source));
+ throw t;
+ }
+ }
+
+ private static List<Integer>[] createPayloadArrayLists(int arrayLength, int listLength) {
+ Random rand = new Random(System.currentTimeMillis());
+
+ List<?>[] payload = new ArrayList<?>[arrayLength];
+ for (int i = 0; i < arrayLength; i++) {
+ ArrayList<Integer> l = new ArrayList<Integer>();
+ for (int j = 0; j < listLength; j++) {
+ l.add(256 + rand.nextInt(1024)); // adding 256 ensures it is a full size int every time, consistent sizing
+ }
+
+ payload[i] = l;
+ }
+
+ return (List<Integer>[]) payload;
+ }
+
+ private static List<Integer>[] createPayloadArrayEmptyList(final int length) {
+ List<?>[] payload = new List<?>[length];
+ for (int i = 0; i < length; i++) {
+ payload[i] = new ArrayList<Integer>(0);
+ }
+
+ return (List<Integer>[]) payload;
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only0() throws Throwable {
+ Object decoded = doDecodeListArrayList0OnlyTestImpl(true, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(List.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new List[0], (List[]) decoded);
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only0Array32() throws Throwable {
+ Object decoded = doDecodeListArrayList0OnlyTestImpl(false, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(List.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new ArrayList[0], (List[]) decoded);
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only254() throws Throwable {
+ try {
+ doDecodeListArrayList0OnlyTestImpl(true, 254);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ assertEquals("Array element count 254 is specified to be greater than the allowed 0-width-element limit for arrays (0)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only254WhenAllowed() throws Throwable {
+ doDecodeListArrayList0OnlyWhenAllowedTestImpl(true, 254);
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only254Array32() throws Throwable {
+ try {
+ doDecodeListArrayList0OnlyTestImpl(false, 254);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ assertEquals("Array element count 254 is specified to be greater than the allowed 0-width-element limit for arrays (0)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only254Array32WhenAllowed() throws Throwable {
+ doDecodeListArrayList0OnlyWhenAllowedTestImpl(false, 254);
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only255() throws Throwable {
+ try {
+ doDecodeListArrayList0OnlyTestImpl(true, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only255WhenAllowed() throws Throwable {
+ doDecodeListArrayList0OnlyWhenAllowedTestImpl(true, 255);
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only255Array32() throws Throwable {
+ try {
+ doDecodeListArrayList0OnlyTestImpl(false, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only255Array32WhenAllowed() throws Throwable {
+ doDecodeListArrayList0OnlyWhenAllowedTestImpl(false, 255);
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only384() throws Throwable {
+ try {
+ doDecodeListArrayList0OnlyTestImpl(false, 384);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeListArrayExplicitlyList0Only384WhenAllowed() throws Throwable {
+ doDecodeListArrayList0OnlyWhenAllowedTestImpl(false, 384);
+ }
+
+ private void doDecodeListArrayList0OnlyWhenAllowedTestImpl(final boolean array8, final int count) throws Exception {
+ decoder.setZeroWidthArrayElementLimit(count);
+ Object decoded = doDecodeListArrayList0OnlyTestImpl(array8, count);
+
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(List.class, decoded.getClass().getComponentType());
+
+ List<?>[] expected = new List<?>[count];
+ for (int i = 0; i < count; i++) {
+ expected[i] = Collections.emptyList();
+ }
+
+ assertArrayEquals(expected, (List<?>[]) decoded);
+ }
+
+ private Object doDecodeListArrayList0OnlyTestImpl(final boolean array8, final int count) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode array8 of list0 with count > 255", count < 256);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x45); // 'list0' type descriptor code
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ return decoder.readObject();
+ }
+
+ @Test
+ public void testDecodeNullArray0() throws Throwable {
+ Object decoded = doDecodeNullArrayTestImpl(true, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(Void.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new Void[0], (Void[]) decoded);
+ }
+
+ @Test
+ public void testDecodeNullArray0Array32() throws Throwable {
+ Object decoded = doDecodeNullArrayTestImpl(false, 0);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(Void.class, decoded.getClass().getComponentType());
+ assertArrayEquals(new Void[0], (Void[]) decoded);
+ }
+
+ @Test
+ public void testDecodeNullArray255() throws Throwable {
+ try {
+ doDecodeNullArrayTestImpl(true, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeNullArray255WhenAllowed() throws Throwable {
+ doDecodeNullArrayWhenAllowedTestImpl(true, 255);
+ }
+
+ @Test
+ public void testDecodeNullArray255Array32() throws Throwable {
+ try {
+ doDecodeNullArrayTestImpl(false, 255);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeNullArray255Array32WhenAllowed() throws Throwable {
+ doDecodeNullArrayWhenAllowedTestImpl(false, 255);
+ }
+
+ @Test
+ public void testDecodeNullArray256() throws Throwable {
+ try {
+ doDecodeNullArrayTestImpl(false, 256);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeNullArray256WhenAllowed() throws Throwable {
+ doDecodeNullArrayWhenAllowedTestImpl(false, 256);
+ }
+
+ @Test
+ public void testDecodeNullArray384() throws Throwable {
+ try {
+ doDecodeNullArrayTestImpl(false, 384);
+ fail("Expected exception");
+ } catch (DecodeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDecodeNullArray384WhenAllowed() throws Throwable {
+ doDecodeNullArrayWhenAllowedTestImpl(false, 384);
+ }
+
+ private void doDecodeNullArrayWhenAllowedTestImpl(boolean array8, final int count) throws Throwable {
+ decoder.setZeroWidthArrayElementLimit(count);
+ Object decoded = doDecodeNullArrayTestImpl(array8, count);
+ assertNotNull(decoded);
+ assertTrue(decoded.getClass().isArray());
+ assertEquals(Void.class, decoded.getClass().getComponentType());
+
+ final Void[] expected = new Void[count];
+
+ assertArrayEquals(expected, (Void[]) decoded);
+ }
+
+ private Object doDecodeNullArrayTestImpl(final boolean array8, final int count) throws Exception {
+ if (array8) {
+ assertTrue("Cant encode null array8 with count > 255", count < 256);
+ }
+
+ int encodingWidth = array8 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+ // Write the array encoding code, array size, and element count
+ if (array8) {
+ buffer.put((byte) 0xE0); // 'array8' type descriptor code
+ buffer.put((byte) arrayPayloadSize);
+ buffer.put((byte) count);
+ } else {
+ buffer.put((byte) 0xF0); // 'array32' type descriptor code
+ buffer.putInt(arrayPayloadSize);
+ buffer.putInt(count);
+ }
+
+ buffer.put((byte) 0x40); // 'null' type descriptor code
+
+ assertEquals(expectedEncodedArraySize, buffer.position());
+
+ buffer.flip();
+
+ return decoder.readObject();
+ }
+
+ @Test
+ public void testEncodeNullArray0() throws Throwable {
+ doEncodeNullArrayTestImpl(0);
+ }
+
+ @Test
+ public void testEncodeNullArray1() throws Throwable {
+ try {
+ doEncodeNullArrayTestImpl(1);
+ fail("Expected exception");
+ } catch (IllegalArgumentException iae) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testEncodeNullArray256() throws Throwable {
+ try {
+ doEncodeNullArrayTestImpl(256);
+ fail("Expected exception");
+ } catch (IllegalArgumentException iae) {
+ // expected
+ }
+ }
+
+ private void doEncodeNullArrayTestImpl(final int count) throws Exception {
+ Void[] source = new Void[count];
+
+ int encodingWidth = count < 256 ? 1 : 4;
+ int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+ int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+ // array size + other encoded payload
+ final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+ ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+ // Write the array encoding code, array size, and element count
+ if(encodingWidth == 1) {
+ expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+ expectedEncodingWrapper.put((byte) arrayPayloadSize);
+ expectedEncodingWrapper.put((byte) count);
+ } else {
+ expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+ expectedEncodingWrapper.putInt(arrayPayloadSize);
+ expectedEncodingWrapper.putInt(count);
+ }
+
+ expectedEncodingWrapper.put((byte) 0x40); // 'null' type descriptor code
+
+ assertEquals(expectedEncodedArraySize, expectedEncodingWrapper.position());
+ expectedEncodingWrapper.flip();
+
+
+ // Now produce actual encoding of the array for later comparison
+ assertEquals("Unexpected buffer position", 0, buffer.position());
+ encoder.writeArray(source);
+
+ byte[] actualEncoding = new byte[buffer.position()];
+ buffer.flip();
+ buffer.get(actualEncoding);
+ assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+ // Now verify expected encoding against the actual encoding of the array
+ assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+ assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+ }
+
+ @Test
+ public void testArrayOfArraysOfMixedTypes() throws IOException {
+ final int size = 10;
+
+ Object[][] source = new Object[2][size];
+ for (int i = 0; i < size; ++i) {
+ source[0][i] = Short.valueOf((short) i);
+ source[1][i] = Integer.valueOf(i);
+ }
+
+ encoder.writeArray(source);
+
+ buffer.flip();
+
+ Object result = decoder.readObject(buffer);
+
+ assertNotNull(result);
+ assertTrue(result.getClass().isArray());
+
+ Object[] resultArray = (Object[]) result;
+
+ assertNotNull(resultArray);
+ assertEquals(2, resultArray.length);
+
+ assertTrue(resultArray[0].getClass().isArray());
+ assertEquals(short.class, resultArray[0].getClass().getComponentType());
+ assertTrue(resultArray[1].getClass().isArray());
+ assertEquals(int.class, resultArray[1].getClass().getComponentType());
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedExplicitTrue() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.BOOLEAN_TRUE;
+ final Boolean described = Boolean.TRUE;
+ final byte arrayType = EncodingCodes.ARRAY32;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedExplicitTrueArray8() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.BOOLEAN_TRUE;
+ final Boolean described = Boolean.TRUE;
+ final byte arrayType = EncodingCodes.ARRAY8;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedExplicitFalse() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.BOOLEAN_FALSE;
+ final Boolean described = Boolean.FALSE;
+ final byte arrayType = EncodingCodes.ARRAY32;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedULong0() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.ULONG0;
+ final UnsignedLong described = UnsignedLong.ZERO;
+ final byte arrayType = EncodingCodes.ARRAY32;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedUInt0() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.UINT0;
+ final UnsignedInteger described = UnsignedInteger.ZERO;
+ final byte arrayType = EncodingCodes.ARRAY32;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedNull() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.NULL;
+ final Object described = null;
+ final byte arrayType = EncodingCodes.ARRAY32;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedList0() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.LIST0;
+ final List<Integer> described = new ArrayList<Integer>();
+ final byte arrayType = EncodingCodes.ARRAY32;
+
+ Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+ assertArrayOfDescribedTypeValues(decoded, described);
+ }
+
+ @Test
+ public void testDecodeArrayOfDescribedNested() throws IOException {
+ final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.DESCRIBED_TYPE_INDICATOR;
+
+ try {
+ doDecodeArrayOfDescribedZeroWidthTestImpl(EncodingCodes.ARRAY32, describedPrimitiveConstructorCode);
+ fail("Expected exception");
+ } catch(DecodeException de) {
+ assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+ }
+
+ buffer.clear();
+
+ try {
+ doDecodeArrayOfDescribedZeroWidthTestImpl(EncodingCodes.ARRAY8, describedPrimitiveConstructorCode);
+ fail("Expected exception");
+ } catch(DecodeException de) {
+ assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+ }
+ }
+
+ private Object[] doDecodeArrayOfDescribedZeroWidthTestImpl(final byte arrayTypeCode, final byte describedPrimitiveConstructorCode) {
+ if (!(describedPrimitiveConstructorCode == EncodingCodes.BOOLEAN_TRUE
+ || describedPrimitiveConstructorCode == EncodingCodes.BOOLEAN_FALSE
+ || describedPrimitiveConstructorCode == EncodingCodes.UINT0
+ || describedPrimitiveConstructorCode == EncodingCodes.ULONG0
+ || describedPrimitiveConstructorCode == EncodingCodes.LIST0
+ || describedPrimitiveConstructorCode == EncodingCodes.NULL
+ || describedPrimitiveConstructorCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR)) {
+ throw new IllegalArgumentException("Expected a 0-width element encoding or described type indicator, got: "
+ + EncodingCodes.toString(describedPrimitiveConstructorCode));
+ }
+
+ if (arrayTypeCode == EncodingCodes.ARRAY32) {
+ buffer.put(EncodingCodes.ARRAY32);
+ buffer.putInt(4 + 1 + 1 + 1 + 10 + 1); // Size
+ buffer.putInt(2); // Count
+ } else {
+ buffer.put(EncodingCodes.ARRAY8);
+ buffer.put((byte) (1 + 1 + 1 + 1 + 10 + 1)); // Size
+ buffer.put((byte) 2); // Count
+ }
+ buffer.put((byte) 0); // Described Type Indicator
+ buffer.put(EncodingCodes.SYM8); // sym8 type code for symbolic descriptor
+ buffer.put((byte) 10); //symbol length
+ buffer.put("DESCRIPTOR".getBytes(StandardCharsets.US_ASCII));
+ buffer.put(describedPrimitiveConstructorCode); // ulong constructor type-code for described primitive
+ // We dont populate any value bytes as we are expecting 0 length element constructors (or described type indicator to throw)
+
+ byte[] expectedEncoding = new byte[buffer.position()];
+
+ buffer.flip();
+
+ buffer.get(expectedEncoding);
+ assertEquals("Should have drained the encoder buffer contents", 0, buffer.remaining());
+
+ buffer.flip();
+
+ // First verify that it throws with the element limit default
+ try {
+ decoder.readObject();
+ fail("Expected exception");
+ } catch (DecodeException de) {
+ // Expected
+ }
+
+ buffer.rewind();
+ assertEquals("Should have expected buffer content remaining", expectedEncoding.length, buffer.remaining());
+
+ // Now try again with permissive element limit
+ decoder.setZeroWidthArrayElementLimit(2);
+
+ Object o = decoder.readObject();
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+
+ Object[] decoded = (Object[]) o;
+ assertEquals(2, decoded.length);
+ return decoded;
+ }
+
+ private void assertArrayOfDescribedTypeValues(Object[] decoded, final Object described) {
+ assertTrue(decoded.length > 0);
+
+ for (int i = 0; i < decoded.length; i++) {
+ assertTrue(decoded[i] instanceof DescribedType);
+ DescribedType dt = (DescribedType) decoded[i];
+ assertEquals(Symbol.valueOf("DESCRIPTOR"), dt.getDescriptor());
+
+ assertEquals(described, dt.getDescribed());
+ }
+ }
+
+ @Test
+ public void testDecodeIntegerArray() throws Exception {
+ Object o = doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 26, 6);
+ assertDecodedIntegerArray(o);
+
+ o = doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 29, 6);
+ assertDecodedIntegerArray(o);
+ }
+
+ private void assertDecodedIntegerArray(Object o) {
+ assertNotNull(o);
+ assertTrue(o.getClass().isArray());
+ assertEquals(int.class, o.getClass().getComponentType());
+
+ int[] array = (int[]) o;
+
+ assertEquals(6, array.length);
+
+ assertEquals(6, array[0]);
+ assertEquals(5, array[1]);
+ assertEquals(4, array[2]);
+ assertEquals(3, array[3]);
+ assertEquals(2, array[4]);
+ assertEquals(1, array[5]);
+ }
+
+ @Test
+ public void testDecodeThrowsWithSizeBeyondAvailable() throws Exception {
+ try {
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 26 + 1, 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array size 27 cant be more than remaining bytes 26", de.getMessage());
+ }
+
+ try {
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 29 + 1, 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array size 30 cant be more than remaining bytes 29", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithNegativeSize() throws Exception {
+ try {
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, UnsignedInteger.MAX_VALUE.intValue(), 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array size -1 cant be negative", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithNegativeCount() throws Exception {
+ try {
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 29, UnsignedInteger.MAX_VALUE.intValue());
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array element count -1 cant be negative", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithCountLargerThanSize() throws Exception {
+ try {
+ // Deliberately tries not to decode the full Array, so the 'remaining' checks dont foul
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 2, 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array element count 6 is specified to be greater than the size of array (2)", de.getMessage());
+ }
+
+ try {
+ // Deliberately tries not to decode the full Array, so the 'remaining' checks dont foul
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 5, 6);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array element count 6 is specified to be greater than the size of array (5)", de.getMessage());
+ }
+ }
+
+ private Object doDecodeIntegerArrayWithSpecificSizeCountTestImpl(final boolean array32, int size, int count) {
+ ByteBuffer tmpBuf = ByteBuffer.allocate(1024);
+ if (array32) {
+ tmpBuf.put((byte) 0xF0); // Array32 primitive type constructor
+ tmpBuf.putInt(size); // size
+ tmpBuf.putInt(count); // count
+ } else {
+ tmpBuf.put((byte) 0xE0); // Array8 primitive type constructor
+ tmpBuf.put((byte) size); // size
+ tmpBuf.put((byte) count); // count
+ }
+ tmpBuf.put((byte) 0x71); // int constructor
+ tmpBuf.putInt(6);
+ tmpBuf.putInt(5);
+ tmpBuf.putInt(4);
+ tmpBuf.putInt(3);
+ tmpBuf.putInt(2);
+ tmpBuf.putInt(1);
+
+ tmpBuf.flip();
+
+ int oldLimit = tmpBuf.limit();
+
+ decoder.setByteBuffer(tmpBuf);
+
+ final Object decoded;
+ try
+ {
+ decoded = decoder.readObject();
+ }
+ finally
+ {
+ assertEquals(oldLimit, tmpBuf.limit());
+ }
+ assertEquals(oldLimit, tmpBuf.position());
+
+ return decoded;
+ }
+
+ @Test
+ public void testDecodeArrayOverflowThrows() throws Exception {
+ try {
+ // Deliberately pass size for part Array, but count for full Array...will try to decode count, overflow
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 22 - 4, 5);
+ fail("Expected exception to be thrown");
+ } catch (BufferUnderflowException bue) {
+ // Expected
+ }
+
+ try {
+ // Deliberately pass size for part Array, but count for full Array...will try to decode count, overflow
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 25 - 4, 5);
+ fail("Expected exception to be thrown");
+ } catch (BufferUnderflowException bue) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testDecodeArrayUnderflowThrows() throws Exception {
+ try {
+ // Deliberately pass size for full Array, but count for part Array...will try to decode count, underflow
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 22, 5 - 1);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array incorrectly encoded, left 4 bytes from size indicated 22", de.getMessage());
+ }
+
+ try {
+ // Deliberately pass size for full Array, but count for part Array...will try to decode count, underflow
+ doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 25, 5 - 1);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("Array incorrectly encoded, left 4 bytes from size indicated 25", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeArrayFailsIfDepthLimitExceeded() throws Exception {
+ final int depth = 2;
+ decoder.setMaxDecodeDepth(1);
+
+ final int width = 2;
+ final int size = 10;
+ Object[][] source = new Object[width][size];
+ for (int i = 0; i < size; ++i) {
+ source[0][i] = Short.valueOf((short) i);
+ source[1][i] = Integer.valueOf(i);
+ }
+
+ encoder.writeObject(source);
+ buffer.flip();
+ buffer.mark();
+
+ try {
+ decoder.readObject();
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+ }
+
+ decoder.setMaxDecodeDepth(depth);
+ buffer.reset();
+
+ try {
+ final Object result = decoder.readObject();
+
+ assertNotNull(result);
+ assertTrue(result.getClass().isArray());
+ assertEquals(Object.class, result.getClass().getComponentType());
+
+ Object[] decoded = (Object[]) result;
+ assertEquals(width, decoded.length);
+
+ assertTrue(decoded[0].getClass().isArray());
+ assertEquals(short.class, decoded[0].getClass().getComponentType());
+
+ assertTrue(decoded[1].getClass().isArray());
+ assertEquals(int.class, decoded[1].getClass().getComponentType());
+ } catch (Exception e) {
+ fail("Should be able to read the array after depth configuration change");
+ }
+ }
+
+ @Test
+ public void testDecodeArrayOfListsOfListsFailsIfDepthLimitExceeded() throws Exception {
+ decoder.setMaxDecodeDepth(1);
+
+ List<List<?>> elementList = new ArrayList<>();
+ elementList.add(Collections.emptyList());
+ elementList.add(Collections.emptyList());
+ elementList.add(Collections.emptyList());
+
+ Object[] source = new Object[2];
+ for (int i = 0; i < source.length; ++i) {
+ source[i] = elementList;
+ }
+
+ encoder.writeArray(source);
+ buffer.flip();
+ buffer.mark();
+
+ try {
+ decoder.readObject();
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+ }
+
+ buffer.reset();
+ decoder.setMaxDecodeDepth(2);
+
+ try {
+ decoder.readObject();
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 2", de.getMessage());
+ }
+
+ buffer.reset();
+ decoder.setMaxDecodeDepth(3);
+
+ try {
+ final Object result = decoder.readObject();
+
+ assertNotNull(result);
+ assertTrue(result.getClass().isArray());
+ } catch (Exception e) {
+ fail("Should be able to read the array after depth configuration change");
+ }
+ }
+
+ @Test
+ public void testDecodeArrayOfListsOfMapsFailsIfDepthLimitExceeded() throws Exception {
+ decoder.setMaxDecodeDepth(1);
+
+ List<Map<?, ?>> elementList = new ArrayList<>();
+ elementList.add(Collections.emptyMap());
+ elementList.add(Collections.emptyMap());
+ elementList.add(Collections.emptyMap());
+
+ Object[] source = new Object[2];
+ for (int i = 0; i < source.length; ++i) {
+ source[i] = elementList;
+ }
+
+ encoder.writeArray(source);
+ buffer.flip();
+ buffer.mark();
+
+ try {
+ decoder.readObject();
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+ }
+
+ buffer.reset();
+ decoder.setMaxDecodeDepth(2);
+
+ try {
+ decoder.readObject();
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 2", de.getMessage());
+ }
+
+ buffer.reset();
+ decoder.setMaxDecodeDepth(3);
+
+ try {
+ final Object result = decoder.readObject();
+
+ assertNotNull(result);
+ assertTrue(result.getClass().isArray());
+ } catch (Exception e) {
+ fail("Should be able to read the array after depth configuration change");
+ }
+ }
+
+ @Test
+ public void testDecodingOfDeeplyNestedArrayOfArray32() throws Exception {
+ doDecodingOfDeeplyNestedArrayOfArrayTestImpl(true, 10, true);
+ doDecodingOfDeeplyNestedArrayOfArrayTestImpl(true, 10, false);
+ }
+
+ @Test
+ public void testDecodingOfDeeplyNestedArrayOfArray8() throws Exception {
+ doDecodingOfDeeplyNestedArrayOfArrayTestImpl(false, 10, true);
+ doDecodingOfDeeplyNestedArrayOfArrayTestImpl(false, 10, false);
+ }
+
+ private void doDecodingOfDeeplyNestedArrayOfArrayTestImpl(final boolean array32, final int depth, boolean readObject) {
+ ByteBuffer tmpBuf = buildNestedArraysBuffer(depth, array32);
+ decoder.setByteBuffer(tmpBuf);
+
+ decoder.setMaxDecodeDepth(depth - 1);
+
+ try {
+ if (readObject) {
+ decoder.readObject();
+ } else {
+ decoder.readArray();
+ }
+ fail("Expected exception");
+ } catch (DecodeException de) {
+ assertEquals("The nesting of types in the object being decoded exceeded the configured limit: " + (depth - 1), de.getMessage());
+ //expected
+ }
+
+ decoder.setMaxDecodeDepth(depth);
+ tmpBuf.clear();
+
+ final Object result;
+ if (readObject) {
+ result = decoder.readObject();
+ } else {
+ result = decoder.readArray();
+ }
+
+ assertTrue(result instanceof Object[]);
+ }
+
+ private ByteBuffer buildNestedArraysBuffer(final int levels, boolean array32)
+ {
+ final int encodingWith = array32 ? 4 : 1;
+ final int totalEncodingSize = ((1 + (encodingWith * 2)) * levels) + 1; // Number of bytes for size + count + 1 byte for array type + 1 for element constructor, times levels
+ // + 1 for final element type constructor (final array is empty)
+
+ ByteBuffer tmpBuf = ByteBuffer.allocate(totalEncodingSize);
+
+ int currentArraySize = totalEncodingSize - encodingWith - 1; // Size does not include its own type code and size itself
+
+ if(!array32) {
+ assertTrue("Cant encode array8 of this depth within 255 byte array size limit", currentArraySize < 256);
+ }
+
+ final byte arrayTypeCode = array32 ? EncodingCodes.ARRAY32 : EncodingCodes.ARRAY8;
+ final int individualArrayBytes = 1 + (encodingWith * 2); // type code, then size, then count
+
+ for (int i = 1; i <= levels; i++)
+ {
+ // Type code
+ tmpBuf.put(arrayTypeCode);
+
+ // Size
+ if(array32) {
+ tmpBuf.putInt(currentArraySize);
+ } else {
+ tmpBuf.put((byte) currentArraySize);
+ }
+
+ if(i < levels) {
+ // Count (1 for all but last, the nested array value coming next)
+ if(array32) {
+ tmpBuf.putInt(1);
+ } else {
+ tmpBuf.put((byte) 1);
+ }
+
+ // Value will be written on next iteration
+
+ // Update encoded array size for next iteration
+ currentArraySize -= individualArrayBytes;
+ } else {
+ // Count (0 for the final empty array)
+ if(array32) {
+ tmpBuf.putInt(0);
+ } else {
+ tmpBuf.put((byte) 0);
+ }
+ }
+ }
+
+ assertEquals(encodingWith + 1, currentArraySize); // count of prior array + the final list0 type constructor below
+
+ tmpBuf.put(EncodingCodes.LIST0);
+
+ // Check we filled the buffer
+ assertEquals(0, tmpBuf.remaining());
+
+ tmpBuf.flip();
+
+ return tmpBuf;
+ }
}
\ No newline at end of file
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java
index c84b93d3..6ea902a1 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java
@@ -16,9 +16,15 @@
*/
package org.apache.qpid.proton.codec;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.IOException;
+import java.nio.ByteBuffer;
import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.UnsignedInteger;
import org.junit.Test;
import org.mockito.Mockito;
@@ -57,4 +63,75 @@ public class BinaryTypeCodecTest extends CodecTestSupport {
// Check that the BinaryType tries to reserve space, actual encoding size not computed here.
Mockito.verify(spy).ensureRemaining(Mockito.anyInt());
}
+
+ @Test
+ public void testDecodeBinary() throws Exception {
+ final Binary expected = new Binary("54321".getBytes());
+
+ Object o = doDecodeBinaryWithSpecificSizeTestImpl(false, 5);
+ assertEquals(expected, o);
+
+ o = doDecodeBinaryWithSpecificSizeTestImpl(true, 5);
+ assertEquals(expected, o);
+ }
+
+ @Test
+ public void testDecodeThrowsWithSizeBeyondAvailable() throws Exception {
+ try {
+ doDecodeBinaryWithSpecificSizeTestImpl(false, 5 + 1);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("binary length 6 cannot be more than remaining bytes 5", de.getMessage());
+ }
+
+ try {
+ doDecodeBinaryWithSpecificSizeTestImpl(true, 5 + 1);
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("binary length 6 cannot be more than remaining bytes 5", de.getMessage());
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWithNegativeSize() throws Exception {
+ try {
+ doDecodeBinaryWithSpecificSizeTestImpl(true, UnsignedInteger.MAX_VALUE.intValue());
+ fail("Expected exception to be thrown");
+ } catch (DecodeException de) {
+ assertEquals("binary length -1 cannot be negative", de.getMessage());
+ }
+ }
+
+ private Object doDecodeBinaryWithSpecificSizeTestImpl(final boolean sym32, int size) {
+ ByteBuffer tmpBuf = ByteBuffer.allocate(1024);
+ if (sym32) {
+ tmpBuf.put((byte) (byte) 0xB0); // vbin32 primitive type constructor
+ tmpBuf.putInt(size); // size
+ } else {
+ assertTrue(size < 256);
+ tmpBuf.put((byte) (byte) 0xA0); // vbin8 primitive type constructor
+ tmpBuf.put((byte) size); // size
+ }
+ tmpBuf.put((byte) 0x35); // "5"
+ tmpBuf.put((byte) 0x34); // "4"
+ tmpBuf.put((byte) 0x33); // "3"
+ tmpBuf.put((byte) 0x32); // "2"
+ tmpBuf.put((byte) 0x31); // "1"
+
+
+ tmpBuf.flip();
+ int oldLimit = tmpBuf.limit();
+
+ decoder.setByteBuffer(tmpBuf);
+
+ final Object decoded;
+ try {
+ decoded = decoder.readObject();
+ } finally {
+ assertEquals(oldLimit, tmpBuf.limit());
+ }
+ assertEquals(oldLimit, tmpBuf.position());
+
+ return decoded;
+ }
}
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java
index bb33697a..f85cd984 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java
@@ -27,13 +27,15 @@ public class CodecTestSupport {
public static final int DEFAULT_MAX_BUFFER = 256 * 1024;
- final DecoderImpl decoder = new DecoderImpl();
- final EncoderImpl encoder = new EncoderImpl(decoder);
+ protected DecoderImpl decoder;
+ protected EncoderImpl encoder;
- ByteBuffer buffer;
+ protected ByteBuffer buffer;
@Before
public void setUp() {
+ decoder = new DecoderImpl();
+ encoder = new EncoderImpl(decoder);
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
buffer = ByteBuffer.allocate(getMaxBufferSize());
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/DecoderImplTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/DecoderImplTest.java
new file mode 100644
index 00000000..2f0558f5
--- /dev/null
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/DecoderImplTest.java
@@ -0,0 +1,221 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
... 9377 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org