Class: EM::Tycoon::Protocol::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/em/tycoon/protocol/message.rb

Overview

Represents a Kyoto Tycoon binary protocol message See KT docs : fallabs.com/kyototycoon/spex.html#protocol

Constant Summary collapse

MAGIC =
{:set => 0xB8,
:get => 0xBA,
:remove => 0xB9,
:play_script => 0xB4,
:replication => 0xB1,
:error => 0xBF}
MSG_TYPES =
MAGIC.invert
FLAGS =
{:no_reply => 0x01}
DEFAULT_OPTS =
{
  :no_reply => false
}
NO_EXPIRATION_TIME =
0x7FFFFFFFFFFFFFFF
PARSE_PHASES =
[:magic,:item_count]
NO_XT_HEX =
"7#{'F'*15}"
KV_PACK_FMT =
"nNNH*a*a*"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(type, data = nil) ⇒ Message

Create a new KT message object to parse a response from KT or to serialize one to send, type indicates the message type being created, as defined by the keys of Message::MAGIC (e.g. :set, :get, etc.), and the optional data parameter can be used to specify the initial contents of the message, specific to the message type



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/em/tycoon/protocol/message.rb', line 43

def initialize(type,data=nil)
  self.class.check_msg_type(type)
  self.type = type
  @data = data
  @bytes_per_record = 0
  @bytes_expected = 5
  @keysize = @valuesize = 0
  @parsed = false
  @buffer = String.new
  @parse_phase = PARSE_PHASES.first
end

Instance Attribute Details

#bufferObject (readonly)

Returns the value of attribute buffer.



24
25
26
# File 'lib/em/tycoon/protocol/message.rb', line 24

def buffer
  @buffer
end

#bytes_expectedObject (readonly)

Returns the value of attribute bytes_expected.



24
25
26
# File 'lib/em/tycoon/protocol/message.rb', line 24

def bytes_expected
  @bytes_expected
end

#dataObject (readonly)

The data payload of the KT message, which can either be empty, contain a hash of KV pairs, or a list of keys



37
38
39
# File 'lib/em/tycoon/protocol/message.rb', line 37

def data
  @data
end

#item_countObject (readonly)

Number of items/KV pairs contained in KT message



30
31
32
# File 'lib/em/tycoon/protocol/message.rb', line 30

def item_count
  @item_count
end

#keysizeObject (readonly)

Total size, in bytes, of all key data contained in KT message



32
33
34
# File 'lib/em/tycoon/protocol/message.rb', line 32

def keysize
  @keysize
end

#magicObject (readonly)

“Magic” number header from KT message, indicating message type



28
29
30
# File 'lib/em/tycoon/protocol/message.rb', line 28

def magic
  @magic
end

#parse_phaseObject (readonly)

Returns the value of attribute parse_phase.



24
25
26
# File 'lib/em/tycoon/protocol/message.rb', line 24

def parse_phase
  @parse_phase
end

#parsedObject (readonly)

Returns the value of attribute parsed.



24
25
26
# File 'lib/em/tycoon/protocol/message.rb', line 24

def parsed
  @parsed
end

#typeObject

The human-readable symbol version of the KT message type, as defined in the keys of Message::MAGIC



26
27
28
# File 'lib/em/tycoon/protocol/message.rb', line 26

def type
  @type
end

#valuesizeObject (readonly)

Total size, in bytes, of all value data contained in KT message



34
35
36
# File 'lib/em/tycoon/protocol/message.rb', line 34

def valuesize
  @valuesize
end

Class Method Details

.check_msg_type(type) ⇒ Object

Raises:

  • (ArgumentError)


135
136
137
# File 'lib/em/tycoon/protocol/message.rb', line 135

def check_msg_type(type)
  raise ArgumentError.new("Unknown message type #{type.inspect}") unless MAGIC.has_key?(type.downcase.to_sym)
end

.generate(type, data, opts = {}) ⇒ Object



128
129
130
131
132
133
# File 'lib/em/tycoon/protocol/message.rb', line 128

def generate(type, data, opts={})
  check_msg_type(type)
  opts=DEFAULT_OPTS.merge(opts)
  classname = type.to_s.gsub(/^[a-z]|_[a-z]/) {|c| c.upcase}.gsub('_','') + "Message"
  Binary.const_get(classname).generate(data,opts)
end

.message_for(data) ⇒ Object

Raises:

  • (ArgumentError)


116
117
118
119
120
121
# File 'lib/em/tycoon/protocol/message.rb', line 116

def message_for(data)
  msgtype = msg_type_for(data)
  raise ArgumentError.new("Unknown magic byte 0x#{('%02X' % data[0])}") unless msgtype
  classname = msgtype.to_s.gsub(/^[a-z]|_[a-z]/) {|c| c.upcase}.gsub('_','') + "Message"
  Binary.const_get(classname).new(data)
end

.msg_type_for(data) ⇒ Object



123
124
125
126
# File 'lib/em/tycoon/protocol/message.rb', line 123

def msg_type_for(data)
  magic = data.unpack("C").first
  MSG_TYPES[magic]
end

Instance Method Details

#[](key) ⇒ Object



109
110
111
# File 'lib/em/tycoon/protocol/message.rb', line 109

def [](key)
  @data[key]
end

#parse(data) ⇒ Object

Parse an arbitrary blob of data, possibly containing an entire message, possibly part of it, possibly more than 1 returns the number of bytes from the buffer that were actually parsed and updates the #bytes_expected property accordingly



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/em/tycoon/protocol/message.rb', line 65

def parse(data)
  return 0 unless data && data.bytesize > 0
  if data.bytesize < @bytes_expected
    @buffer << data
    @bytes_expected -= data.bytesize
    return data.bytesize
  else
    @buffer << data[0..@bytes_expected]
    bytes_parsed = parse_chunk(@buffer)
    return 0 if bytes_parsed == 0 # This is an error
    @bytes_expected -= bytes_parsed
    @buffer = String.new
    if @bytes_expected == 0
      @parsed = true
    elsif (data.bytesize-bytes_parsed) > 0
      bytes_parsed += parse(data[bytes_parsed..-1])
    end
    return bytes_parsed 
  end
end

#parse_chunk(data) ⇒ Object

Parse a Kyoto Tycoon binary protocol message part into this Message instance, returning the number of bytes parsed. Default implementation supports standard magic+hits or just magic (in case of error message) messages



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/em/tycoon/protocol/message.rb', line 89

def parse_chunk(data)
  return 0 if data.nil?
  return 0 unless data.bytesize == @bytes_expected
  bytes_parsed = 0
  if @bytes_expected > 1
    @magic,@item_count = data.unpack("CN")
    bytes_parsed = 5
  else
    @magic = data.unpack("C").first
    bytes_parsed = 1
  end
  @data = @item_count
  @parse_phase = :item_count
  return bytes_parsed
end

#parsed?Boolean

Returns:

  • (Boolean)


105
106
107
# File 'lib/em/tycoon/protocol/message.rb', line 105

def parsed?
  @parsed
end