module Binary::Protocol::ClassMethods

Provides a DSL for defining struct-like fields for building binary messages.

@example

class Command
  include Binary::Protocol

  int32 :length
end

Command.fields # => [:length]
command = Command.new
command.length = 12
command.serialize_length("") # => "\f\x00\x00\x00"

Public Instance Methods

deserialize(buffer = nil, &block) click to toggle source
# File lib/binary/protocol.rb, line 214
def deserialize(buffer = nil, &block)
  if block_given?
    re_define_method(:deserialize, &block)
  else
    message = allocate
    message.deserialize(buffer)
    message
  end
end
fields() click to toggle source

@return [Array] the fields defined for this message

# File lib/binary/protocol.rb, line 77
def fields
  @fields ||= []
end
finalize() click to toggle source

Declares the protocol class as complete, and defines its serialization method from the declared fields.

# File lib/binary/protocol.rb, line 204
      def finalize
        class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def serialize(buffer = "")
            #{serialization.map { |command| "#{command}(buffer)" }.join("\n")}
            buffer
          end
          alias to_s serialize
        RUBY
      end
serialization() click to toggle source

@return [Array] the methods to run in order for serialiation

# File lib/binary/protocol.rb, line 72
def serialization
  @serialization ||= []
end
string(name, options = {}) click to toggle source

Declare a string field.

@example

class Message
  include Binary::Protocol
  string :collection
end

@param [String] name the name of this field

# File lib/binary/protocol.rb, line 138
      def string(name, options = {})
        if options.key?(:always)
          __define_always__(name, options[:always])
        else
          if options.key?(:default)
            __define_default__(name, options[:default])
          else
            attr_accessor name
          end

          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def deserialize_#{name}(buffer)
              raise NotImplementedError
            end
          RUBY
        end

        class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def serialize_#{name}(buffer)
            buffer << #{name}
          end
        RUBY

        serialization << :"serialize_#{name}"
        fields << name
      end
stringz(name, options = {}) click to toggle source

Declare a null terminated string field.

@example

class Message
  include Binary::Protocol
  stringz :collection
end

@param [String] name the name of this field

# File lib/binary/protocol.rb, line 174
      def stringz(name, options = {})
        if options.key?(:always)
          __define_always__(name, options[:always])
        else
          if options.key?(:default)
            __define_default__(name, options[:default])
          else
            attr_accessor name
          end

          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def deserialize_#{name}(buffer)
              raise NotImplementedError
            end
          RUBY
        end

        class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def serialize_#{name}(buffer)
            buffer << #{name}
            buffer << 0
          end
        RUBY

        serialization << :"serialize_#{name}"
        fields << name
      end

Protected Instance Methods

__bytes__(pack_const, size_const, name, options = {}, &block) click to toggle source
# File lib/binary/protocol.rb, line 226
      def __bytes__(pack_const, size_const, name, options = {}, &block)
        if block_given?
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def before_serialize_#{name}(buffer)
              @#{name}_start = buffer.bytesize
            end
          RUBY

          serialization << :"before_serialize_#{name}"
        end

        if options.key?(:always)
          __define_always__(name, options[:always])
        else
          if options.key?(:default)
            __define_default__(name, options[:default])
          else
            attr_accessor name
          end

          if options[:type] == :array
            class_eval <<-RUBY, __FILE__, __LINE__ + 1
              def deserialize_#{name}(buffer)
                raise NotImplementedError
              end
            RUBY
          else
            class_eval <<-RUBY, __FILE__, __LINE__ + 1
              def deserialize_#{name}(buffer)
                self.#{name}, = buffer.read(#{size_const}).unpack(#{pack_const})
              end
            RUBY
          end
        end

        if options[:type] == :array
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def serialize_#{name}(buffer)
              buffer << #{name}.pack(#{pack_const}+"*")
            end
          RUBY
        else
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def serialize_#{name}(buffer)
              buffer << [#{name}].pack(#{pack_const})
            end
          RUBY
        end

        serialization << :"serialize_#{name}"

        if block_given?
          returning = fields << name
          instance_eval(&block)

          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def after_serialize_#{name}(buffer)
              self.#{name} = buffer.bytesize - @#{name}_start - #{options[:inclusive] ? 0 : size_const}
              buffer[@#{name}_start, #{size_const}] = serialize_#{name} ""
            end
          RUBY

          serialization << :"after_serialize_#{name}"

          returning
        else
          fields << name
        end
      end
__define_always__(name, always) click to toggle source
# File lib/binary/protocol.rb, line 296
      def __define_always__(name, always)
        if always.respond_to?(:call)
          re_define_method(name, &always)
        else
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def #{name}
              @#{name} ||= #{always.inspect}
            end
          RUBY
        end
        class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def deserialize_#{name}(buffer)
            # do nothing
          end
        RUBY
      end
__define_default__(name, default) click to toggle source
# File lib/binary/protocol.rb, line 313
      def __define_default__(name, default)
        attr_writer name

        if default.respond_to?(:call)
          dval = :"__#{name}_default_value__"
          ivar = :"@#{name}"
          re_define_method(dval, &default)
          re_define_method(name) do
            if instance_variable_defined?(ivar)
              instance_variable_get(ivar)
            else
              instance_variable_set(ivar, __send__(dval))
            end
          end
        else
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def #{name}
              @#{name} ||= #{default.inspect}
            end
          RUBY
        end
      end

Private Instance Methods

inherited(subclass) click to toggle source

This ensures that subclasses of the primary protocol classes have identical fields.

Calls superclass method
# File lib/binary/protocol.rb, line 340
def inherited(subclass)
  super

  subclass.serialization.replace serialization
  subclass.fields.replace fields
end