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