QPID-6085: [Python client] 08..091 implement sending/receiving of additional property types
diff --git a/qpid/codec.py b/qpid/codec.py
index 8026b20..a4c5424 100644
--- a/qpid/codec.py
+++ b/qpid/codec.py
@@ -26,14 +26,18 @@
The unit test for this module is located in tests/codec.py
"""
-import re, qpid, spec08
+import re, qpid, spec08, os
from cStringIO import StringIO
from struct import *
from reference import ReferenceId
+from logging import getLogger
+
+log = getLogger("qpid.codec")
class EOF(Exception):
pass
+# This code appears to be dead
TYPE_ALIASES = {
"long_string": "longstr",
"unsigned_int": "long"
@@ -56,36 +60,81 @@
self.incoming_bits = []
self.outgoing_bits = []
+ # Before 0-91, the AMQP's set of types did not include the boolean type. However,
+ # the 0-8 and 0-9 Java client uses this type so we encode/decode it too. However, this
+ # can be turned off by setting the followng environment value.
+ if "QPID_CODEC_DISABLE_0_91_BOOLEAN" in os.environ:
+ self.understand_boolean = False
+ else:
+ self.understand_boolean = True
+
+ log.debug("AMQP 0-91 boolean supported : %r", self.understand_boolean)
+
self.types = {}
self.codes = {}
+ self.integertypes = [int, long]
self.encodings = {
+ float: "double", # python uses 64bit floats, send them as doubles
basestring: "longstr",
- int: "long",
- long: "long",
None.__class__:"void",
list: "sequence",
tuple: "sequence",
dict: "table"
}
+ if self.understand_boolean:
+ self.encodings[bool] = "boolean"
+
for constant in self.spec.constants:
+ # This code appears to be dead
if constant.klass == "field-table-type":
type = constant.name.replace("field_table_", "")
self.typecode(constant.id, TYPE_ALIASES.get(type, type))
if not self.types:
+ # long-string 'S'
self.typecode(ord('S'), "longstr")
- self.typecode(ord('I'), "long")
+ # void 'V'
+ self.typecode(ord('V'), "void")
+ # long-int 'I' (32bit signed)
+ self.typecode(ord('I'), "signed_int")
+ # long-long-int 'l' (64bit signed)
+ # This is a long standing pre-0-91-spec type used by the Java
+ # client, 0-9-1 says it should be unsigned or use 'L')
+ self.typecode(ord('l'), "signed_long")
+ # double 'd'
+ self.typecode(ord('d'), "double")
+ # float 'f'
+ self.typecode(ord('f'), "float")
+
+ if self.understand_boolean:
+ self.typecode(ord('t'), "boolean")
+
+ ## The following are supported for decoding only ##
+
+ # short-short-uint 'b' (8bit signed)
+ self.types[ord('b')] = "signed_octet"
+ # short-int 's' (16bit signed)
+ # This is a long standing pre-0-91-spec type code used by the Java
+ # client to send shorts, it should really be a short-string, or for 0-9-1 use 'U'
+ self.types[ord('s')] = "signed_short"
def typecode(self, code, type):
self.types[code] = type
self.codes[type] = code
- def resolve(self, klass):
+ def resolve(self, klass, value):
+ if(klass in self.integertypes):
+ if (value >= -2147483648 and value <= 2147483647):
+ return "signed_int"
+ elif (value >= -9223372036854775808 and value <= 9223372036854775807):
+ return "signed_long"
+ else:
+ raise ValueError('Integer value is outwith the supported 64bit signed range')
if self.encodings.has_key(klass):
return self.encodings[klass]
for base in klass.__bases__:
- result = self.resolve(base)
+ result = self.resolve(base, value)
if result != None:
return result
@@ -168,6 +217,7 @@
if isinstance(type, spec08.Struct):
return self.decode_struct(type)
else:
+ log.debug("Decoding using method: decode_" + type)
return getattr(self, "decode_" + type)()
def encode_bit(self, o):
@@ -191,7 +241,7 @@
def encode_octet(self, o):
"""
- encodes octet (8 bits) data 'o' in network byte order
+ encodes an UNSIGNED octet (8 bits) data 'o' in network byte order
"""
# octet's valid range is [0,255]
@@ -202,13 +252,20 @@
def decode_octet(self):
"""
- decodes a octet (8 bits) encoded in network byte order
+ decodes an UNSIGNED octet (8 bits) encoded in network byte order
"""
return self.unpack("!B")
+ def decode_signed_octet(self):
+ """
+ decodes a signed octet (8 bits) encoded in network byte order
+ """
+ return self.unpack("!b")
+
def encode_short(self, o):
"""
- encodes short (16 bits) data 'o' in network byte order
+ encodes an UNSIGNED short (16 bits) data 'o' in network byte order
+ AMQP 0-9-1 type: short-uint
"""
# short int's valid range is [0,65535]
@@ -219,13 +276,22 @@
def decode_short(self):
"""
- decodes a short (16 bits) in network byte order
+ decodes an UNSIGNED short (16 bits) in network byte order
+ AMQP 0-9-1 type: short-uint
"""
return self.unpack("!H")
+ def decode_signed_short(self):
+ """
+ decodes a signed short (16 bits) in network byte order
+ AMQP 0-9-1 type: short-int
+ """
+ return self.unpack("!h")
+
def encode_long(self, o):
"""
- encodes long (32 bits) data 'o' in network byte order
+ encodes an UNSIGNED long (32 bits) data 'o' in network byte order
+ AMQP 0-9-1 type: long-uint
"""
# we need to check both bounds because on 64 bit platforms
@@ -237,31 +303,50 @@
def decode_long(self):
"""
- decodes a long (32 bits) in network byte order
+ decodes an UNSIGNED long (32 bits) in network byte order
+ AMQP 0-9-1 type: long-uint
"""
return self.unpack("!L")
def encode_signed_long(self, o):
+ """
+ encodes a signed long (64 bits) in network byte order
+ AMQP 0-9-1 type: long-long-int
+ """
self.pack("!q", o)
def decode_signed_long(self):
+ """
+ decodes a signed long (64 bits) in network byte order
+ AMQP 0-9-1 type: long-long-int
+ """
return self.unpack("!q")
def encode_signed_int(self, o):
+ """
+ encodes a signed int (32 bits) in network byte order
+ AMQP 0-9-1 type: long-int
+ """
self.pack("!l", o)
def decode_signed_int(self):
+ """
+ decodes a signed int (32 bits) in network byte order
+ AMQP 0-9-1 type: long-int
+ """
return self.unpack("!l")
def encode_longlong(self, o):
"""
- encodes long long (64 bits) data 'o' in network byte order
+ encodes an UNSIGNED long long (64 bits) data 'o' in network byte order
+ AMQP 0-9-1 type: long-long-uint
"""
self.pack("!Q", o)
def decode_longlong(self):
"""
- decodes a long long (64 bits) in network byte order
+ decodes an UNSIGNED long long (64 bits) in network byte order
+ AMQP 0-9-1 type: long-long-uint
"""
return self.unpack("!Q")
@@ -354,9 +439,9 @@
for key, value in tbl.items():
if self.spec.major == 8 and self.spec.minor == 0 and len(key) > 128:
raise ValueError("field table key too long: '%s'" % key)
- type = self.resolve(value.__class__)
+ type = self.resolve(value.__class__, value)
if type == None:
- raise ValueError("no encoding for: " + value.__class__)
+ raise ValueError("no encoding for: " + str(value.__class__))
codec.encode_shortstr(key)
codec.encode_octet(self.codes[type])
codec.encode(type, value)
@@ -373,7 +458,9 @@
result = {}
while self.nread - start < size:
key = self.decode_shortstr()
+ log.debug("Field table entry key: %r", key)
code = self.decode_octet()
+ log.debug("Field table entry type code: %r", code)
if self.types.has_key(code):
value = self.decode(self.types[code])
else:
@@ -383,6 +470,7 @@
else:
value = self.read(self.dec_num(w))
result[key] = value
+ log.debug("Field table entry value: %r", value)
return result
def encode_timestamp(self, t):
@@ -451,6 +539,13 @@
def decode_uuid(self):
return self.unpack("16s")
+ def encode_void(self,o):
+ #NO-OP, value is implicit in the type.
+ return
+
+ def decode_void(self):
+ return None
+
def enc_num(self, width, n):
if width == 1:
self.encode_octet(n)
@@ -565,6 +660,22 @@
result.append(value)
return result
+ def encode_boolean(self, s):
+ if (s):
+ self.pack("!c", "\x01")
+ else:
+ self.pack("!c", "\x00")
+
+ def decode_boolean(self):
+ b = self.unpack("!c")
+ if b == "\x00":
+ return False
+ else:
+ # AMQP spec says anything else is True
+ return True
+
+
+
def fixed(code):
return (code >> 6) != 2
diff --git a/qpid/tests/codec.py b/qpid/tests/codec.py
index 8fd0528..8017f79 100644
--- a/qpid/tests/codec.py
+++ b/qpid/tests/codec.py
@@ -487,6 +487,134 @@
"""
self.failUnlessEqual(self.readFunc('decode_content', '\x01\x00\x00\x00\x07dummyId').id, 'dummyId', 'reference content decode FAILED...')
+# -----------------------------------
+# -----------------------------------
+class BooleanTestCase(BaseDataTypes):
+
+ # -------------------
+ def test_true_encode(self):
+ self.failUnlessEqual(self.callFunc('encode_boolean', True), '\x01', 'True encoding FAILED...')
+
+ # -------------------
+ def test_true_decode(self):
+ self.failUnlessEqual(self.readFunc('decode_boolean', '\x01'), True, 'True decoding FAILED...')
+ self.failUnlessEqual(self.readFunc('decode_boolean', '\x02'), True, 'True decoding FAILED...')
+ self.failUnlessEqual(self.readFunc('decode_boolean', '\xFF'), True, 'True decoding FAILED...')
+
+ # -------------------
+ def test_false_encode(self):
+ self.failUnlessEqual(self.callFunc('encode_boolean', False), '\x00', 'False encoding FAILED...')
+
+ # -------------------
+ def test_false_decode(self):
+ self.failUnlessEqual(self.readFunc('decode_boolean', '\x00'), False, 'False decoding FAILED...')
+
+# -----------------------------------
+# -----------------------------------
+class ResolveTestCase(BaseDataTypes):
+
+ # -------------------
+ # Test resolving the value 1, which should implicitly be a python int
+ def test_resolve_int_1(self):
+ value = 1
+ expected = "signed_int"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving the value -1, which should implicitly be a python int
+ def test_resolve_int_negative_1(self):
+ value = -1
+ expected = "signed_int"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving the min signed 32bit integer value, -2^31
+ def test_resolve_int_min(self):
+ value = -2147483648 #-2^31
+ expected = "signed_int"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving the max signed 32bit integer value, 2^31 -1
+ def test_resolve_int_max(self):
+ value = 2147483647 #2^31 -1
+ expected = "signed_int"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving above the max signed 32bit integer value of 2^31 -1
+ # Should be a python long, but should be classed as a signed 64bit long on the wire either way
+ def test_resolve_int_above_signed_32bit_max(self):
+ value = 2147483648 #2^31, i.e 1 above the 32bit signed max
+ expected = "signed_long"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving above the max signed 32bit integer value of 2^31 -1
+ # As above except use an explicitly cast python long
+ def test_resolve_long_above_signed_32bit_max(self):
+ value = 2147483648L #2^31, i.e 1 above the 32bit signed max
+ expected = "signed_long"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving an explicitly cast python long of value 1, i.e less than the max signed 32bit integer value
+ # Should be encoded as a 32bit signed int on the wire
+ def test_resolve_long_1(self):
+ value = 1L
+ expected = "signed_int"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving the max signed 64bit integer value of 2^63 -1
+ # Should be a python long, but should be classed as a signed 64bit long on the wire either way
+ def test_resolve_64bit_signed_max(self):
+ value = 9223372036854775807 #2^63 -1
+ expected = "signed_long"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving the min signed 64bit integer value of -2^63
+ # Should be a python long, but should be classed as a signed 64bit long on the wire either way
+ def test_resolve_64bit_signed_min(self):
+ value = -9223372036854775808 # -2^63
+ expected = "signed_long"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving a value of 2^63, i.e more than the max a signed 64bit integer value can hold.
+ # Should throw an exception indicating the value can't be encoded.
+ def test_resolve_above_64bit_signed_max(self):
+ value = 9223372036854775808L #2^63
+ self.failUnlessRaises(Exception, self.codec.resolve, value.__class__, value)
+ # -------------------
+ # Test resolving a value of -2^63 -1, i.e less than the min a signed 64bit integer value can hold.
+ # Should throw an exception indicating the value can't be encoded.
+ def test_resolve_below_64bit_signed_min(self):
+ value = 9223372036854775808L # -2^63 -1
+ self.failUnlessRaises(Exception, self.codec.resolve, value.__class__, value)
+ # -------------------
+ # Test resolving a float. Should indicate use of double as python uses 64bit floats
+ def test_resolve_float(self):
+ value = 1.1
+ expected = "double"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving a string. Should indicate use of long string encoding
+ def test_resolve_string(self):
+ value = "myString"
+ expected = "longstr"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+ # -------------------
+ # Test resolving None. Should indicate use of a void encoding.
+ def test_resolve_None(self):
+ value = None
+ expected = "void"
+ resolved = self.codec.resolve(value.__class__, value)
+ self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved))
+
# ------------------------ #
# Pre - existing test code #
# ------------------------ #