# encoding: utf-8 # encoding: binary # THIS IS AN AUTOGENERATED FILE, DO NOT MODIFY # IT DIRECTLY ! FOR CHANGES, PLEASE UPDATE FILES # IN THE ./codegen DIRECTORY OF THE AMQ-PROTOCOL REPOSITORY.<% import codegen_helpers as helpers %><% import re, os, codegen %> require "amq/pack" require "amq/protocol/table" require "amq/protocol/frame" require "amq/protocol/constants" require "amq/protocol/exceptions" module AMQ module Protocol PROTOCOL_VERSION = "${spec.major}.${spec.minor}.${spec.revision}".freeze PREAMBLE = "${'AMQP\\x00\\x%02x\\x%02x\\x%02x' % (spec.major, spec.minor, spec.revision)}".freeze DEFAULT_PORT = ${spec.port} # @return [Array] Collection of subclasses of AMQ::Protocol::Class. def self.classes Protocol::Class.classes end # @return [Array] Collection of subclasses of AMQ::Protocol::Method. def self.methods Protocol::Method.methods end % for tuple in spec.constants: % if tuple[2] == "soft-error" or tuple[2] == "hard-error": class ${codegen.to_ruby_class_name(tuple[0])} < ${codegen.to_ruby_class_name(tuple[2])} VALUE = ${tuple[1]} end % endif % endfor class Class @classes = Array.new def self.method_id @method_id end def self.name @name end def self.inherited(base) if self == Protocol::Class @classes << base end end def self.classes @classes end end class Method @methods = Array.new def self.method_id @method_id end def self.name @name end def self.index @index end def self.inherited(base) if self == Protocol::Method @methods << base end end def self.methods @methods end def self.split_headers(user_headers) properties, headers = {}, {} user_headers.each do |key, value| # key MUST be a symbol since symbols are not garbage-collected if Basic::PROPERTIES.include?(key) properties[key] = value else headers[key] = value end end return [properties, headers] end def self.encode_body(body, channel, frame_size) return [] if body.empty? # 8 = 1 + 2 + 4 + 1 # 1 byte of frame type # 2 bytes of channel number # 4 bytes of frame payload length # 1 byte of payload trailer FRAME_END byte limit = frame_size - 8 return [BodyFrame.new(body, channel)] if body.bytesize < limit # Otherwise String#slice on 1.9 will operate with code points, # and we need bytes. MK. body.force_encoding("ASCII-8BIT") if RUBY_VERSION.to_f >= 1.9 array = Array.new while body payload, body = body[0, limit], body[limit, body.length - limit] array << BodyFrame.new(payload, channel) end array end def self.instantiate(*args, &block) self.new(*args, &block) end end % for klass in spec.classes : class ${klass.constant_name} < Protocol::Class @name = "${klass.name}" @method_id = ${klass.index} % if klass.fields: ## only the Basic class has fields (refered as properties in the JSON) PROPERTIES = [ % for field in klass.fields: :${field.ruby_name}, # ${spec.resolveDomain(field.domain)} % endfor ] % for f in klass.fields: # <% i = klass.fields.index(f) %>1 << ${15 - i} def self.encode_${f.ruby_name}(value) buffer = '' % for line in helpers.genSingleEncode(spec, "value", f.domain): ${line} % endfor [${i}, ${"0x%04x" % ( 1 << (15-i),)}, buffer] end % endfor % endif % if klass.name == "basic" : def self.encode_properties(body_size, properties) pieces, flags = [], 0 properties.reject {|key, value| value.nil?}.each do |key, value| i, f, result = self.__send__(:"encode_#{key}", value) flags |= f pieces[i] = result end # result = [${klass.index}, 0, body_size, flags].pack('n2Qn') result = [${klass.index}, 0].pack(PACK_UINT16_X2) result += AMQ::Pack.pack_uint64_big_endian(body_size) result += [flags].pack(PACK_UINT16) result + pieces.join(EMPTY_STRING) end # THIS DECODES ONLY FLAGS DECODE_PROPERTIES = { % for f in klass.fields: ${"0x%04x" % ( 1 << (15 - klass.fields.index(f)),)} => :${f.ruby_name}, % endfor } DECODE_PROPERTIES_TYPE = { % for f in klass.fields: ${"0x%04x" % ( 1 << (15 - klass.fields.index(f)),)} => :${spec.resolveDomain(f.domain)}, % endfor } # Hash doesn't give any guarantees on keys order, we will do it in a # straightforward way DECODE_PROPERTIES_KEYS = [ % for f in klass.fields: ${"0x%04x" % ( 1 << (15 - klass.fields.index(f)),)}, % endfor ] def self.decode_properties(data) offset, data_length, properties = 0, data.bytesize, {} compressed_index = data[offset, 2].unpack(PACK_UINT16)[0] offset += 2 while data_length > offset DECODE_PROPERTIES_KEYS.each do |key| next unless compressed_index >= key compressed_index -= key name = DECODE_PROPERTIES[key] || raise(RuntimeError.new("No property found for index #{index.inspect}!")) case DECODE_PROPERTIES_TYPE[key] when :shortstr size = data[offset, 1].unpack(PACK_CHAR)[0] offset += 1 result = data[offset, size] when :octet size = 1 result = data[offset, size].unpack(PACK_CHAR).first when :timestamp size = 8 result = Time.at(data[offset, size].unpack(PACK_UINT32_X2).last) when :table size = 4 + data[offset, 4].unpack(PACK_UINT32)[0] result = Table.decode(data[offset, size]) end properties[name] = result offset += size end end properties end % endif % for method in klass.methods: class ${method.constant_name} < Protocol::Method @name = "${klass.name}.${method.name}" @method_id = ${method.index} @index = ${method.binary()} @packed_indexes = [${klass.index}, ${method.index}].pack(PACK_UINT16_X2).freeze % if (spec.type == "client" and method.accepted_by("client")) or (spec.type == "server" and method.accepted_by("server") or spec.type == "all"): # @return def self.decode(data) offset = 0 % for line in helpers.genDecodeMethodDefinition(spec, method): ${line} % endfor % if (method.klass.name == "connection" or method.klass.name == "channel") and method.name == "close": self.new(${', '.join([f.ruby_name for f in method.arguments])}) % else: self.new(${', '.join([f.ruby_name for f in method.arguments])}) % endif end % if len(method.arguments) > 0: attr_reader ${', '.join([":" + f.ruby_name for f in method.arguments])} % endif def initialize(${', '.join([f.ruby_name for f in method.arguments])}) % for f in method.arguments: @${f.ruby_name} = ${f.ruby_name} % endfor end % endif def self.has_content? % if method.hasContent: true % else: false % endif end % if (spec.type == "client" and method.accepted_by("server")) or (spec.type == "server" and method.accepted_by("client")) or spec.type == "all": # @return # ${method.params()} % if klass.name == "connection": def self.encode(${(", ").join(method.not_ignored_args())}) % else: def self.encode(${(", ").join(["channel"] + method.not_ignored_args())}) % endif % for argument in method.ignored_args(): ${codegen.convert_to_ruby(argument)} % endfor % if klass.name == "connection": channel = 0 % endif buffer = @packed_indexes.dup % for line in helpers.genEncodeMethodDefinition(spec, method): ${line} % endfor % if "payload" in method.args() or "user_headers" in method.args(): frames = [MethodFrame.new(buffer, channel)] % if "user_headers" in method.args(): properties, headers = self.split_headers(user_headers) if properties.nil? or properties.empty? raise RuntimeError.new("Properties can not be empty!") end properties_payload = Basic.encode_properties(payload.bytesize, properties) frames << HeaderFrame.new(properties_payload, channel) % endif % if "payload" in method.args(): frames += self.encode_body(payload, channel, frame_size) frames % endif % else: MethodFrame.new(buffer, channel) % endif end % endif end % endfor end % endfor METHODS = begin Method.methods.inject(Hash.new) do |hash, klass| hash.merge!(klass.index => klass) end end end end