diff --git a/coolamqp/framing/frames/compilation/compile_definitions.py b/coolamqp/framing/frames/compilation/compile_definitions.py index 8526beec47bc14d04e12723c142d34d5be27858b..430440d115f9e91e635e0c1fab3688a6cb5135ef 100644 --- a/coolamqp/framing/frames/compilation/compile_definitions.py +++ b/coolamqp/framing/frames/compilation/compile_definitions.py @@ -10,6 +10,17 @@ from coolamqp.framing.frames.compilation.utilities import get_constants, get_cla frepr from coolamqp.framing.frames.base import BASIC_TYPES +TYPE_TRANSLATOR = { + 'shortstr': 'binary type (max length 255)', + 'longstr': 'binary type', + 'table': 'table. See coolamqp.framing.frames.field_table', + 'bit': 'bool', + 'octet': 'int, 8 bit unsigned', + 'short': 'int, 16 bit unsigned', + 'long': 'int, 32 bit unsigned', + 'longlong': 'int, 64 bit unsigned', + 'timestamp': '64 bit signed POSIX timestamp (in seconds)', +} def compile_definitions(xml_file='resources/amqp0-9-1.xml', out_file='coolamqp/framing/frames/definitions.py'): """parse resources/amqp-0-9-1.xml into """ @@ -102,22 +113,296 @@ from coolamqp.framing.frames.field_table import enframe_table, deframe_table, fr name_class(cls.name), doxify(None, cls.docs), frepr(cls.name), cls.index, name_class(cls.name)) + is_static = all(property.basic_type not in ('table', 'longstr', 'shortstr') for property in cls.content_properties) + for property in cls.content_properties: line(' (%s, %s, %s), # %s\n', frepr(property.name), frepr(property.type), frepr(property.basic_type), frepr(property.label)) line(' ]\n\n') - line(''' def __init__(%s):' + line(''' def __init__(%s): + """ + Create the property list. + ''', - u', '.join(['self'] + [name_field(property.name) for property in cls.content_properties]) + u', '.join(['self'] + [name_field(property.name) for property in cls.content_properties if not property.reserved]) ) - for property in cls.content_properties: + for property in (p for p in cls.content_properties if not p.reserved): + line(''' :param %s: %s + :type %s: %s (%s in AMQP) +''', + name_field(property.name), property.label, name_field(property.name), TYPE_TRANSLATOR[property.basic_type], property.type) + line(''' """ +''') + + for property in (p for p in cls.content_properties if not p.reserved): + line(' self.%s = %s # %s\n', name_field(property.name), name_field(property.name), property.label) + line('''\n def write_arguments(self, buf): +''') + + def emit_structs(su): + if len(su) == 0: + return + line(" buf.write(struct.pack('!") + line(''.join(a for a, b in su)) + line("', ") + line(', '.join(b for a, b in su)) + line('))\n') + + def emit_bits(bits): + bits = [b for b in bits if b != '0'] # reserved values are out :> + + line(" buf.write(struct.pack('!B', %s))\n", + u' | '.join((u'(int(%s) << %s)' % (bit, position)) for position, bit in enumerate(bits)) + ) + + good_structs = [] + written = False + bits = [] + for property in cls.content_properties: + val = 'self.' + name_field(property.name) if not property.reserved else BASIC_TYPES[property.basic_type][2] + + if (len(bits) == 8) or ((property.basic_type != 'bit') and len(bits) > 0): + emit_bits(bits) + bits = [] + written = True + + if property.basic_type == 'bit': + bits.append(val) + elif property.reserved: + line(" buf.write(" + BASIC_TYPES[property.basic_type][2] + ")\n") + written = True + continue + elif BASIC_TYPES[property.basic_type][1] is None: + # struct can't do it + + if property.basic_type == 'longstr': + good_structs.append(('L', 'len(%s)' % (val,))) + + elif property.basic_type == 'shortstr': + good_structs.append(('B', 'len(%s)' % (val,))) + + emit_structs(good_structs) + good_structs = [] + + if property.basic_type == 'table': + line(' enframe_table(buf, %s)\n' % (val,)) + written = True + else: + # emit ours + line(' buf.write(' + val + ')\n') + written = True + else: + # special case - empty string + if property.basic_type == 'shortstr' and property.reserved: + continue # just skip :) + + val = ('self.' + name_field(property.name)) if not property.reserved else frepr( + BASIC_TYPES[property.basic_type][2], sop=six.binary_type) + + good_structs.append((BASIC_TYPES[property.basic_type][1], val)) + written = True + written = written or len(good_structs) > 0 + emit_structs(good_structs) + if len(bits) > 0: + emit_bits(bits) + written = True + bits = [] + + if not written: + line(' pass # this has a frame, but it''s only default shortstrs\n') + line('\n') + + line(''' @staticmethod + def from_buffer(buf, start_offset): +''') + + if len([f for f in cls.content_properties if not f.reserved]) == 0: + line(" return %sContentPropertyList()\n\n", name_class(cls.name)) + else: + line(""" assert (len(buf) - start_offset) >= %s.MINIMUM_SIZE, 'Content property list too short!' + offset = start_offset # we will use it to count consumed bytes +""", + full_class_name) + # The simple, or the painful way? + has_nonstruct_fields = False + for property in cls.content_properties: + if BASIC_TYPES[property.basic_type][1] is None: + has_nonstruct_fields = True + + if len(cls.content_properties) == 0: + line(' return %s(), 0\n', full_class_name) + elif is_static: + fieldnames = [] + formats = [] + + bits = [] + bit_id = 0 + bits_to_sync_later = {} # bit_0 => [fLSB, fMSB] + + for property in cls.content_properties: + if property.basic_type == 'bit': + bits.append(None if property.reserved else name_field(property.name)) + + if len(bits) == 8: + fieldnames.append('_bit_%s' % (bit_id,)) + formats.append('B') + bits_to_sync_later['_bit_%s' % (bit_id,)] = bits + bits = [] + bit_id += 1 + + elif property.reserved: + formats.append('%sx' % (BASIC_TYPES[property.basic_type][0],)) + else: + fieldnames.append(name_field(property.name)) + formats.append(BASIC_TYPES[property.basic_type][1]) + + # sync bits + if len(bits) > 0: + fieldnames.append('_bit_%s' % (bit_id,)) + formats.append('B') + bits_to_sync_later['_bit_%s' % (bit_id,)] = bits + line(" %s, = struct.unpack_from('!%s', buf, offset)\n", + u', '.join(fieldnames), + u''.join(formats) + ) + # If there were any bits, unpack them now + for var_name, bits in bits_to_sync_later.items(): + for bitname, multiplier in zip(bits, (1, 2, 4, 8, 16, 32, 64, 128)): + line(" %s = bool(%s & %s)\n", bitname, var_name, multiplier) + line(" return %s(%s)", full_class_name, u', '.join([ + name_field(property.name) for field in + cls.content_properties if not property.reserved + ])) + + else: + def emit_bits(bits): + + if all(n == '_' for n in bits): + # everything is reserved, lol + line(""" offset += 1 +""") + return + + line(""" _bit, = struct.unpack_from('!B', buf, offset) + offset += 1 +""") + for bit, multiplier in zip(bits, (1, 2, 4, 8, 16, 32, 64, 128)): + if bit != '_': + line(""" %s = bool(_bit & %s) +""", + bit, multiplier) + + def emit_structures(ss, ln): + line(""" %s, = struct.unpack_from('!%s', buf, offset) + offset += %s +""", + u', '.join([a[0] for a in ss if not (a[0] == '_' and a[1][-1] == 'x')]), + ''.join([a[1] for a in ss]), + ln + ) + + # we'll be counting bytes + to_struct = [] # accumulate static field, (var name, struct_code) + cur_struct_len = 0 # length of current struct + + bits = [] + bit_id = 0 + + for property in cls.content_properties: + fieldname = '_' if property.reserved else name_field(property.name) + + if (len(bits) > 0) and (property.basic_type != 'bit'): + emit_bits(bits) + bits = [] + + # offset is current start + # length is length to read + if BASIC_TYPES[property.basic_type][0] is not None: + if property.reserved: + to_struct.append(('_', '%sx' % (BASIC_TYPES[property.basic_type][0],))) + else: + to_struct.append((fieldname, BASIC_TYPES[property.basic_type][1])) + cur_struct_len += BASIC_TYPES[property.basic_type][0] + elif property.basic_type == 'bit': + bits.append(fieldname) + else: + if property.basic_type == 'table': # oh my god + line(""" %s, delta = deframe_table(buf, offset) + offset += delta +""", name_field(property.name)) + else: # longstr or shortstr + f_q, f_l = ('L', 4) if property.basic_type == 'longstr' else ('B', 1) + to_struct.append(('s_len', f_q)) + cur_struct_len += f_l + emit_structures(to_struct, cur_struct_len) + to_struct, cur_struct_len = [], 0 + if property.reserved: + line(" offset += s_len\n") + else: + line(" %s = buf[offset:offset+s_len]\n offset += s_len\n", + fieldname) + + # check bits for overflow + if len(bits) == 8: + emit_bits(bits) + bits = [] + + if len(bits) > 0: + emit_bits(bits) + elif len(to_struct) > 0: + emit_structures(to_struct, cur_struct_len) + + line(" return %sContentPropertyList(%s)", + name_class(cls.name), + u', '.join(name_field(property.name) for property in cls.content_properties if not property.reserved)) + + line('\n\n') + + line(' def get_size(self):\n return ') + parts = [] + accumulator = 0 + bits = 0 + for property in cls.content_properties: + bt = property.basic_type + + if (bits > 0) and (bt != 'bit'): # sync bits if not + accumulator += int(math.ceil(bits / 8)) + bits = 0 + + if property.basic_type == 'bit': + bits += 1 + elif property.reserved: + accumulator += BASIC_TYPES[property.basic_type][3] + elif BASIC_TYPES[bt][0] is not None: + accumulator += BASIC_TYPES[property.basic_type][0] + elif bt == 'shortstr': + parts.append('len(self.' + name_field(property.name) + ')') + accumulator += 1 + elif bt == 'longstr': + parts.append('len(self.' + name_field(property.name) + ')') + accumulator += 4 + elif bt == 'table': + parts.append('frame_table_size(self.' + name_field(property.name) + ')') + accumulator += 4 + else: + raise Exception() + + if bits > 0: # sync bits + accumulator += int(math.ceil(bits / 8)) + bits = 0 + + parts.append(repr(accumulator)) + line(u' + '.join(parts)) + line('\n\n') + + # ============================================ Do methods for this class for method in cls.methods: full_class_name = '%s%s' % (name_class(cls.name), name_method(method.name)) @@ -225,19 +510,9 @@ from coolamqp.framing.frames.field_table import enframe_table, deframe_table, fr line(' :param %s: %s\n', name_field(field.name), doxify(field.label, field.docs, prefix=12, blank=False)) - tp = { - 'shortstr': 'binary type (max length 255)', - 'longstr': 'binary type', - 'table': 'table. See coolamqp.framing.frames.field_table', - 'bit': 'bool', - 'octet': 'int, 8 bit unsigned', - 'short': 'int, 16 bit unsigned', - 'long': 'int, 32 bit unsigned', - 'longlong': 'int, 64 bit unsigned', - 'timestamp': '64 bit signed POSIX timestamp (in seconds)', - } - - line(' :type %s: %s (%s in AMQP)\n', name_field(field.name), tp[field.basic_type], field.type) + + + line(' :type %s: %s (%s in AMQP)\n', name_field(field.name), TYPE_TRANSLATOR[field.basic_type], field.type) line(' """\n') diff --git a/coolamqp/framing/frames/compilation/utilities.py b/coolamqp/framing/frames/compilation/utilities.py index 48f59d293cb33eedb9ea4a305e5e0d8220a2b06f..a0bdd86641969e39b981a55b073e09146137c40b 100644 --- a/coolamqp/framing/frames/compilation/utilities.py +++ b/coolamqp/framing/frames/compilation/utilities.py @@ -14,7 +14,7 @@ Method = namedtuple('Method', ('name', 'synchronous', 'index', 'label', 'docs', 'sent_by_client', 'sent_by_server', 'constant')) # synchronous is bool, constant is bool # repponse is a list of method.name -Property = namedtuple('Property', ('name', 'type', 'label', 'basic_type')) +Property = namedtuple('Property', ('name', 'type', 'label', 'basic_type', 'reserved')) Class_ = namedtuple('Class_', ('name', 'index', 'docs', 'methods', 'content_properties')) # label is int Domain = namedtuple('Domain', ('name', 'type', 'elementary')) # elementary is bool @@ -109,7 +109,8 @@ def for_method_field(elem): # for <field> in <method> def for_content_property(elem): a = elem.attrib - return Property(a['name'], a['domain'], a.get('label', ''), None) + return Property(a['name'], a['domain'], a.get('label', ''), None, 'reserved' in a['name']) + def for_method(elem): # for <method> a = elem.attrib diff --git a/coolamqp/framing/frames/definitions.py b/coolamqp/framing/frames/definitions.py index d95813006e5f8d73213489da1cde9103f1f228b7..c9001741a267ca2bf79a8184f91af5205cc3987d 100644 --- a/coolamqp/framing/frames/definitions.py +++ b/coolamqp/framing/frames/definitions.py @@ -118,7 +118,22 @@ class ConnectionContentPropertyList(AMQPContentPropertyList): ] - def __init__(self):' + def __init__(self): + """ + Create the property list. + + """ + + def write_arguments(self, buf): + pass # this has a frame, but its only default shortstrs + + @staticmethod + def from_buffer(buf, start_offset): + return ConnectionContentPropertyList() + + def get_size(self): + return 0 + class ConnectionClose(AMQPMethodPayload): """ @@ -860,7 +875,22 @@ class ChannelContentPropertyList(AMQPContentPropertyList): ] - def __init__(self):' + def __init__(self): + """ + Create the property list. + + """ + + def write_arguments(self, buf): + pass # this has a frame, but its only default shortstrs + + @staticmethod + def from_buffer(buf, start_offset): + return ChannelContentPropertyList() + + def get_size(self): + return 0 + class ChannelClose(AMQPMethodPayload): """ @@ -1226,7 +1256,22 @@ class ExchangeContentPropertyList(AMQPContentPropertyList): ] - def __init__(self):' + def __init__(self): + """ + Create the property list. + + """ + + def write_arguments(self, buf): + pass # this has a frame, but its only default shortstrs + + @staticmethod + def from_buffer(buf, start_offset): + return ExchangeContentPropertyList() + + def get_size(self): + return 0 + class ExchangeDeclare(AMQPMethodPayload): """ @@ -1537,7 +1582,22 @@ class QueueContentPropertyList(AMQPContentPropertyList): ] - def __init__(self):' + def __init__(self): + """ + Create the property list. + + """ + + def write_arguments(self, buf): + pass # this has a frame, but its only default shortstrs + + @staticmethod + def from_buffer(buf, start_offset): + return QueueContentPropertyList() + + def get_size(self): + return 0 + class QueueBind(AMQPMethodPayload): """ @@ -2326,7 +2386,37 @@ class BasicContentPropertyList(AMQPContentPropertyList): (u'reserved', u'shortstr', u'shortstr'), # u'reserved, must be empty' ] - def __init__(self, content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id, reserved):' + def __init__(self, content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id): + """ + Create the property list. + + :param content_type: MIME content type + :type content_type: binary type (max length 255) (shortstr in AMQP) + :param content_encoding: MIME content encoding + :type content_encoding: binary type (max length 255) (shortstr in AMQP) + :param headers: message header field table + :type headers: table. See coolamqp.framing.frames.field_table (table in AMQP) + :param delivery_mode: non-persistent (1) or persistent (2) + :type delivery_mode: int, 8 bit unsigned (octet in AMQP) + :param priority: message priority, 0 to 9 + :type priority: int, 8 bit unsigned (octet in AMQP) + :param correlation_id: application correlation identifier + :type correlation_id: binary type (max length 255) (shortstr in AMQP) + :param reply_to: address to reply to + :type reply_to: binary type (max length 255) (shortstr in AMQP) + :param expiration: message expiration specification + :type expiration: binary type (max length 255) (shortstr in AMQP) + :param message_id: application message identifier + :type message_id: binary type (max length 255) (shortstr in AMQP) + :param timestamp: message timestamp + :type timestamp: 64 bit signed POSIX timestamp (in seconds) (timestamp in AMQP) + :param type: message type name + :type type: binary type (max length 255) (shortstr in AMQP) + :param user_id: creating user id + :type user_id: binary type (max length 255) (shortstr in AMQP) + :param app_id: creating application id + :type app_id: binary type (max length 255) (shortstr in AMQP) + """ self.content_type = content_type # MIME content type self.content_encoding = content_encoding # MIME content encoding self.headers = headers # message header field table @@ -2340,7 +2430,79 @@ class BasicContentPropertyList(AMQPContentPropertyList): self.type = type # message type name self.user_id = user_id # creating user id self.app_id = app_id # creating application id - self.reserved = reserved # reserved, must be empty + + def write_arguments(self, buf): + buf.write(struct.pack('!B', len(self.content_type))) + buf.write(self.content_type) + buf.write(struct.pack('!B', len(self.content_encoding))) + buf.write(self.content_encoding) + enframe_table(buf, self.headers) + buf.write(struct.pack('!BBB', self.delivery_mode, self.priority, len(self.correlation_id))) + buf.write(self.correlation_id) + buf.write(struct.pack('!B', len(self.reply_to))) + buf.write(self.reply_to) + buf.write(struct.pack('!B', len(self.expiration))) + buf.write(self.expiration) + buf.write(struct.pack('!B', len(self.message_id))) + buf.write(self.message_id) + buf.write(struct.pack('!LB', self.timestamp, len(self.type))) + buf.write(self.type) + buf.write(struct.pack('!B', len(self.user_id))) + buf.write(self.user_id) + buf.write(struct.pack('!B', len(self.app_id))) + buf.write(self.app_id) + buf.write(b'\x00') + + @staticmethod + def from_buffer(buf, start_offset): + assert (len(buf) - start_offset) >= QueueUnbindOk.MINIMUM_SIZE, 'Content property list too short!' + offset = start_offset # we will use it to count consumed bytes + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + content_type = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + content_encoding = buf[offset:offset+s_len] + offset += s_len + headers, delta = deframe_table(buf, offset) + offset += delta + delivery_mode, priority, s_len, = struct.unpack_from('!BBB', buf, offset) + offset += 3 + correlation_id = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + reply_to = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + expiration = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + message_id = buf[offset:offset+s_len] + offset += s_len + timestamp, s_len, = struct.unpack_from('!LB', buf, offset) + offset += 9 + type = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + user_id = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + app_id = buf[offset:offset+s_len] + offset += s_len + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + offset += s_len + return BasicContentPropertyList(content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id) + + def get_size(self): + return len(self.content_type) + len(self.content_encoding) + frame_table_size(self.headers) + len(self.correlation_id) + len(self.reply_to) + len(self.expiration) + len(self.message_id) + len(self.type) + len(self.user_id) + len(self.app_id) + 24 + class BasicAck(AMQPMethodPayload): """ @@ -3596,7 +3758,22 @@ class TxContentPropertyList(AMQPContentPropertyList): ] - def __init__(self):' + def __init__(self): + """ + Create the property list. + + """ + + def write_arguments(self, buf): + pass # this has a frame, but its only default shortstrs + + @staticmethod + def from_buffer(buf, start_offset): + return TxContentPropertyList() + + def get_size(self): + return 0 + class TxCommit(AMQPMethodPayload): """