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 #
 # ------------------------ #