krpc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/krpc.gemspec +24 -0
- data/lib/krpc.rb +1 -0
- data/lib/krpc/KRPC.pb.rb +125 -0
- data/lib/krpc/attributes.rb +81 -0
- data/lib/krpc/client.rb +130 -0
- data/lib/krpc/connection.rb +108 -0
- data/lib/krpc/core_extensions.rb +28 -0
- data/lib/krpc/decoder.rb +54 -0
- data/lib/krpc/doc.rb +124 -0
- data/lib/krpc/encoder.rb +60 -0
- data/lib/krpc/error.rb +33 -0
- data/lib/krpc/gen.rb +117 -0
- data/lib/krpc/protobuf_utils.rb +134 -0
- data/lib/krpc/repl_tools.rb +12 -0
- data/lib/krpc/service.rb +96 -0
- data/lib/krpc/types.rb +237 -0
- metadata +105 -0
@@ -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
|
+
|
data/lib/krpc/decoder.rb
ADDED
@@ -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
|
+
|
data/lib/krpc/doc.rb
ADDED
@@ -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
|
+
|
data/lib/krpc/encoder.rb
ADDED
@@ -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
|
+
|
data/lib/krpc/error.rb
ADDED
@@ -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
|
+
|
data/lib/krpc/gen.rb
ADDED
@@ -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
|
+
|