rlp 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/LICENSE +21 -0
- data/README.md +3 -0
- data/lib/rlp.rb +67 -0
- data/lib/rlp/constant.rb +9 -0
- data/lib/rlp/data.rb +7 -0
- data/lib/rlp/error.rb +67 -0
- data/lib/rlp/sedes.rb +33 -0
- data/lib/rlp/sedes/big_endian_int.rb +37 -0
- data/lib/rlp/sedes/binary.rb +45 -0
- data/lib/rlp/sedes/list.rb +63 -0
- data/lib/rlp/utils.rb +35 -0
- data/lib/rlp/version.rb +3 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6b6741d638fbdc0a96769c278107372a0b7a8fd7
|
4
|
+
data.tar.gz: 583c56e5f86fb18c21022ce5c9b12e42abd24b43
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 28451e98078179c7b1290822cfbc83c28326f9b8a221973e2f24bfdbf839bd5d5eb3c6289231621c0c98e7928377d2bc5efdc9384493fe941e4168083c34a578
|
7
|
+
data.tar.gz: 537920b429404476ef93c663aec0cb91f961e152c2645c4b4d77a2fe8ae9c8b3a7b7da775da3e2f502308450fe670bc5b5cce674d53374341e0747901e3ea3e2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Jan Xie
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/lib/rlp.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rlp/constant'
|
2
|
+
require 'rlp/data'
|
3
|
+
require 'rlp/error'
|
4
|
+
require 'rlp/utils'
|
5
|
+
require 'rlp/sedes'
|
6
|
+
|
7
|
+
module RLP
|
8
|
+
include Constant
|
9
|
+
include Error
|
10
|
+
include Utils
|
11
|
+
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def encode(obj, sedes: nil, infer_serializer: true, cache: false)
|
15
|
+
# TODO: cache flow
|
16
|
+
|
17
|
+
if sedes
|
18
|
+
# TODO: customize sedes flow
|
19
|
+
#item = sedes.serialize(obj)
|
20
|
+
elsif infer_serializer
|
21
|
+
item = Sedes.infer(obj).serialize(obj)
|
22
|
+
else
|
23
|
+
item = obj
|
24
|
+
end
|
25
|
+
|
26
|
+
result = encode_raw(item)
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode_raw(item)
|
31
|
+
return item if item.instance_of?(RLP::Data)
|
32
|
+
return encode_primitive(item) if primitive?(item)
|
33
|
+
return encode_list(item) if list?(item)
|
34
|
+
|
35
|
+
msg = "Cannot encode object of type %s" % item.class.name
|
36
|
+
raise ArgumentError, msg
|
37
|
+
end
|
38
|
+
|
39
|
+
def encode_primitive(item)
|
40
|
+
return str_to_bytes(item) if item.size == 1 && item.ord < 0x80
|
41
|
+
|
42
|
+
payload = str_to_bytes item
|
43
|
+
prefix = length_prefix payload.size, PRIMITIVE_PREFIX_OFFSET
|
44
|
+
|
45
|
+
"#{prefix}#{payload}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def encode_list(list)
|
49
|
+
payload = list.map {|item| encode_raw(item) }.join
|
50
|
+
prefix = length_prefix payload.size, LIST_PREFIX_OFFSET
|
51
|
+
|
52
|
+
"#{prefix}#{payload}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def length_prefix(length, offset)
|
56
|
+
if length < SHORT_LENGTH_LIMIT
|
57
|
+
(offset+length).chr
|
58
|
+
elsif length < LONG_LENGTH_LIMIT
|
59
|
+
length_string = int_to_big_endian(length)
|
60
|
+
length_len = (offset + SHORT_LENGTH_LIMIT - 1 + length_string.size).chr
|
61
|
+
"#{length_len}#{length_string}"
|
62
|
+
else
|
63
|
+
raise ArgumentError, "Length greater than 256**8"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/lib/rlp/constant.rb
ADDED
data/lib/rlp/data.rb
ADDED
data/lib/rlp/error.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module RLP
|
2
|
+
module Error
|
3
|
+
|
4
|
+
class RLPException < Exception; end
|
5
|
+
|
6
|
+
class EncodingError < RLPException
|
7
|
+
def initialize(message, obj)
|
8
|
+
super(message)
|
9
|
+
|
10
|
+
@obj = obj
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DecodingError < RLPException
|
15
|
+
def initialize(message, rlp)
|
16
|
+
super(message)
|
17
|
+
|
18
|
+
@rlp = rlp
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SerializationError < RLPException
|
23
|
+
def initialize(message, obj)
|
24
|
+
super(message)
|
25
|
+
|
26
|
+
@obj = obj
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DeserializationError < RLPException
|
31
|
+
def initialize(message, serial)
|
32
|
+
super(message)
|
33
|
+
|
34
|
+
@serial = serial
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ListSerializationError < SerializationError
|
39
|
+
def initialize(message: nil, obj: nil, element_exception: nil, index: nil)
|
40
|
+
if message.nil?
|
41
|
+
raise ArgumentError, "index and element_exception must be present" if index.nil? || element_exception.nil?
|
42
|
+
message = 'Serialization failed because of element at index %s ("%s")' % [index, element_exception]
|
43
|
+
end
|
44
|
+
|
45
|
+
super(message, obj)
|
46
|
+
|
47
|
+
@index = index
|
48
|
+
@element_exception = element_exception
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class ListDeserializationError < DeserializationError
|
53
|
+
def initialize(message: nil, serial: nil, element_exception: nil, index: nil)
|
54
|
+
if message.nil?
|
55
|
+
raise ArgumentError, "index and element_exception must be present" if index.nil? || element_exception.nil?
|
56
|
+
message = 'Deserialization failed because of element at index %s ("%s")' % [index, element_exception]
|
57
|
+
end
|
58
|
+
|
59
|
+
super(message, serial)
|
60
|
+
|
61
|
+
@index = index
|
62
|
+
@element_exception = element_exception
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
data/lib/rlp/sedes.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'sedes/big_endian_int'
|
2
|
+
require_relative 'sedes/binary'
|
3
|
+
require_relative 'sedes/list'
|
4
|
+
|
5
|
+
module RLP
|
6
|
+
module Sedes
|
7
|
+
|
8
|
+
class <<self
|
9
|
+
def infer(obj)
|
10
|
+
return obj if sedes?(obj)
|
11
|
+
return big_endian_int if obj.is_a?(Integer) && obj >= 0
|
12
|
+
return binary if Binary.valid_type?(obj)
|
13
|
+
return List.new(elements: obj.map {|item| infer(item) }) if RLP.list?(obj)
|
14
|
+
|
15
|
+
msg = "Did not find sedes handling type %s" % obj.class.name
|
16
|
+
raise ArgumentError, msg
|
17
|
+
end
|
18
|
+
|
19
|
+
def sedes?(obj)
|
20
|
+
obj.respond_to?(:serialize) && obj.respond_to?(:deserialize)
|
21
|
+
end
|
22
|
+
|
23
|
+
def big_endian_int
|
24
|
+
@big_endian_int ||= BigEndianInt.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def binary
|
28
|
+
@binary ||= Binary.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RLP
|
2
|
+
module Sedes
|
3
|
+
class BigEndianInt
|
4
|
+
include RLP::Utils
|
5
|
+
|
6
|
+
ZERO = "\x00".force_encoding('ascii-8bit').freeze
|
7
|
+
EMPTY = ''.force_encoding('ascii-8bit').freeze
|
8
|
+
|
9
|
+
def initialize(size: nil)
|
10
|
+
@size = size
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(obj)
|
14
|
+
raise ArgumentError, "Can only serialize integers" unless obj.is_a?(Integer)
|
15
|
+
raise ArgumentError, "Cannot serialize negative integers" if obj < 0
|
16
|
+
|
17
|
+
if @size && obj >= 256**@size
|
18
|
+
msg = "Integer too large (does not fit in %s bytes)" % @size
|
19
|
+
raise ArgumentError, msg
|
20
|
+
end
|
21
|
+
|
22
|
+
s = obj == 0 ? ZERO : int_to_big_endian(obj)
|
23
|
+
|
24
|
+
@size ? "#{ZERO * [0, @size-s.size].max}#{s}" : s
|
25
|
+
end
|
26
|
+
|
27
|
+
def deserialize(serial)
|
28
|
+
raise ArgumentError, "Invalid serialization (wrong size)" if @size && serial.size != @size
|
29
|
+
raise ArgumentError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == ZERO
|
30
|
+
|
31
|
+
serial = serial || ZERO
|
32
|
+
big_endian_to_int(serial)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RLP
|
2
|
+
module Sedes
|
3
|
+
class Binary
|
4
|
+
include RLP::Utils
|
5
|
+
|
6
|
+
Infinity = 1.0 / 0.0
|
7
|
+
|
8
|
+
class <<self
|
9
|
+
def valid_type?(obj)
|
10
|
+
obj.instance_of?(String)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(min_length: 0, max_length: Infinity, allow_empty: false)
|
15
|
+
@min_length = min_length
|
16
|
+
@max_length = max_length
|
17
|
+
@allow_empty = allow_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize(obj)
|
21
|
+
raise SerializationError.new("Object is not a serializable (%s)" % obj.class, obj) unless self.class.valid_type?(obj)
|
22
|
+
|
23
|
+
serial = str_to_bytes obj
|
24
|
+
raise SerializationError.new("Object has invalid length", serial) unless valid_length?(serial.size)
|
25
|
+
|
26
|
+
serial
|
27
|
+
end
|
28
|
+
|
29
|
+
def deserialize(serial)
|
30
|
+
raise DeserializationError.new("Objects of type %s cannot be deserialized" % serial.class, serial) unless primitive?(serial)
|
31
|
+
raise DeserializationError.new("%s has invalid length" % serial.class, serial) unless valid_length?(serial.size)
|
32
|
+
|
33
|
+
serial
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def valid_length?(len)
|
39
|
+
(@min_length <= len && len <= @max_length) ||
|
40
|
+
(@allow_empty && len == 0)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RLP
|
2
|
+
module Sedes
|
3
|
+
class List < Array
|
4
|
+
include RLP::Error
|
5
|
+
include RLP::Utils
|
6
|
+
|
7
|
+
def initialize(elements: [], strict: true)
|
8
|
+
super()
|
9
|
+
|
10
|
+
@strict = strict
|
11
|
+
|
12
|
+
elements.each do |e|
|
13
|
+
if Sedes.sedes?(e)
|
14
|
+
push e
|
15
|
+
elsif list?(e)
|
16
|
+
push List.new(elements: e)
|
17
|
+
else
|
18
|
+
raise TypeError, "Instances of List must only contain sedes objects or nested sequences thereof."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialize(obj)
|
24
|
+
raise ListSerializationError.new(message: "Can only serialize sequences", obj: obj) unless list?(obj)
|
25
|
+
raise ListSerializationError.new(message: "List has wrong length", obj: obj) if (@strict && self.size != obj.size) || self.size < obj.size
|
26
|
+
|
27
|
+
result = []
|
28
|
+
obj.zip(self).each_with_index do |(element, sedes), i|
|
29
|
+
begin
|
30
|
+
result.push sedes.serialize(element)
|
31
|
+
rescue SerializationError => e
|
32
|
+
raise ListSerializationError.new(obj: obj, element_exception: e, index: i)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def deserialize(serial)
|
40
|
+
raise ListDeserializationError.new(message: 'Can only deserialize sequences', serial: serial) unless list?(serial)
|
41
|
+
raise ListDeserializationError.new(message: 'List has wrong length', serial: serial) if @strict && serial.size != self.size
|
42
|
+
|
43
|
+
result = []
|
44
|
+
|
45
|
+
len = [serial.size, self.size].min
|
46
|
+
len.times do |i|
|
47
|
+
begin
|
48
|
+
sedes = self[i]
|
49
|
+
element = serial[i]
|
50
|
+
result.push sedes.deserialize(element)
|
51
|
+
rescue DeserializationError => e
|
52
|
+
raise ListDeserializationError.new(serial: serial, element_exception: e, index: i)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#TODO: CountableList
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/lib/rlp/utils.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module RLP
|
2
|
+
module Utils
|
3
|
+
def primitive?(item)
|
4
|
+
item.instance_of?(String)
|
5
|
+
end
|
6
|
+
|
7
|
+
def list?(item)
|
8
|
+
item.respond_to?(:each)
|
9
|
+
end
|
10
|
+
|
11
|
+
def bytes_to_str(v)
|
12
|
+
v.unpack('U*').pack('U*')
|
13
|
+
end
|
14
|
+
|
15
|
+
def str_to_bytes(v)
|
16
|
+
v.dup.force_encoding('ascii-8bit')
|
17
|
+
end
|
18
|
+
|
19
|
+
def big_endian_to_int(v)
|
20
|
+
v.unpack('H*').first.to_i(16)
|
21
|
+
end
|
22
|
+
|
23
|
+
def int_to_big_endian(v)
|
24
|
+
hex = v.to_s(16)
|
25
|
+
hex = "0#{hex}" if hex.size.odd?
|
26
|
+
[hex].pack('H*')
|
27
|
+
end
|
28
|
+
|
29
|
+
def encode_hex(b)
|
30
|
+
raise ArgumentError, "Value must be an instance of String" unless b.instance_of?(String)
|
31
|
+
b = str_to_bytes(b) unless b.encoding.name == 'ASCII-8BIT'
|
32
|
+
b.unpack("H*").first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rlp/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rlp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jan Xie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 10.5.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 10.5.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.8.3
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.8.3
|
41
|
+
description: A Ruby implementation of Ethereum's Recursive Length Prefix encoding
|
42
|
+
(RLP).
|
43
|
+
email:
|
44
|
+
- jan.h.xie@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- lib/rlp.rb
|
52
|
+
- lib/rlp/constant.rb
|
53
|
+
- lib/rlp/data.rb
|
54
|
+
- lib/rlp/error.rb
|
55
|
+
- lib/rlp/sedes.rb
|
56
|
+
- lib/rlp/sedes/big_endian_int.rb
|
57
|
+
- lib/rlp/sedes/binary.rb
|
58
|
+
- lib/rlp/sedes/list.rb
|
59
|
+
- lib/rlp/utils.rb
|
60
|
+
- lib/rlp/version.rb
|
61
|
+
homepage: https://github.com/janx/ruby-rlp
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.4.5
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: The ruby RLP serialization library.
|
85
|
+
test_files: []
|
86
|
+
has_rdoc:
|