bones-rpc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bones-rpc.gemspec +29 -0
- data/lib/bones-rpc.rb +2 -0
- data/lib/bones/rpc.rb +23 -0
- data/lib/bones/rpc/adapter.rb +49 -0
- data/lib/bones/rpc/adapter/base.rb +41 -0
- data/lib/bones/rpc/adapter/erlang.rb +28 -0
- data/lib/bones/rpc/adapter/json.rb +23 -0
- data/lib/bones/rpc/adapter/msgpack.rb +52 -0
- data/lib/bones/rpc/adapter/parser.rb +37 -0
- data/lib/bones/rpc/address.rb +167 -0
- data/lib/bones/rpc/cluster.rb +266 -0
- data/lib/bones/rpc/connection.rb +146 -0
- data/lib/bones/rpc/connection/reader.rb +49 -0
- data/lib/bones/rpc/connection/socket.rb +4 -0
- data/lib/bones/rpc/connection/socket/connectable.rb +196 -0
- data/lib/bones/rpc/connection/socket/ssl.rb +35 -0
- data/lib/bones/rpc/connection/socket/tcp.rb +28 -0
- data/lib/bones/rpc/connection/writer.rb +51 -0
- data/lib/bones/rpc/context.rb +48 -0
- data/lib/bones/rpc/errors.rb +33 -0
- data/lib/bones/rpc/failover.rb +38 -0
- data/lib/bones/rpc/failover/disconnect.rb +33 -0
- data/lib/bones/rpc/failover/ignore.rb +31 -0
- data/lib/bones/rpc/failover/retry.rb +39 -0
- data/lib/bones/rpc/future.rb +26 -0
- data/lib/bones/rpc/instrumentable.rb +41 -0
- data/lib/bones/rpc/instrumentable/log.rb +45 -0
- data/lib/bones/rpc/instrumentable/noop.rb +33 -0
- data/lib/bones/rpc/loggable.rb +112 -0
- data/lib/bones/rpc/node.rb +317 -0
- data/lib/bones/rpc/node/registry.rb +32 -0
- data/lib/bones/rpc/parser.rb +114 -0
- data/lib/bones/rpc/parser/buffer.rb +80 -0
- data/lib/bones/rpc/protocol.rb +106 -0
- data/lib/bones/rpc/protocol/acknowledge.rb +82 -0
- data/lib/bones/rpc/protocol/adapter_helper.rb +164 -0
- data/lib/bones/rpc/protocol/binary_helper.rb +431 -0
- data/lib/bones/rpc/protocol/ext_message.rb +86 -0
- data/lib/bones/rpc/protocol/notify.rb +38 -0
- data/lib/bones/rpc/protocol/request.rb +45 -0
- data/lib/bones/rpc/protocol/response.rb +58 -0
- data/lib/bones/rpc/protocol/synchronize.rb +70 -0
- data/lib/bones/rpc/read_preference.rb +43 -0
- data/lib/bones/rpc/read_preference/nearest.rb +57 -0
- data/lib/bones/rpc/read_preference/selectable.rb +81 -0
- data/lib/bones/rpc/readable.rb +57 -0
- data/lib/bones/rpc/session.rb +195 -0
- data/lib/bones/rpc/uri.rb +222 -0
- data/lib/bones/rpc/version.rb +6 -0
- metadata +198 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
class Node
|
5
|
+
class Registry
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@registry = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def flush(exception = Errors::ConnectionFailure.new("Socket closed"))
|
12
|
+
return true if @registry.empty?
|
13
|
+
@registry.each do |channel, futures|
|
14
|
+
futures.each do |id, future|
|
15
|
+
future.signal(FutureValue.new(exception)) rescue nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
@registry.clear
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(channel, id)
|
22
|
+
(@registry[channel] ||= {}).delete(id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def set(channel, id, future)
|
26
|
+
(@registry[channel] ||= {})[id] = future
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'bones/rpc/parser/buffer'
|
3
|
+
|
4
|
+
module Bones
|
5
|
+
module RPC
|
6
|
+
class Parser
|
7
|
+
|
8
|
+
EXT8 = [0xC7].pack('C').freeze
|
9
|
+
EXT16 = [0xC8].pack('C').freeze
|
10
|
+
EXT32 = [0xC9].pack('C').freeze
|
11
|
+
|
12
|
+
attr_reader :stream, :adapter
|
13
|
+
|
14
|
+
def buffer
|
15
|
+
@buffer ||= Bones::RPC::Parser::Buffer.new(@stream)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(stream, adapter)
|
19
|
+
@stream = stream.force_encoding('BINARY')
|
20
|
+
@adapter = Adapter.get(adapter)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parser
|
24
|
+
@parser ||= @adapter.parser(@stream)
|
25
|
+
end
|
26
|
+
|
27
|
+
def read
|
28
|
+
sync do
|
29
|
+
b = buffer.getc
|
30
|
+
buffer.ungetc(b)
|
31
|
+
if b.nil?
|
32
|
+
raise EOFError
|
33
|
+
elsif b.start_with?(EXT8)
|
34
|
+
buffer.skip(1)
|
35
|
+
len, = buffer.read(1).unpack('C')
|
36
|
+
type, = buffer.read(1).unpack('C')
|
37
|
+
check_ext!(type)
|
38
|
+
head, = buffer.read(1).unpack('C')
|
39
|
+
data = buffer.read(len-1)
|
40
|
+
parse_ext!(head, data)
|
41
|
+
elsif b.start_with?(EXT16)
|
42
|
+
buffer.skip(1)
|
43
|
+
len, = buffer.read(2).unpack('n')
|
44
|
+
type, = buffer.read(1).unpack('C')
|
45
|
+
check_ext!(type)
|
46
|
+
head, = buffer.read(1).unpack('C')
|
47
|
+
data = buffer.read(len-1)
|
48
|
+
parse_ext!(head, data)
|
49
|
+
elsif b.start_with?(EXT32)
|
50
|
+
buffer.skip(1)
|
51
|
+
len, = buffer.read(4).unpack('N')
|
52
|
+
type, = buffer.read(1).unpack('C')
|
53
|
+
check_ext!(type)
|
54
|
+
head, = buffer.read(1).unpack('C')
|
55
|
+
data = buffer.read(len-1)
|
56
|
+
parse_ext!(head, data)
|
57
|
+
else
|
58
|
+
object = parser.read
|
59
|
+
buffer.seek(parser.unpacker_pos)
|
60
|
+
map_from!(object)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def check_ext!(type)
|
68
|
+
raise(Errors::InvalidExtMessage, "bad ext message received of type #{type.inspect} (should be #{0x0D.inspect})") unless valid_ext?(type)
|
69
|
+
end
|
70
|
+
|
71
|
+
def map_from!(object)
|
72
|
+
case object
|
73
|
+
when Array
|
74
|
+
if (3..4).include?(object.size)
|
75
|
+
case object.first
|
76
|
+
when 0
|
77
|
+
Protocol::Request.map_from(object)
|
78
|
+
when 1
|
79
|
+
Protocol::Response.map_from(object)
|
80
|
+
when 2
|
81
|
+
Protocol::Notify.map_from(object)
|
82
|
+
else
|
83
|
+
object
|
84
|
+
end
|
85
|
+
end
|
86
|
+
else
|
87
|
+
object
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_ext!(head, data)
|
92
|
+
message = Protocol.get_by_ext_head(head)
|
93
|
+
if message
|
94
|
+
message.unpack(data)
|
95
|
+
else
|
96
|
+
map_from!(Adapter.get_by_ext_head(head).unpack(data))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def sync
|
101
|
+
buffer.transaction do
|
102
|
+
yield
|
103
|
+
end
|
104
|
+
ensure
|
105
|
+
parser.unpacker_seek(buffer.pos) if parser.unpacker_pos != buffer.pos
|
106
|
+
end
|
107
|
+
|
108
|
+
def valid_ext?(type)
|
109
|
+
type == 0x0D
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
class Parser
|
5
|
+
class Buffer
|
6
|
+
|
7
|
+
attr_reader :io
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@io = StringIO.new(data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def getc
|
14
|
+
io.getc
|
15
|
+
end
|
16
|
+
|
17
|
+
def pos
|
18
|
+
io.pos
|
19
|
+
end
|
20
|
+
|
21
|
+
def read(n)
|
22
|
+
i = pos
|
23
|
+
data = io.read(n)
|
24
|
+
if data.bytesize < n
|
25
|
+
seek(i)
|
26
|
+
raise EOFError
|
27
|
+
else
|
28
|
+
data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def rewind
|
33
|
+
io.rewind
|
34
|
+
end
|
35
|
+
|
36
|
+
def seek(pos)
|
37
|
+
io.seek(pos)
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
io.size
|
42
|
+
end
|
43
|
+
|
44
|
+
def skip(n)
|
45
|
+
seek(pos + n)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sync(*others)
|
49
|
+
yield
|
50
|
+
ensure
|
51
|
+
others.each { |other| other.seek(pos) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_str
|
55
|
+
i = pos
|
56
|
+
begin
|
57
|
+
io.read || ""
|
58
|
+
ensure
|
59
|
+
seek(i)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def transaction
|
64
|
+
i = pos
|
65
|
+
begin
|
66
|
+
yield
|
67
|
+
rescue EOFError => e
|
68
|
+
seek(i)
|
69
|
+
raise e
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def ungetc(c)
|
74
|
+
io.ungetc(c)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Bones #:nodoc:
|
2
|
+
module RPC
|
3
|
+
|
4
|
+
# The +Bones::RPC::Protocol+ namespace contains convenience classes for
|
5
|
+
# building all of the possible messages defined in the Bones RPC Protocol.
|
6
|
+
module Protocol
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def get_by_ext_head(head)
|
10
|
+
ext_heads[head]
|
11
|
+
end
|
12
|
+
|
13
|
+
def register_ext_head(message, head)
|
14
|
+
ext_heads[head] ||= message
|
15
|
+
return message
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def ext_heads
|
21
|
+
@ext_heads ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'bones/rpc/protocol/adapter_helper'
|
28
|
+
require 'bones/rpc/protocol/binary_helper'
|
29
|
+
|
30
|
+
require 'bones/rpc/protocol/ext_message'
|
31
|
+
require 'bones/rpc/protocol/acknowledge'
|
32
|
+
require 'bones/rpc/protocol/synchronize'
|
33
|
+
|
34
|
+
require 'bones/rpc/protocol/notify'
|
35
|
+
require 'bones/rpc/protocol/request'
|
36
|
+
require 'bones/rpc/protocol/response'
|
37
|
+
|
38
|
+
module Bones
|
39
|
+
module RPC
|
40
|
+
module Protocol
|
41
|
+
|
42
|
+
def deserialize(buffer, adapter = nil)
|
43
|
+
char = buffer.getc
|
44
|
+
buffer.ungetc(char)
|
45
|
+
if sub = MAP[char]
|
46
|
+
sub.deserialize(buffer, adapter)
|
47
|
+
elsif adapter
|
48
|
+
Adapter.get(adapter).deserialize(buffer)
|
49
|
+
else
|
50
|
+
raise NotImplementedError, "Unknown data received: #{char.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module MessagePackExtended
|
55
|
+
extend self
|
56
|
+
|
57
|
+
def deserialize(buffer, adapter = nil)
|
58
|
+
ext8 = buffer.getc
|
59
|
+
len = buffer.getc
|
60
|
+
type = buffer.getc
|
61
|
+
buffer.ungetc(type)
|
62
|
+
buffer.ungetc(len)
|
63
|
+
buffer.ungetc(ext8)
|
64
|
+
if sub = MAP[type]
|
65
|
+
sub.deserialize(buffer, adapter)
|
66
|
+
else
|
67
|
+
raise NotImplementedError, "Unknown MessagePackExtended data received: {ext8: #{ext8.inspect}, len: #{len.inspect}, type: #{type.inspect}}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module BonesRPC
|
72
|
+
extend self
|
73
|
+
|
74
|
+
def deserialize(buffer, adapter = nil)
|
75
|
+
ext8 = buffer.getc
|
76
|
+
len = buffer.getc
|
77
|
+
type = buffer.getc
|
78
|
+
head = buffer.getc
|
79
|
+
buffer.ungetc(head)
|
80
|
+
buffer.ungetc(type)
|
81
|
+
buffer.ungetc(len)
|
82
|
+
buffer.ungetc(ext8)
|
83
|
+
if sub = MAP[head]
|
84
|
+
sub.deserialize(buffer, adapter)
|
85
|
+
else
|
86
|
+
raise NotImplementedError, "Unknown BonesRPC data received: {ext8: #{ext8.inspect}, len: #{len.inspect}, type: #{type.inspect}, head: #{head.inspect}}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
MAP = {
|
91
|
+
[0].pack('C').freeze => Synchronize,
|
92
|
+
[1].pack('C').freeze => Acknowledge
|
93
|
+
}.freeze
|
94
|
+
end
|
95
|
+
|
96
|
+
MAP = {
|
97
|
+
[0x0d].pack('C').freeze => BonesRPC
|
98
|
+
}.freeze
|
99
|
+
end
|
100
|
+
|
101
|
+
MAP = {
|
102
|
+
[0xc7].pack('C').freeze => MessagePackExtended
|
103
|
+
}.freeze
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
module Protocol
|
5
|
+
class Acknowledge < ExtMessage
|
6
|
+
|
7
|
+
uint32 :id
|
8
|
+
uint8 :ready
|
9
|
+
|
10
|
+
finalize
|
11
|
+
|
12
|
+
def initialize(id, ready)
|
13
|
+
self.id = id
|
14
|
+
self.ready = ready
|
15
|
+
end
|
16
|
+
|
17
|
+
undef ext_head
|
18
|
+
undef ready
|
19
|
+
undef :ready=
|
20
|
+
undef serialize_ready
|
21
|
+
|
22
|
+
def ext_head
|
23
|
+
1
|
24
|
+
end
|
25
|
+
|
26
|
+
def ready
|
27
|
+
@ready
|
28
|
+
end
|
29
|
+
|
30
|
+
def ready=(val)
|
31
|
+
@ready = case val
|
32
|
+
when 0xC2
|
33
|
+
false
|
34
|
+
when 0xC3
|
35
|
+
true
|
36
|
+
else
|
37
|
+
!!val
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def serialize_ready(buffer)
|
42
|
+
buffer << [ready ? 0xC3 : 0xC2].pack('C')
|
43
|
+
end
|
44
|
+
|
45
|
+
def log_inspect
|
46
|
+
type = "ACKNOWLEDGE"
|
47
|
+
fields = []
|
48
|
+
fields << ["%-12s", type]
|
49
|
+
fields << ["id=%s", id]
|
50
|
+
fields << ["ready=%s", ready]
|
51
|
+
f, v = fields.transpose
|
52
|
+
f.join(" ") % v
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.deserialize(buffer, adapter = nil)
|
56
|
+
message = super
|
57
|
+
message.deserialize_id(buffer)
|
58
|
+
message.deserialize_ready(buffer)
|
59
|
+
message
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.unpack(data)
|
63
|
+
buffer = StringIO.new(data)
|
64
|
+
id, = buffer.read(4).unpack('N')
|
65
|
+
ready = buffer.read(1)
|
66
|
+
new(id, ready)
|
67
|
+
end
|
68
|
+
|
69
|
+
def get(node)
|
70
|
+
node.detach(:synack, id)
|
71
|
+
end
|
72
|
+
|
73
|
+
def signal(future)
|
74
|
+
future.signal(FutureValue.new(self))
|
75
|
+
end
|
76
|
+
|
77
|
+
Protocol.register_ext_head self, 1
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
module AdapterHelper
|
7
|
+
|
8
|
+
# Default implementation for a message is to do nothing when receiving
|
9
|
+
# replies.
|
10
|
+
#
|
11
|
+
# @example Receive replies.
|
12
|
+
# message.receive_replies(connection)
|
13
|
+
#
|
14
|
+
# @param [ Connection ] connection The connection.
|
15
|
+
#
|
16
|
+
# @return [ nil ] nil.
|
17
|
+
#
|
18
|
+
# @since 1.0.0
|
19
|
+
def receive_replies(connection); end
|
20
|
+
|
21
|
+
# Serializes the message and all of its fields to a new buffer or to the
|
22
|
+
# provided buffer.
|
23
|
+
#
|
24
|
+
# @example Serliaze the message.
|
25
|
+
# message.serialize
|
26
|
+
#
|
27
|
+
# @param [ String ] buffer A buffer to serialize to.
|
28
|
+
#
|
29
|
+
# @return [ String ] The result of serliazing this message
|
30
|
+
#
|
31
|
+
# @since 1.0.0
|
32
|
+
def serialize(buffer, adapter)
|
33
|
+
Adapter.get(adapter).serialize(process, buffer)
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
|
38
|
+
# Extends the including class with +ClassMethods+.
|
39
|
+
#
|
40
|
+
# @param [Class] subclass the inheriting class
|
41
|
+
def included(base)
|
42
|
+
super
|
43
|
+
base.extend(ClassMethods)
|
44
|
+
end
|
45
|
+
private :included
|
46
|
+
end
|
47
|
+
|
48
|
+
# Provides a DSL for defining struct-like fields for building messages
|
49
|
+
# for the Mongo Wire.
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# class Command
|
53
|
+
# extend Message::ClassMethods
|
54
|
+
#
|
55
|
+
# int32 :length
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# Command.fields # => [:length]
|
59
|
+
# command = Command.new
|
60
|
+
# command.length = 12
|
61
|
+
# command.serialize_length("") # => "\f\x00\x00\x00"
|
62
|
+
module ClassMethods
|
63
|
+
|
64
|
+
def deserialize(adapter, buffer="")
|
65
|
+
adapter = Adapter.get(adapter)
|
66
|
+
message = adapter.deserialize(buffer)
|
67
|
+
message.shift
|
68
|
+
new(adapter, *message)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Array] the fields defined for this message
|
72
|
+
def fields
|
73
|
+
@fields ||= []
|
74
|
+
end
|
75
|
+
|
76
|
+
def any(name)
|
77
|
+
attr_accessor name
|
78
|
+
|
79
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
80
|
+
def process_#{name}
|
81
|
+
#{name}
|
82
|
+
end
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
fields << name
|
86
|
+
end
|
87
|
+
|
88
|
+
# Declare a binary field.
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# class Query < Message
|
92
|
+
# binary :collection
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# @param [String] name the name of this field
|
96
|
+
def binary(name)
|
97
|
+
attr_accessor name
|
98
|
+
|
99
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
100
|
+
def process_#{name}
|
101
|
+
#{name}
|
102
|
+
end
|
103
|
+
RUBY
|
104
|
+
|
105
|
+
fields << name
|
106
|
+
end
|
107
|
+
|
108
|
+
def integer(name)
|
109
|
+
attr_accessor name
|
110
|
+
|
111
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
112
|
+
def #{name}
|
113
|
+
@#{name} ||= 0
|
114
|
+
end
|
115
|
+
|
116
|
+
def process_#{name}
|
117
|
+
#{name}
|
118
|
+
end
|
119
|
+
RUBY
|
120
|
+
|
121
|
+
fields << name
|
122
|
+
end
|
123
|
+
|
124
|
+
def list(name)
|
125
|
+
attr_accessor name
|
126
|
+
|
127
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
128
|
+
def #{name}
|
129
|
+
@#{name} ||= []
|
130
|
+
end
|
131
|
+
|
132
|
+
def process_#{name}
|
133
|
+
#{name}
|
134
|
+
end
|
135
|
+
RUBY
|
136
|
+
|
137
|
+
fields << name
|
138
|
+
end
|
139
|
+
|
140
|
+
# Declares the message class as complete, and defines its serialization
|
141
|
+
# method from the declared fields.
|
142
|
+
def finalize
|
143
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
144
|
+
def process
|
145
|
+
list = []
|
146
|
+
#{fields.map { |f| "list << process_#{f}" }.join("\n")}
|
147
|
+
list
|
148
|
+
end
|
149
|
+
EOS
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# This ensures that subclasses of the primary wire message classes have
|
155
|
+
# identical fields.
|
156
|
+
def inherited(subclass)
|
157
|
+
super
|
158
|
+
subclass.fields.replace(fields)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|