class Mongo::Protocol::Message

A base class providing functionality required by all messages in the MongoDB wire protocol. It provides a minimal DSL for defining typed fields to enable serialization and deserialization over the wire.

@example

class WireProtocolMessage < Message

  private

  def op_code
    1234
  end

  FLAGS = [:first_bit, :bit_two]

  # payload
  field :flags, BitVector.new(FLAGS)
  field :namespace, CString
  field :document, Document
  field :documents, Document, true
end

@abstract @api semiprivate

Constants

BATCH_SIZE

The batch size constant.

@since 2.2.0

COLLECTION

The collection constant.

@since 2.2.0

LIMIT

The limit constant.

@since 2.2.0

MAX_MESSAGE_SIZE

Default max message size of 48MB.

@since 2.2.1

ORDERED

The ordered constant.

@since 2.2.0

Q

The q constant.

@since 2.2.0

Attributes

request_id[R]

Returns the request id for the message

@return [Fixnum] The request id for this message

Public Class Methods

deserialize(io, max_message_size = MAX_MESSAGE_SIZE, expected_response_to = nil) click to toggle source

Deserializes messages from an IO stream

@param [ Integer ] max_message_size The max message size. @param [ IO ] io Stream containing a message

@return [ Message ] Instance of a Message class

# File lib/mongo/protocol/message.rb, line 138
def self.deserialize(io, max_message_size = MAX_MESSAGE_SIZE, expected_response_to = nil)
  length, _request_id, response_to, _op_code = deserialize_header(BSON::ByteBuffer.new(io.read(16)))

  # Protection from potential DOS man-in-the-middle attacks. See
  # DRIVERS-276.
  if length > (max_message_size || MAX_MESSAGE_SIZE)
    raise Error::MaxMessageSize.new(max_message_size)
  end

  # Protection against returning the response to a previous request. See
  # RUBY-1117
  if expected_response_to && response_to != expected_response_to
    raise Error::UnexpectedResponse.new(expected_response_to, response_to)
  end

  message = Registry.get(_op_code).allocate
  buffer = BSON::ByteBuffer.new(io.read(length - 16))

  message.send(:fields).each do |field|
    if field[:multi]
      deserialize_array(message, buffer, field)
    else
      deserialize_field(message, buffer, field)
    end
  end
  message.inflate!
end

Private Class Methods

deserialize_array(message, io, field) click to toggle source

Deserializes an array of fields in a message

The number of items in the array must be described by a previously deserialized field specified in the class by the field dsl under the key :multi

@param message [Message] Message to contain the deserialized array. @param io [IO] Stream containing the array to deserialize. @param field [Hash] Hash representing a field. @return [Message] Message with deserialized array.

# File lib/mongo/protocol/message.rb, line 303
def self.deserialize_array(message, io, field)
  elements = []
  count = message.instance_variable_get(field[:multi])
  count.times { elements << field[:type].deserialize(io) }
  message.instance_variable_set(field[:name], elements)
end
deserialize_field(message, io, field) click to toggle source

Deserializes a single field in a message

@param message [Message] Message to contain the deserialized field. @param io [IO] Stream containing the field to deserialize. @param field [Hash] Hash representing a field. @return [Message] Message with deserialized field.

# File lib/mongo/protocol/message.rb, line 316
def self.deserialize_field(message, io, field)
  message.instance_variable_set(
    field[:name],
    field[:type].deserialize(io)
  )
end
deserialize_header(io) click to toggle source

Deserializes the header of the message

@param io [IO] Stream containing the header. @return [Array<Fixnum>] Deserialized header.

# File lib/mongo/protocol/message.rb, line 264
def self.deserialize_header(io)
  Header.deserialize(io)
end
field(name, type, multi = false) click to toggle source

A method for declaring a message field

@param name [String] Name of the field @param type [Module] Type specific serialization strategies @param multi [true, false, Symbol] Specify as true to

serialize the field's value as an array of type +:type+ or as a
symbol describing the field having the number of items in the
array (used upon deserialization)

  Note: In fields where multi is a symbol representing the field
  containing number items in the repetition, the field containing
  that information *must* be deserialized prior to deserializing
  fields that use the number.

@return [NilClass]

# File lib/mongo/protocol/message.rb, line 283
def self.field(name, type, multi = false)
  fields << {
    :name => "@#{name}".intern,
    :type => type,
    :multi => multi
  }

  attr_reader name
end
fields() click to toggle source

A class method for getting the fields for a message class

@return [Integer] the fields for the message class

# File lib/mongo/protocol/message.rb, line 216
def self.fields
  @fields ||= []
end

Public Instance Methods

==(other) click to toggle source

Tests for equality between two wire protocol messages by comparing class and field values.

@param other [Mongo::Protocol::Message] The wire protocol message. @return [true, false] The equality of the messages.

# File lib/mongo/protocol/message.rb, line 171
def ==(other)
  return false if self.class != other.class
  fields.all? do |field|
    name = field[:name]
    instance_variable_get(name) ==
      other.instance_variable_get(name)
  end
end
Also aliased as: eql?
compress!(compressor, zlib_compression_level = nil) click to toggle source

Compress a message.

@param [ String, Symbol ] compressor The compressor to use. @param [ Integer ] zlib_compression_level The zlib compression level to use.

@return [ self ] Always returns self. Other message types should override this method.

@since 2.5.0

# File lib/mongo/protocol/message.rb, line 106
def compress!(compressor, zlib_compression_level = nil)
  self
end
eql?(other)
Alias for: ==
hash() click to toggle source

Creates a hash from the values of the fields of a message.

@return [ Fixnum ] The hash code for the message.

# File lib/mongo/protocol/message.rb, line 184
def hash
  fields.map { |field| instance_variable_get(field[:name]) }.hash
end
inflate!() click to toggle source

Inflate a message.

@return [ self ] Always returns self. Other message types should override this method.

@since 2.5.0

# File lib/mongo/protocol/message.rb, line 115
def inflate!
  self
end
number_returned() click to toggle source

Default number returned value for protocol messages.

@return [ 0 ] This method must be overridden, otherwise, always returns 0.

@since 2.5.0

# File lib/mongo/protocol/message.rb, line 202
def number_returned; 0; end
replyable?() click to toggle source

The default for messages is not to require a reply after sending a message to the server.

@example Does the message require a reply?

message.replyable?

@return [ false ] The default is to not require a reply.

@since 2.0.0

# File lib/mongo/protocol/message.rb, line 94
def replyable?
  false
end
serialize(buffer = BSON::ByteBuffer.new, max_bson_size = nil) click to toggle source

Serializes message into bytes that can be sent on the wire

@param buffer [String] buffer where the message should be inserted @return [String] buffer containing the serialized message

# File lib/mongo/protocol/message.rb, line 123
def serialize(buffer = BSON::ByteBuffer.new, max_bson_size = nil)
  start = buffer.length
  serialize_header(buffer)
  serialize_fields(buffer, max_bson_size)
  buffer.replace_int32(start, buffer.length - start)
end
Also aliased as: to_s
set_request_id() click to toggle source

Generates a request id for a message

@return [Fixnum] a request id used for sending a message to the

server. The server will put this id in the response_to field of
a reply.
# File lib/mongo/protocol/message.rb, line 193
def set_request_id
  @request_id = self.class.next_id
end
to_s(buffer = BSON::ByteBuffer.new, max_bson_size = nil)
Alias for: serialize

Private Instance Methods

fields() click to toggle source

A method for getting the fields for a message class

@return [Integer] the fields for the message class

# File lib/mongo/protocol/message.rb, line 209
def fields
  self.class.fields
end
serialize_fields(buffer, max_bson_size = nil) click to toggle source

Serializes message fields into a buffer

@param buffer [String] buffer to receive the field @return [String] buffer with serialized field

# File lib/mongo/protocol/message.rb, line 224
def serialize_fields(buffer, max_bson_size = nil)
  fields.each do |field|
    value = instance_variable_get(field[:name])
    if field[:multi]
      value.each do |item|
        if field[:type].respond_to?(:size_limited?)
          field[:type].serialize(buffer, item, max_bson_size, validating_keys?)
        else
          field[:type].serialize(buffer, item, validating_keys?)
        end
      end
    else
      if field[:type].respond_to?(:size_limited?)
        field[:type].serialize(buffer, value, max_bson_size, validating_keys?)
      else
        field[:type].serialize(buffer, value, validating_keys?)
      end
    end
  end
end
serialize_header(buffer) click to toggle source

Serializes the header of the message consisting of 4 32bit integers

The integers represent a message length placeholder (calculation of the actual length is deferred) the request id, the response to id, and the op code for the message

Currently uses hardcoded 0 for request id and response to as their values are irrelevent to the server

@param buffer [String] Buffer to receive the header @return [String] Serialized header

# File lib/mongo/protocol/message.rb, line 256
def serialize_header(buffer)
  Header.serialize(buffer, [0, request_id, 0, op_code])
end
validating_keys?() click to toggle source
# File lib/mongo/protocol/message.rb, line 323
def validating_keys?
  @options[:validating_keys] if @options
end