bones-rpc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|