krpc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+
2
+ class Module
3
+ def class_name
4
+ name.rpartition("::").last
5
+ end
6
+
7
+ def const_get_or_create(module_name, value = nil, &block)
8
+ return const_get(module_name) if const_defined?(module_name, false)
9
+ value = block.call if block_given?
10
+ const_set(module_name, value)
11
+ value
12
+ end
13
+ end
14
+
15
+ module Boolean; end
16
+ class TrueClass; include Boolean; end
17
+ class FalseClass; include Boolean; end
18
+
19
+ class String
20
+ def underscore
21
+ self.gsub(/::/, '/').
22
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
23
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
24
+ tr("-", "_").
25
+ downcase
26
+ end
27
+ end
28
+
@@ -0,0 +1,54 @@
1
+ require 'krpc/protobuf_utils'
2
+ require 'set'
3
+
4
+ module KRPC
5
+ module Decoder
6
+ OK_LENGTH = 2
7
+ OK_MESSAGE = "\x4F\x4B"
8
+ GUID_LENGTH = 16
9
+
10
+ class << self
11
+
12
+ # Given a type object, and serialized data, decode the ruby value/object
13
+ def decode(data, type, type_store)
14
+ if type.is_a?(Types::MessageType) then decode_message(data, type)
15
+ elsif type.is_a?(Types::ValueType) then decode_value(data, type)
16
+ elsif type.is_a?(Types::EnumType)
17
+ v = decode_value(data, type_store.as_type("int32"))
18
+ type.ruby_type.key(v)
19
+ elsif type.is_a?(Types::ClassType)
20
+ remote_oid = decode_value(data, type_store.as_type("uint64"))
21
+ if remote_oid != 0
22
+ type.ruby_type.new(remote_oid)
23
+ else nil end
24
+ elsif type.is_a?(Types::ListType)
25
+ msg = decode_message(data, type_store.as_type("KRPC.List"))
26
+ msg.items.map{|x| decode(x, type.value_type, type_store)}.to_a
27
+ elsif type.is_a?(Types::DictionaryType)
28
+ msg = decode_message(data, type_store.as_type("KRPC.Dictionary"))
29
+ msg.entries.map{|e| [decode(e.key, type.key_type, type_store),
30
+ decode(e.value, type.value_type, type_store)]}.to_h
31
+ elsif type.is_a?(Types::SetType)
32
+ msg = decode_message(data, type_store.as_type("KRPC.Set"))
33
+ Set.new(msg.items.map{|x| decode(x, type.value_type, type_store)}.to_a)
34
+ elsif type.is_a?(Types::TupleType)
35
+ msg = decode_message(data, type_store.as_type("KRPC.Tuple"))
36
+ msg.items.zip(type.value_types).map{|x,t| decode(x, t, type_store)}.to_a
37
+ else raise RuntimeError.new("Cannot decode type #{type} from data: #{data}")
38
+ end
39
+ end
40
+
41
+ def decode_value(data, type)
42
+ ProtobufUtils::Decoder.decode(data, type.protobuf_type)
43
+ end
44
+
45
+ def decode_message(data, type)
46
+ msg = type.ruby_type.new
47
+ msg.parse_from_string data.to_s
48
+ msg
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,124 @@
1
+ require 'krpc/core_extensions'
2
+ require 'colorize'
3
+
4
+ #TODO: Pry's show-doc integration ?
5
+
6
+ module KRPC
7
+ module Doc
8
+ class << self
9
+ @@docstr_infos = {}
10
+ @@procedure_docstr_infos = {}
11
+
12
+ def docstring_for_method(method_owner, method_name)
13
+ is_static, class_cls = method_owner.class == Class ? [true, method_owner] : [false, method_owner.class]
14
+ service_module_name, class_name = ruby_class_to_pb_module_class_pair(class_cls)
15
+ key = [service_module_name, is_static, class_name, method_name.to_s].hash
16
+ if (@@docstr_infos.has_key? key)
17
+ construct_docstring(*@@docstr_infos[key], true)
18
+ else
19
+ "No docstring for #{class_cls.name}#{calc_separator(is_static)}#{method_name.to_s} method" +
20
+ (method_owner.respond_to?(method_name) ? "" : "\nThere is no such method -- maybe a typo")
21
+ end
22
+ end
23
+
24
+ def docstring_for_procedure(service_name, procedure_name)
25
+ key = [service_name, procedure_name].hash
26
+ if (@@procedure_docstr_infos.has_key? key)
27
+ construct_docstring(service_name, '.', procedure_name, *@@procedure_docstr_infos[key][3..-1], false)
28
+ else
29
+ "No docstring for #{service_name}.#{procedure_name} procedure"
30
+ end
31
+ end
32
+
33
+ def add_docstring_info(is_static, cls, method_name, service_name="", procedure_name="", param_names=[], param_types=[], param_default=[], return_type: nil)
34
+ service_module_name = service_name == cls.class_name ? Services.class_name : service_name
35
+ key0 = [service_name, procedure_name].hash
36
+ key1 = [service_module_name, false, cls.class_name, method_name].hash
37
+ val = [cls.krpc_name, calc_separator(is_static), method_name, param_names, param_types, param_default, return_type]
38
+ @@docstr_infos[key1] = @@procedure_docstr_infos[key0] = val
39
+ if is_static
40
+ key2 = [service_module_name, true, cls.class_name, method_name].hash
41
+ @@docstr_infos[key2] = val
42
+ end
43
+ end
44
+
45
+ def add_special_docstring_info(key, value)
46
+ @@docstr_infos[key] = value
47
+ end
48
+
49
+ private #----------------------------------
50
+
51
+ def ruby_class_to_pb_module_class_pair(ruby_class)
52
+ return ["", Client.class_name] if ruby_class == Client
53
+ rest, _, pb_class_name = ruby_class.name.rpartition("::")
54
+ _, _, pb_service_name = rest.rpartition("::")
55
+ [pb_service_name, pb_class_name]
56
+ end
57
+
58
+ def calc_separator(is_static)
59
+ is_static ? '.' : '#'
60
+ end
61
+
62
+ def construct_docstring(namespace, separator, name, param_names, param_types, param_default, return_type, is_hide_this_param)
63
+ def type2str(type)
64
+ return "nil" if type.nil?
65
+ return type.class_name if type.class == Class
66
+ rt = type.ruby_type
67
+ if type.is_a?(Types::EnumType) then "Enum" + rt.keys.to_s
68
+ elsif type.is_a?(Types::ListType) ||
69
+ type.is_a?(Types::SetType)
70
+ "#{rt.class_name}[#{type2str(type.value_type)}]"
71
+ elsif type.is_a?(Types::DictionaryType)
72
+ %Q{#{rt.class_name}[#{type2str(type.key_type)} => #{type2str(type.value_type)}]}
73
+ elsif type.is_a?(Types::TupleType)
74
+ %Q{#{rt.class_name}[#{type.value_types.map{|x| type2str(x)}.join(", ")}]}
75
+ else rt.class_name end
76
+ end
77
+ param_infos = param_names.zip(param_types.map{|x| type2str(x)}, param_default)
78
+ param_infos.shift if is_hide_this_param && param_names[0] == "this"
79
+ if param_infos.empty?
80
+ params = ""
81
+ else
82
+ params = "\n" + param_infos.map do |pi|
83
+ "\t#{pi[0].light_green} :#{pi[1]}" + (pi[2].nil? ? "" : " = #{pi[2]}".magenta)
84
+ end.join(",\n") + "\n"
85
+ end
86
+ "#{namespace.cyan}#{separator.cyan}#{name.bold}(#{params}) :#{type2str(return_type).light_red}"
87
+ end
88
+
89
+ end
90
+
91
+ module SuffixMethods
92
+ DOCSTRING_SUFFIX = "_doc"
93
+ DOCSTRING_SUFFIX_REGEX = /^(.+)(?:#{DOCSTRING_SUFFIX}(=)?)$/
94
+
95
+ def self.included(base)
96
+ base.extend self
97
+ class << base
98
+ def krpc_name
99
+ class_name
100
+ end
101
+ end
102
+ end
103
+
104
+ def method_missing(method, *args, &block)
105
+ if DOCSTRING_SUFFIX_REGEX =~ method.to_s
106
+ documented_method_name = $1 + $2.to_s
107
+ if respond_to? documented_method_name.to_sym
108
+ return puts Doc.docstring_for_method(self,documented_method_name)
109
+ end
110
+ end
111
+ super
112
+ end
113
+
114
+ def respond_to_missing?(method, *)
115
+ if DOCSTRING_SUFFIX_REGEX =~ method.to_s
116
+ return true if respond_to? ($1 + $2.to_s).to_sym
117
+ end
118
+ super
119
+ end
120
+ end
121
+
122
+ end
123
+ end
124
+
@@ -0,0 +1,60 @@
1
+ require 'krpc/protobuf_utils'
2
+
3
+ module KRPC
4
+ module Encoder
5
+ RPC_HELLO_MESSAGE = "\x48\x45\x4C\x4C\x4F\x2D\x52\x50\x43\x00\x00\x00"
6
+ STREAM_HELLO_MESSAGE = "\x48\x45\x4C\x4C\x4F\x2D\x53\x54\x52\x45\x41\x4D"
7
+ NAME_LENGTH = 32
8
+
9
+ class << self
10
+
11
+ # Given a type object, and ruby object, encode the ruby object
12
+ def encode(obj, type, type_store)
13
+ if type.is_a?(Types::MessageType) then obj.serialize_to_string
14
+ elsif type.is_a?(Types::ValueType) then encode_value(obj, type)
15
+ elsif type.is_a?(Types::EnumType)
16
+ enum_value = type.ruby_type[obj]
17
+ encode_value(enum_value, type_store.as_type("int32"))
18
+ elsif type.is_a?(Types::ClassType)
19
+ remote_oid = if obj == nil then 0 else obj.remote_oid end
20
+ encode_value(remote_oid, type_store.as_type("uint64"))
21
+ elsif type.is_a?(Types::ListType)
22
+ msg = type_store.as_type("KRPC.List").ruby_type.new
23
+ msg.items = obj.map{|x| encode(x, type.value_type, type_store)}.to_a
24
+ msg.serialize_to_string
25
+ elsif type.is_a?(Types::DictionaryType)
26
+ entry_type = type_store.as_type("KRPC.DictionaryEntry").ruby_type
27
+ msg = type_store.as_type("KRPC.Dictionary").ruby_type.new
28
+ msg.entries = obj.map do |k,v|
29
+ entry = entry_type.new
30
+ entry.key = encode(k, type.key_type, type_store)
31
+ entry.value = encode(v, type.value_type, type_store)
32
+ entry
33
+ end
34
+ msg.serialize_to_string
35
+ elsif type.is_a?(Types::SetType)
36
+ msg = type_store.as_type("KRPC.Set").ruby_type.new
37
+ msg.items = obj.map{|x| encode(x, type.value_type, type_store)}.to_a
38
+ msg.serialize_to_string
39
+ elsif type.is_a?(Types::TupleType)
40
+ msg = type_store.as_type("KRPC.Tuple").ruby_type.new
41
+ msg.items = obj.zip(type.value_types).map{|x,t| encode(x, t, type_store)}.to_a
42
+ msg.serialize_to_string
43
+ else raise(RuntimeError, "Cannot encode object #{obj} of type #{type}")
44
+ end
45
+ end
46
+
47
+ def encode_value(value, type)
48
+ ProtobufUtils::Encoder.encode(value, type.protobuf_type)
49
+ end
50
+
51
+ def encode_request(req)
52
+ data = req.serialize_to_string
53
+ length = ProtobufUtils::Encoder.encode_nonnegative_varint(data.length)
54
+ length + data
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,33 @@
1
+
2
+ module KRPC
3
+
4
+ class ConnectionError < Exception; end
5
+ class RPCError < Exception; end
6
+ class ValueError < Exception; end
7
+
8
+
9
+ class ArgumentErrorSig < ArgumentError
10
+ attr_reader :message_without_signature, :signature
11
+ def initialize(msg = nil, sig = nil)
12
+ @message_without_signature, @signature = msg, sig
13
+ super(signature.nil? ? msg : msg + "\n" + signature.to_s)
14
+ end
15
+ def with_signature(sig)
16
+ self.class.new(message_without_signature, sig)
17
+ end
18
+ end
19
+
20
+ class ArgumentsNumberErrorSig < ArgumentErrorSig
21
+ attr_reader :args_count, :valid_params_count_range
22
+ def initialize(args_count, valid_params_count_range, sig = nil)
23
+ @args_count, @valid_params_count_range = args_count, valid_params_count_range
24
+ valid_params_str = (valid_params_count_range.min == valid_params_count_range.max ? valid_params_count_range.min : valid_params_count_range).to_s
25
+ super("wrong number of arguments (#{args_count} for #{valid_params_str})", sig)
26
+ end
27
+ def with_signature(sig)
28
+ self.class.new(args_count, valid_params_count_range, sig)
29
+ end
30
+ end
31
+
32
+ end
33
+
@@ -0,0 +1,117 @@
1
+ require 'krpc/doc'
2
+ require 'krpc/core_extensions'
3
+
4
+ module KRPC
5
+ module Gen
6
+ AvailableToClassAndInstanceModuleName = "AvailableToClassAndInstance"
7
+
8
+ class << self
9
+ def service_gen_module(service_name)
10
+ const_get_or_create(service_name, Module.new)
11
+ end
12
+
13
+ def generate_class(service_name, class_name)
14
+ mod = service_gen_module(service_name)
15
+ mod.const_get_or_create(class_name) do
16
+ Class.new(ClassBase) do
17
+ attr_reader :service_name
18
+ @service_name = service_name
19
+ end
20
+ end
21
+ end
22
+
23
+ def generate_enum(service_name, enum_name, values)
24
+ mod = service_gen_module(service_name)
25
+ mod.const_get_or_create(enum_name) do
26
+ values.map{|ev| [ev.name.underscore.to_sym, ev.value]}.to_h
27
+ end
28
+ end
29
+
30
+ def add_rpc_method(cls, method_name, service_name, proc, client, *options)
31
+ is_static = options.include? :static
32
+ prepend_self_to_args = options.include? :prepend_self_to_args
33
+ target_module = is_static ? cls.const_get_or_create(AvailableToClassAndInstanceModuleName, Module.new) : cls
34
+ param_names, param_types, required_params_count, param_default, return_type = parse_procedure(proc, client)
35
+ method_name = method_name.underscore
36
+
37
+ target_module.instance_eval do
38
+ define_method method_name do |*args, **kwargs|
39
+ begin
40
+ args = [self] + args if prepend_self_to_args
41
+ client.rpc(service_name, proc.name, args, kwargs, param_names, param_types, required_params_count, param_default, return_type: return_type)
42
+ rescue ArgumentsNumberErrorSig => err
43
+ sig = Doc.docstring_for_method(self, method_name)
44
+ if prepend_self_to_args then raise ArgumentsNumberErrorSig.new(err.args_count - 1, (err.valid_params_count_range.min-1)..(err.valid_params_count_range.max-1), sig)
45
+ else raise err.with_signature(sig) end
46
+ rescue ArgumentErrorSig => err
47
+ raise err.with_signature(Doc.docstring_for_method(self, method_name))
48
+ end
49
+ end
50
+ end
51
+ Doc.add_docstring_info(is_static, cls, method_name, service_name, proc, param_names, param_types, param_default, return_type: return_type)
52
+ end
53
+
54
+ private #----------------------------------
55
+
56
+ def parse_procedure(proc, client)
57
+ param_names = proc.parameters.map{|p| p.name.underscore}
58
+ param_types = proc.parameters.map.with_index do |p,i|
59
+ client.type_store.get_parameter_type(i, p.type, proc.attributes)
60
+ end
61
+ param_required = proc.parameters.map{|p| not p.has_field?("default_argument")}
62
+ required_params_count = param_required.take_while{|x| x}.size
63
+ param_default = proc.parameters.zip(param_types).map do |param, type|
64
+ if param.has_field?("default_argument")
65
+ Decoder.decode(param.default_argument, type, client.type_store)
66
+ else nil
67
+ end
68
+ end
69
+ return_type = if proc.has_field?("return_type")
70
+ client.type_store.get_return_type(proc.return_type, proc.attributes)
71
+ else nil
72
+ end
73
+ [param_names, param_types, required_params_count, param_default, return_type]
74
+ end
75
+ end
76
+
77
+ module RPCMethodGenerator
78
+ def include_rpc_method(method_name, service_name, procedure_name, return_type: nil)
79
+ Gen.add_rpc_method(self.class, method_name, service_name, PB::Procedure.new(name: procedure_name, return_type: return_type), client)
80
+ end
81
+ end
82
+
83
+ module AvailableToClassAndInstanceMethodsHandler
84
+ def add_methods_available_to_class_and_instance
85
+ if const_defined? AvailableToClassAndInstanceModuleName
86
+ extend const_get(AvailableToClassAndInstanceModuleName)
87
+ include const_get(AvailableToClassAndInstanceModuleName)
88
+ end
89
+ end
90
+ end
91
+
92
+ class ClassBase
93
+ extend AvailableToClassAndInstanceMethodsHandler
94
+ include Doc::SuffixMethods
95
+
96
+ attr_reader :remote_oid
97
+
98
+ def initialize(remote_oid)
99
+ @remote_oid = remote_oid
100
+ end
101
+
102
+ alias_method :eql?, :==
103
+ def ==(other)
104
+ other.class == self.class and other.remote_oid == remote_oid
105
+ end
106
+ def hash
107
+ remote_oid.hash
108
+ end
109
+
110
+ def self.krpc_name
111
+ name[11..-1]
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+
@@ -0,0 +1,134 @@
1
+ require 'krpc/KRPC.pb'
2
+
3
+ module KRPC
4
+ module ProtobufUtils
5
+ class << self
6
+ def create_PB_to_PB_message_class_hash(package)
7
+ protobuf_module = Kernel.const_get(package.gsub(".","::") + "::PB")
8
+ msg_classes_names = protobuf_module.constants.select{|c| protobuf_module.const_get(c,false).superclass == ::Protobuf::Message}
9
+ msg_classes_names.map do |name|
10
+ [package + "." + name.to_s, protobuf_module.const_get(name,false)]
11
+ end.to_h
12
+ end
13
+ end
14
+
15
+ module Decoder
16
+ class << self
17
+ def decode(bytes, type)
18
+ meth_name = "decode_" + type
19
+ raise RuntimeError.new("Unsupported type #{type}") unless respond_to?(meth_name)
20
+ send(meth_name, bytes)
21
+ end
22
+
23
+ # based on: https://developers.google.com/protocol-buffers/docs/encoding#varints & http://www.rubydoc.info/gems/ruby-protocol-buffers/1.0.1/ProtocolBuffers/Varint#decode-class_method & https://github.com/google/protobuf/blob/master/python/google/protobuf/internal/decoder.py#L136
24
+ def decode_varint(bytes)
25
+ decode_varint_pos(bytes)[0]
26
+ end
27
+ def decode_varint_pos(bytes)
28
+ pos = 0
29
+ result = 0
30
+ shift = 0
31
+ loop do
32
+ byte = bytes[pos].ord # <-- replace ".ord" with ".bytes[0]" if ".ord" start to cause problems again
33
+ binding.pry if byte > 130
34
+ pos += 1
35
+ result |= (byte & 0b0111_1111) << shift
36
+ return [result, pos] if (byte & 0b1000_0000) == 0
37
+ shift += 7
38
+ raise(RuntimeError, "too many bytes when decoding varint") if shift >= 64
39
+ end
40
+ end
41
+ def decode_signed_varint(bytes)
42
+ result = decode_varint(bytes)
43
+ result -= (1 << 64) if result > 0x7fffffffffffffff
44
+ result
45
+ end
46
+
47
+ alias_method :decode_int32, :decode_signed_varint
48
+ alias_method :decode_int64, :decode_signed_varint
49
+ alias_method :decode_uint32, :decode_varint
50
+ alias_method :decode_uint64, :decode_varint
51
+
52
+ # based on: https://github.com/ruby-protobuf/protobuf/search?q=pack
53
+ def decode_float(bytes)
54
+ bytes.unpack('e').first
55
+ end
56
+ def decode_double(bytes)
57
+ bytes.unpack('E').first
58
+ end
59
+ def decode_bool(bytes)
60
+ decode_varint(bytes) != 0
61
+ end
62
+ def decode_string(bytes) #TODO: String encoding should be utf-8
63
+ size, pos = decode_varint_pos(bytes)
64
+ bytes[pos..(pos+size)]
65
+ end
66
+ def decode_bytes(bytes)
67
+ size, pos = decode_varint_pos(bytes)
68
+ bytes[pos..(pos+size)]
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ module Encoder
75
+ class << self
76
+ def encode(value, type)
77
+ meth_name = "encode_" + type
78
+ raise(RuntimeError, "Unsupported type #{type}") unless respond_to?(meth_name)
79
+ send(meth_name, value)
80
+ end
81
+
82
+ # based on: http://www.rubydoc.info/gems/ruby-protocol-buffers/1.0.1/ProtocolBuffers/Varint#decode-class_method & https://github.com/google/protobuf/blob/master/python/google/protobuf/internal/encoder.py#L390
83
+ def encode_varint(value)
84
+ return [value].pack('C') if value < 0b1000_0000
85
+ result = ""
86
+ loop do
87
+ byte = value & 0b0111_1111
88
+ value >>= 7
89
+ if value == 0
90
+ return result << byte.chr
91
+ else
92
+ result << (byte | 0b1000_0000).chr
93
+ end
94
+ end
95
+ end
96
+ def encode_signed_varint(value)
97
+ value += (1 << 64) if value < 0
98
+ encode_varint(value)
99
+ end
100
+ def encode_nonnegative_varint(value)
101
+ raise(RangeError, "Value must be non-negative, got #{value}") if value < 0
102
+ encode_varint(value)
103
+ end
104
+
105
+ alias_method :encode_int32, :encode_signed_varint
106
+ alias_method :encode_int64, :encode_signed_varint
107
+ alias_method :encode_uint32, :encode_nonnegative_varint
108
+ alias_method :encode_uint64, :encode_nonnegative_varint
109
+
110
+ def encode_float(value)
111
+ [value].pack('e')
112
+ end
113
+ def encode_double(value)
114
+ [value].pack('E')
115
+ end
116
+ def encode_bool(value)
117
+ encode_varint(value ? 1 : 0)
118
+ end
119
+ def encode_string(value)
120
+ size = encode_varint(value.bytesize)
121
+ size + value.bytes.map(&:chr).join.b
122
+ end
123
+ def encode_bytes(value)
124
+ size = encode_varint(value.size)
125
+ size + value.map(&:chr).join.b
126
+ end
127
+
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+
134
+