krpc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dd2789a9c287acf5c0102288bf37d79551d85bf1
4
+ data.tar.gz: c4135dc71e1b1e11ee8b75978ebeaf0d014e779d
5
+ SHA512:
6
+ metadata.gz: 45bab162e5b7402b9553fc2e4fef9a1bbb8a048c40985e14b9fb53f1fe69580ae84da35813709b1cd8424100bdb67b1e2a5d2a304fda27efab38c5ef66110bb7
7
+ data.tar.gz: c2c5c2cf56506d4cb0df4940ed87945a464f64610aed0d47443dd0d3a1b74669f832f77e84256a2e33fb3fec5e6a6f8478e436d714bd4ddd75099d6a724ac81a
@@ -0,0 +1,6 @@
1
+ *.*~
2
+
3
+ *.gem
4
+ Gemfile.lock
5
+ .bundle
6
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in krpc.gemspec
4
+ gemspec
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "krpc"
8
+ s.version = "0.1.0"
9
+ s.authors = ["Tomasz Więch"]
10
+ s.email = ["tewu.dev@gmail.com"]
11
+
12
+ s.summary = "Client library for kRPC"
13
+ s.homepage = "https://github.com/TeWu/krpc-rb"
14
+ s.license = "GPL-3.0"
15
+
16
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency "ruby_protobuf", "~> 0.4"
20
+ s.add_runtime_dependency "colorize", "~> 0.7"
21
+
22
+ s.add_development_dependency "bundler", "~> 1.10"
23
+ end
24
+
@@ -0,0 +1 @@
1
+ require 'krpc/client'
@@ -0,0 +1,125 @@
1
+
2
+ ### Generated by rprotoc. DO NOT EDIT!
3
+
4
+ require 'protobuf/message/message'
5
+ require 'protobuf/message/enum'
6
+ require 'protobuf/message/service'
7
+ require 'protobuf/message/extend'
8
+
9
+ module KRPC
10
+ module PB
11
+ class Request < ::Protobuf::Message
12
+ defined_in __FILE__
13
+ required :string, :service, 1
14
+ required :string, :procedure, 2
15
+ repeated :Argument, :arguments, 3
16
+ end
17
+ class Argument < ::Protobuf::Message
18
+ defined_in __FILE__
19
+ required :uint32, :position, 1
20
+ required :bytes, :value, 2
21
+ end
22
+ class Response < ::Protobuf::Message
23
+ defined_in __FILE__
24
+ required :double, :time, 1
25
+ optional :string, :error, 2
26
+ optional :bytes, :return_value, 3
27
+ end
28
+ class StreamMessage < ::Protobuf::Message
29
+ defined_in __FILE__
30
+ repeated :StreamResponse, :responses, 1
31
+ end
32
+ class StreamResponse < ::Protobuf::Message
33
+ defined_in __FILE__
34
+ required :uint32, :id, 1
35
+ required :Response, :response, 2
36
+ end
37
+ class Services < ::Protobuf::Message
38
+ defined_in __FILE__
39
+ repeated :Service, :services, 1
40
+ end
41
+ class Service < ::Protobuf::Message
42
+ defined_in __FILE__
43
+ required :string, :name, 1
44
+ repeated :Procedure, :procedures, 2
45
+ repeated :Class, :classes, 3
46
+ repeated :Enumeration, :enumerations, 4
47
+ optional :string, :documentation, 5
48
+ end
49
+ class Procedure < ::Protobuf::Message
50
+ defined_in __FILE__
51
+ required :string, :name, 1
52
+ repeated :Parameter, :parameters, 2
53
+ optional :string, :return_type, 3
54
+ repeated :string, :attributes, 4
55
+ optional :string, :documentation, 5
56
+ end
57
+ class Parameter < ::Protobuf::Message
58
+ defined_in __FILE__
59
+ required :string, :name, 1
60
+ required :string, :type, 2
61
+ optional :bytes, :default_argument, 3
62
+ end
63
+ class Class < ::Protobuf::Message
64
+ defined_in __FILE__
65
+ required :string, :name, 1
66
+ optional :string, :documentation, 2
67
+ end
68
+ class Enumeration < ::Protobuf::Message
69
+ defined_in __FILE__
70
+ required :string, :name, 1
71
+ repeated :EnumerationValue, :values, 2
72
+ optional :string, :documentation, 3
73
+ end
74
+ class EnumerationValue < ::Protobuf::Message
75
+ defined_in __FILE__
76
+ required :string, :name, 1
77
+ required :int32, :value, 2
78
+ optional :string, :documentation, 3
79
+ end
80
+ class List < ::Protobuf::Message
81
+ defined_in __FILE__
82
+ repeated :bytes, :items, 1
83
+ end
84
+ class Dictionary < ::Protobuf::Message
85
+ defined_in __FILE__
86
+ repeated :DictionaryEntry, :entries, 1
87
+ end
88
+ class DictionaryEntry < ::Protobuf::Message
89
+ defined_in __FILE__
90
+ required :bytes, :key, 1
91
+ required :bytes, :value, 2
92
+ end
93
+ class Set < ::Protobuf::Message
94
+ defined_in __FILE__
95
+ repeated :bytes, :items, 1
96
+ end
97
+ class Tuple < ::Protobuf::Message
98
+ defined_in __FILE__
99
+ repeated :bytes, :items, 1
100
+ end
101
+ class Status < ::Protobuf::Message
102
+ defined_in __FILE__
103
+ required :string, :version, 1
104
+ required :uint64, :bytes_read, 2
105
+ required :uint64, :bytes_written, 3
106
+ required :float, :bytes_read_rate, 4
107
+ required :float, :bytes_written_rate, 5
108
+ required :uint64, :rpcs_executed, 6
109
+ required :float, :rpc_rate, 7
110
+ required :bool, :one_rpc_per_update, 8
111
+ required :uint32, :max_time_per_update, 9
112
+ required :bool, :adaptive_rate_control, 10
113
+ required :bool, :blocking_recv, 11
114
+ required :uint32, :recv_timeout, 12
115
+ required :float, :time_per_rpc_update, 13
116
+ required :float, :poll_time_per_rpc_update, 14
117
+ required :float, :exec_time_per_rpc_update, 15
118
+ required :uint32, :stream_rpcs, 16
119
+ required :uint64, :stream_rpcs_executed, 17
120
+ required :float, :stream_rpc_rate, 18
121
+ required :float, :time_per_stream_update, 19
122
+ end
123
+ end
124
+ end
125
+
@@ -0,0 +1,81 @@
1
+ require 'krpc/error'
2
+
3
+ module KRPC
4
+ module Attributes
5
+ class << self
6
+
7
+ def is_any_start_with?(attrs, prefix)
8
+ attrs.any?{|a| a.start_with? prefix }
9
+ end
10
+ alias_method :asw?, :is_any_start_with?
11
+
12
+ def is_a_property_accessor(attrs) asw?(attrs,"Property.") end
13
+ def is_a_property_getter(attrs) asw?(attrs,"Property.Get(") end
14
+ def is_a_property_setter(attrs) asw?(attrs,"Property.Set(") end
15
+ def is_a_class_method_or_property_accessor(attrs) asw?(attrs,"Class.") end
16
+ def is_a_class_method(attrs) asw?(attrs,"Class.Method(") end
17
+ def is_a_class_static_method(attrs) asw?(attrs,"Class.StaticMethod(") end
18
+ def is_a_class_property_accessor(attrs) asw?(attrs,"Class.Property.") end
19
+ def is_a_class_property_getter(attrs) asw?(attrs,"Class.Property.Get(") end
20
+ def is_a_class_property_setter(attrs) asw?(attrs,"Class.Property.Set(") end
21
+
22
+ def get_service_name(attrs)
23
+ if is_a_class_method(attrs) || is_a_class_static_method(attrs)
24
+ attrs.each do |a|
25
+ return $1 if /^Class\.(?:Static)?Method\(([^,\.]+)\.[^,]+,[^,]+\)$/ =~ a
26
+ end
27
+ elsif is_a_class_property_accessor(attrs)
28
+ attrs.each do |a|
29
+ return $1 if /^Class\.Property.(?:Get|Set)\(([^,\.]+)\.[^,]+,[^,]+\)$/ =~ a
30
+ end
31
+ end
32
+ raise(ValueError, "Procedure attributes are not a class method or property accessor")
33
+ end
34
+
35
+ def get_class_name(attrs)
36
+ if is_a_class_method(attrs) || is_a_class_static_method(attrs)
37
+ attrs.each do |a|
38
+ return $1 if /^Class\.(?:Static)?Method\([^,\.]+\.([^,\.]+),[^,]+\)$/ =~ a
39
+ end
40
+ elsif is_a_class_property_accessor(attrs)
41
+ attrs.each do |a|
42
+ return $1 if /^Class\.Property.(?:Get|Set)\([^,\.]+\.([^,]+),[^,]+\)$/ =~ a
43
+ end
44
+ end
45
+ raise(ValueError, "Procedure attributes are not a class method or property accessor")
46
+ end
47
+
48
+ def get_property_name(attrs)
49
+ if is_a_property_accessor(attrs)
50
+ attrs.each do |a|
51
+ return $1 if /^Property\.(?:Get|Set)\((.+)\)$/ =~ a
52
+ end
53
+ end
54
+ raise(ValueError, "Procedure attributes are not a property accessor")
55
+ end
56
+
57
+ def get_class_method_or_property_name(attrs)
58
+ if is_a_class_method(attrs) || is_a_class_static_method(attrs) || is_a_class_property_accessor(attrs)
59
+ attrs.each do |a|
60
+ return $1 if /^Class\.(?:(?:Static)?Method|Property\.(?:Get|Set))\([^,]+,([^,]+)\)$/ =~ a
61
+ end
62
+ end
63
+ raise(ValueError, "Procedure attributes are not a class method or class property accessor")
64
+ end
65
+
66
+ def get_parameter_type_attrs(pos, attrs)
67
+ attrs.map do |a|
68
+ (/^ParameterType\(#{pos}\).(.+)$/ =~ a) ? $1 : nil
69
+ end.compact
70
+ end
71
+
72
+ def get_return_type_attrs(attrs)
73
+ attrs.map do |a|
74
+ (/^ReturnType.(.+)$/ =~ a) ? $1 : nil
75
+ end.compact
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,130 @@
1
+ require 'krpc/connection'
2
+ require 'krpc/service'
3
+ require 'krpc/types'
4
+ require 'krpc/encoder'
5
+ require 'krpc/decoder'
6
+ require 'krpc/error'
7
+ require 'krpc/core_extensions'
8
+ require 'krpc/KRPC.pb'
9
+
10
+ module KRPC
11
+ class Client
12
+ DEFAULT_NAME = ""
13
+
14
+ include Doc::SuffixMethods
15
+
16
+ attr_reader :name, :rpc_connection, :stream_connection, :type_store, :krpc
17
+
18
+ def initialize(name = DEFAULT_NAME, host = Connection::DEFAULT_SERVER_HOST, rpc_port = Connection::DEFAULT_SERVER_RPC_PORT, stream_port = Connection::DEFAULT_SERVER_STREAM_PORT)
19
+ @name = name
20
+ @rpc_connection = RPCConncetion.new(name, host, rpc_port)
21
+ @stream_connection = StreamConncetion.new(rpc_connection, host, stream_port)
22
+ @type_store = Types::TypeStore.new
23
+ @krpc = Services::KRPC.new(self)
24
+ Doc.add_docstring_info(false, self.class, "krpc", return_type: @krpc.class)
25
+ end
26
+
27
+ def connect
28
+ rpc_connection.connect
29
+ stream_connection.connect
30
+ self
31
+ end
32
+
33
+ def connect!
34
+ connect
35
+ generate_services_api!
36
+ self
37
+ end
38
+
39
+ def close
40
+ stream_connection.close
41
+ rpc_connection.close
42
+ end
43
+
44
+ def connected?
45
+ rpc_connection.connected?
46
+ end
47
+
48
+ def generate_services_api!
49
+ return self if services_api_generated?
50
+ raise(Exception, "Can't generate services API while not connected to server -- call Client#connect! to connect to server and generate services API in one call") if not connected?
51
+
52
+ resp = krpc.get_services
53
+ resp.services.each do |service_msg|
54
+ next if service_msg.name == "KRPC"
55
+ service = Services.create_service(service_msg, self)
56
+ method_name = service.class.class_name.underscore
57
+ self.class.instance_eval do
58
+ define_method method_name do service end
59
+ end
60
+ Doc.add_docstring_info(false, self.class, method_name, return_type: service.class)
61
+ end
62
+ self
63
+ end
64
+
65
+ def services_api_generated?
66
+ respond_to? :space_center
67
+ end
68
+
69
+ def rpc(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], required_params_count=0, param_default=[], return_type: nil)
70
+ # Send request
71
+ req = build_request(service, procedure, args, kwargs, param_names, param_types, required_params_count, param_default)
72
+ rpc_connection.send Encoder.encode_request(req)
73
+ # Receive response
74
+ resp_length = rpc_connection.recv_varint
75
+ resp_data = rpc_connection.recv resp_length
76
+ resp = PB::Response.new
77
+ resp.parse_from_string resp_data
78
+ # Check for an error response
79
+ raise(RPCError, resp.error) if resp.has_field? "error"
80
+ # Optionally decode and return the response' return value
81
+ if return_type == nil
82
+ nil
83
+ else
84
+ Decoder.decode(resp.return_value, return_type, type_store)
85
+ end
86
+ rescue IOError => e
87
+ raise(Exception, "RPC call attempt while not connected to server -- call Client#connect first") if not connected?
88
+ raise e
89
+ end
90
+
91
+ protected #----------------------------------
92
+
93
+ def build_request(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], required_params_count=0, param_default=[])
94
+ begin
95
+ raise(ArgumentError, "param_names and param_types should be equal length\n\tparam_names = #{param_names}\n\tparam_types = #{param_types}") unless param_names.size == param_types.size
96
+ raise ArgumentsNumberErrorSig.new(args.count, required_params_count..param_names.count) unless args.count <= param_names.count
97
+ kwargs_remaining = kwargs.count
98
+
99
+ param_names_symbols = param_names.map(&:to_sym)
100
+ req_args = param_names_symbols.map.with_index do |name,i|
101
+ is_kwarg = kwargs.has_key? name
102
+ raise ArgumentErrorSig.new("there are both positional and keyword arguments for parameter \"#{name}\"") if is_kwarg && i < args.count
103
+ kwargs_remaining -= 1 if is_kwarg
104
+ unless i >= required_params_count &&
105
+ (!is_kwarg && i >= args.count ||
106
+ !is_kwarg && args[i] == param_default[i] ||
107
+ is_kwarg && kwargs[name] == param_default[i])
108
+ arg = if is_kwarg then kwargs[name]
109
+ elsif i < args.count then args[i]
110
+ else raise ArgumentErrorSig.new("missing argument for parameter \"#{name}\"")
111
+ end
112
+ begin
113
+ arg = type_store.coerce_to(arg, param_types[i])
114
+ rescue ValueError
115
+ raise ArgumentErrorSig.new("argument for parameter \"#{name}\" must be a #{param_types[i].ruby_type} -- got #{args[i]} of type #{args[i].class}")
116
+ end
117
+ v = Encoder.encode(arg, param_types[i], type_store)
118
+ PB::Argument.new(position: i, value: v)
119
+ end
120
+ end.compact
121
+ raise ArgumentErrorSig.new("keyword arguments for non existing parameters: #{(kwargs.keys - param_names_symbols).join(", ")}") unless kwargs_remaining == 0
122
+ rescue ArgumentErrorSig => err
123
+ raise err.with_signature(Doc.docstring_for_procedure(service, procedure))
124
+ end
125
+ PB::Request.new(service: service, procedure: procedure, arguments: req_args)
126
+ end
127
+
128
+ end
129
+ end
130
+
@@ -0,0 +1,108 @@
1
+ require 'krpc/encoder'
2
+ require 'krpc/decoder'
3
+ require 'socket'
4
+
5
+ module KRPC
6
+
7
+ class Connection
8
+ DEFAULT_SERVER_HOST = "127.0.0.1"
9
+ DEFAULT_SERVER_RPC_PORT = 50000
10
+ DEFAULT_SERVER_STREAM_PORT = 50001
11
+
12
+ attr_reader :host, :port, :socket
13
+
14
+ def initialize(host, port)
15
+ @host, @port = host, port
16
+ end
17
+
18
+ def connect
19
+ if connected? then raise(ConnectionError, "Already connected")
20
+ else
21
+ @socket = TCPSocket.open(host, port)
22
+ begin
23
+ handshake
24
+ rescue Exception => e
25
+ close
26
+ raise e
27
+ end
28
+ end
29
+ self
30
+ end
31
+
32
+ def close
33
+ if connected?
34
+ socket.close
35
+ cleanup
36
+ true
37
+ else false end
38
+ end
39
+
40
+ def connected?
41
+ !socket.nil? && !socket.closed?
42
+ end
43
+
44
+ def handshake; end
45
+ def cleanup; end
46
+
47
+ def send(msg) @socket.send(msg,0) end
48
+ def recv(maxlen = 1) @socket.recv(maxlen) end
49
+
50
+ def recv_varint
51
+ int_val = 0
52
+ shift = 0
53
+ loop do
54
+ byte = recv.ord
55
+ int_val |= (byte & 0b0111_1111) << shift
56
+ return int_val if (byte & 0b1000_0000) == 0
57
+ shift += 7
58
+ raise(RuntimeError, "too many bytes when decoding varint") if shift >= 64
59
+ sleep 0.1
60
+ end
61
+ end
62
+
63
+ protected #----------------------------------
64
+
65
+ def trim_fill(str, len, fill_char = "\x00")
66
+ str = str.encode("UTF-8")[0,len]
67
+ str + fill_char*(len-str.length)
68
+ end
69
+ end
70
+
71
+ class RPCConncetion < Connection
72
+ attr_reader :name, :client_id
73
+
74
+ def initialize(name, host = DEFAULT_SERVER_HOST, port = DEFAULT_SERVER_RPC_PORT)
75
+ super host, port
76
+ @name = name
77
+ end
78
+
79
+ def handshake
80
+ send Encoder::RPC_HELLO_MESSAGE
81
+ send trim_fill(name, Encoder::NAME_LENGTH)
82
+ @client_id = recv Decoder::GUID_LENGTH
83
+ end
84
+
85
+ def cleanup
86
+ @client_id = nil
87
+ end
88
+ end
89
+
90
+ class StreamConncetion < Connection
91
+ attr_reader :rpc_connection
92
+
93
+ def initialize(rpc_connection, host = DEFAULT_SERVER_HOST, port = DEFAULT_SERVER_STREAM_PORT)
94
+ super host, port
95
+ @rpc_connection = rpc_connection
96
+ end
97
+
98
+ def handshake
99
+ raise(ConnectionError, "RPC connection must optain client_id before stream connection can perform valid handshake - closing stream connection") if rpc_connection.client_id.nil?
100
+ send Encoder::STREAM_HELLO_MESSAGE
101
+ send rpc_connection.client_id
102
+ resp = recv Decoder::OK_LENGTH
103
+ raise ConnectionError unless resp == Decoder::OK_MESSAGE
104
+ end
105
+ end
106
+
107
+ end
108
+