krpc 0.1.0

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.
@@ -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
+