rlp-lite 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +13 -0
- data/README.md +33 -0
- data/Rakefile +31 -0
- data/lib/rlp-lite/decoder.rb +97 -0
- data/lib/rlp-lite/encoder.rb +59 -0
- data/lib/rlp-lite/sedes/big_endian_int.rb +73 -0
- data/lib/rlp-lite/sedes/binary.rb +103 -0
- data/lib/rlp-lite/sedes/list.rb +68 -0
- data/lib/rlp-lite/sedes.rb +73 -0
- data/lib/rlp-lite/util.rb +108 -0
- data/lib/rlp-lite/version.rb +5 -0
- data/lib/rlp-lite.rb +82 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b94a62e8d77c1f5752f02e1088db2394fb281800a444fc744bcb17e2c8bb1d3f
|
4
|
+
data.tar.gz: 4708183e54acac23ef32ebd0f74c2b78cdced4778752dbe6b2d51260363dd6b8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5fadf8e0fe94f689ba0ee198bd7e2d94c5bd6d606261d26c2308a2a676fe9ddb9e5489ec00ff31b2e592987727982b02bdeb48dea3ee105c8ab8ca1fa8b9988a
|
7
|
+
data.tar.gz: 390a76f7d97500ee445e653a98eb250ff254f0936167e6900444f5e3540439453023f463c7272ed5d453416d2f2d2a46cd884736dc9399f26b6b1eb95c1c1f0a
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
CHANGELOG.md
|
2
|
+
Manifest.txt
|
3
|
+
README.md
|
4
|
+
Rakefile
|
5
|
+
lib/rlp-lite.rb
|
6
|
+
lib/rlp-lite/decoder.rb
|
7
|
+
lib/rlp-lite/encoder.rb
|
8
|
+
lib/rlp-lite/sedes.rb
|
9
|
+
lib/rlp-lite/sedes/big_endian_int.rb
|
10
|
+
lib/rlp-lite/sedes/binary.rb
|
11
|
+
lib/rlp-lite/sedes/list.rb
|
12
|
+
lib/rlp-lite/util.rb
|
13
|
+
lib/rlp-lite/version.rb
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Recursive Length Prefix (RLP) Lite
|
2
|
+
|
3
|
+
|
4
|
+
rlp-lite - light-weight machinery to serialize / deserialze via rlp
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
* home :: [github.com/pixelartexchange/artbase](https://github.com/pixelartexchange/artbase)
|
9
|
+
* bugs :: [github.com/pixelartexchange/artbase/issues](https://github.com/pixelartexchange/artbase/issues)
|
10
|
+
* gem :: [rubygems.org/gems/rlp-lite](https://rubygems.org/gems/rlp-lite)
|
11
|
+
* rdoc :: [rubydoc.info/gems/rlp-lite](http://rubydoc.info/gems/rlp-lite)
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
to be done
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## License
|
24
|
+
|
25
|
+
The scripts are dedicated to the public domain.
|
26
|
+
Use it as you please with no restrictions whatsoever.
|
27
|
+
|
28
|
+
|
29
|
+
## Questions? Comments?
|
30
|
+
|
31
|
+
|
32
|
+
Post them on the [D.I.Y. Punk (Pixel) Art reddit](https://old.reddit.com/r/DIYPunkArt). Thanks.
|
33
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
require './lib/rlp-lite/version.rb'
|
3
|
+
|
4
|
+
|
5
|
+
Hoe.spec 'rlp-lite' do
|
6
|
+
|
7
|
+
self.version = Rlp::VERSION
|
8
|
+
|
9
|
+
self.summary = "rlp-lite - light-weight machinery to serialize / deserialze via rlp (recursive length prefix)"
|
10
|
+
self.description = summary
|
11
|
+
|
12
|
+
self.urls = { home: 'https://github.com/pixelartexchange/artbase' }
|
13
|
+
|
14
|
+
self.author = 'Gerald Bauer'
|
15
|
+
self.email = 'wwwmake@googlegroups.com'
|
16
|
+
|
17
|
+
# switch extension to .markdown for gihub formatting
|
18
|
+
self.readme_file = 'README.md'
|
19
|
+
self.history_file = 'CHANGELOG.md'
|
20
|
+
|
21
|
+
self.extra_deps = [
|
22
|
+
]
|
23
|
+
|
24
|
+
self.licenses = ['Public Domain']
|
25
|
+
|
26
|
+
self.spec_extras = {
|
27
|
+
required_ruby_version: '>= 2.3'
|
28
|
+
}
|
29
|
+
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Rlp
|
2
|
+
|
3
|
+
# Provides an RLP-decoder.
|
4
|
+
module Decoder
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Decodes an RLP-encoded object.
|
8
|
+
#
|
9
|
+
# @param rlp [String] an RLP-encoded object.
|
10
|
+
# @return [Object] the decoded and maybe deserialized object.
|
11
|
+
# @raise [Eth::Rlp::DecodingError] if the input string does not end after
|
12
|
+
# the root item.
|
13
|
+
def perform(rlp)
|
14
|
+
rlp = Util.hex_to_bin( rlp ) if Util.is_hex?( rlp )
|
15
|
+
rlp = Util.str_to_bytes( rlp )
|
16
|
+
begin
|
17
|
+
item, next_start = consume_item( rlp, 0 )
|
18
|
+
rescue Exception => e
|
19
|
+
raise DecodingError, "Cannot decode rlp string: #{e}"
|
20
|
+
end
|
21
|
+
raise DecodingError, "RLP string ends with #{rlp.size - next_start} superfluous bytes" if next_start != rlp.size
|
22
|
+
return item
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Consume an RLP-encoded item from the given start.
|
29
|
+
def consume_item(rlp, start)
|
30
|
+
t, l, s = consume_length_prefix rlp, start
|
31
|
+
consume_payload rlp, s, t, l
|
32
|
+
end
|
33
|
+
|
34
|
+
# Consume an RLP length prefix at the given position.
|
35
|
+
def consume_length_prefix(rlp, start)
|
36
|
+
b0 = rlp[start].ord
|
37
|
+
if b0 < PRIMITIVE_PREFIX_OFFSET
|
38
|
+
|
39
|
+
# single byte
|
40
|
+
[:str, 1, start]
|
41
|
+
elsif b0 < PRIMITIVE_PREFIX_OFFSET + SHORT_LENGTH_LIMIT
|
42
|
+
raise DecodingError, "Encoded as short string although single byte was possible" if (b0 - PRIMITIVE_PREFIX_OFFSET == 1) && rlp[start + 1].ord < PRIMITIVE_PREFIX_OFFSET
|
43
|
+
|
44
|
+
# short string
|
45
|
+
[:str, b0 - PRIMITIVE_PREFIX_OFFSET, start + 1]
|
46
|
+
elsif b0 < LIST_PREFIX_OFFSET
|
47
|
+
enforce_no_zero_bytes rlp, start
|
48
|
+
|
49
|
+
# long string
|
50
|
+
ll = b0 - PRIMITIVE_PREFIX_OFFSET - SHORT_LENGTH_LIMIT + 1
|
51
|
+
l = Util.big_endian_to_int rlp[(start + 1)...(start + 1 + ll)]
|
52
|
+
raise DecodingError, "Long string prefix used for short string" if l < SHORT_LENGTH_LIMIT
|
53
|
+
[:str, l, start + 1 + ll]
|
54
|
+
elsif b0 < LIST_PREFIX_OFFSET + SHORT_LENGTH_LIMIT
|
55
|
+
|
56
|
+
# short list
|
57
|
+
[:list, b0 - LIST_PREFIX_OFFSET, start + 1]
|
58
|
+
else
|
59
|
+
enforce_no_zero_bytes rlp, start
|
60
|
+
|
61
|
+
# long list
|
62
|
+
ll = b0 - LIST_PREFIX_OFFSET - SHORT_LENGTH_LIMIT + 1
|
63
|
+
l = Util.big_endian_to_int rlp[(start + 1)...(start + 1 + ll)]
|
64
|
+
raise DecodingError, "Long list prefix used for short list" if l < SHORT_LENGTH_LIMIT
|
65
|
+
[:list, l, start + 1 + ll]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Enforce RLP slices to not start with empty bytes.
|
70
|
+
def enforce_no_zero_bytes(rlp, start)
|
71
|
+
raise DecodingError, "Length starts with zero bytes" if rlp.slice(start + 1) == BYTE_ZERO
|
72
|
+
end
|
73
|
+
|
74
|
+
# Consume an RLP payload at the given position of given type and size.
|
75
|
+
def consume_payload(rlp, start, type, length)
|
76
|
+
case type
|
77
|
+
when :str
|
78
|
+
[rlp[start...(start + length)], start + length]
|
79
|
+
when :list
|
80
|
+
items = []
|
81
|
+
next_item_start = start
|
82
|
+
payload_end = next_item_start + length
|
83
|
+
while next_item_start < payload_end
|
84
|
+
item, next_item_start = consume_item rlp, next_item_start
|
85
|
+
items.push item
|
86
|
+
end
|
87
|
+
raise DecodingError, "List length prefix announced a too small length" if next_item_start > payload_end
|
88
|
+
[items, next_item_start]
|
89
|
+
else
|
90
|
+
raise TypeError, "Type must be either :str or :list"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end # module Rlp
|
97
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
# Provides an recursive-length prefix (RLP) encoder and decoder.
|
3
|
+
module Rlp
|
4
|
+
|
5
|
+
# Provides an RLP-encoder.
|
6
|
+
module Encoder
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Encodes a Ruby object in RLP format.
|
10
|
+
#
|
11
|
+
# @param obj [Object] a Ruby object.
|
12
|
+
# @return [String] the RLP encoded item.
|
13
|
+
# @raise [Eth::Rlp::EncodingError] in the rather unlikely case that the item
|
14
|
+
# is too big to encode (will not happen).
|
15
|
+
# @raise [Eth::Rlp::SerializationError] if the serialization fails.
|
16
|
+
def perform(obj)
|
17
|
+
item = Sedes.infer(obj).serialize(obj)
|
18
|
+
result = encode_raw( item )
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Encodes the raw item.
|
24
|
+
def encode_raw(item)
|
25
|
+
return item if item.instance_of? Rlp::Data
|
26
|
+
return encode_primitive item if Sedes.is_primitive? item
|
27
|
+
return encode_list item if Sedes.is_list? item
|
28
|
+
raise EncodingError "Cannot encode object of type #{item.class.name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Encodes a single primitive.
|
32
|
+
def encode_primitive(item)
|
33
|
+
return Util.str_to_bytes item if item.size == 1 && item.ord < PRIMITIVE_PREFIX_OFFSET
|
34
|
+
payload = Util.str_to_bytes item
|
35
|
+
prefix = length_prefix payload.size, PRIMITIVE_PREFIX_OFFSET
|
36
|
+
"#{prefix}#{payload}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Encodes a single list.
|
40
|
+
def encode_list(list)
|
41
|
+
payload = list.map { |item| encode_raw item }.join
|
42
|
+
prefix = length_prefix payload.size, LIST_PREFIX_OFFSET
|
43
|
+
"#{prefix}#{payload}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Determines a length prefix.
|
47
|
+
def length_prefix(length, offset)
|
48
|
+
if length < SHORT_LENGTH_LIMIT
|
49
|
+
(offset + length).chr
|
50
|
+
elsif length < LONG_LENGTH_LIMIT
|
51
|
+
length_string = Util.int_to_big_endian( length )
|
52
|
+
length_len = (offset + SHORT_LENGTH_LIMIT - 1 + length_string.size).chr
|
53
|
+
"#{length_len}#{length_string}"
|
54
|
+
else
|
55
|
+
raise EncodingError, "Length greater than 256**8: #{length}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end # module Rlp
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rlp
|
4
|
+
module Sedes
|
5
|
+
|
6
|
+
# A serializable, big-endian, unsigned integer type.
|
7
|
+
class BigEndianInt
|
8
|
+
|
9
|
+
# Create a serializable, big-endian, unsigned integer.
|
10
|
+
#
|
11
|
+
# @param size [Integer] the size of the big endian.
|
12
|
+
def initialize(size = nil)
|
13
|
+
@size = size
|
14
|
+
end
|
15
|
+
|
16
|
+
# Serialize a big-endian integer.
|
17
|
+
#
|
18
|
+
# @param obj [Integer] the integer to be serialized.
|
19
|
+
# @return [String] a serialized big-endian integer.
|
20
|
+
# @raise [SerializationError] if provided object is not an integer.
|
21
|
+
# @raise [SerializationError] if provided integer is negative.
|
22
|
+
# @raise [SerializationError] if provided integer is too big for @size.
|
23
|
+
def serialize(obj)
|
24
|
+
raise SerializationError, "Can only serialize integers" unless obj.is_a?(Integer)
|
25
|
+
raise SerializationError, "Cannot serialize negative integers" if obj < 0
|
26
|
+
raise SerializationError, "Integer too large (does not fit in #{@size} bytes)" if @size && obj >= 256 ** @size
|
27
|
+
s = obj == 0 ? BYTE_EMPTY : _int_to_big_endian(obj)
|
28
|
+
@size ? "#{BYTE_ZERO * [0, @size - s.size].max}#{s}" : s
|
29
|
+
end
|
30
|
+
|
31
|
+
# Deserializes an unsigned integer.
|
32
|
+
#
|
33
|
+
# @param serial [String] the serialized integer.
|
34
|
+
# @return [Integer] a number.
|
35
|
+
# @raise [DeserializationError] if provided serial is of wrong size.
|
36
|
+
# @raise [DeserializationError] if provided serial is not of minimal length.
|
37
|
+
def deserialize(serial)
|
38
|
+
raise DeserializationError, "Invalid serialization (wrong size)" if @size && serial.size != @size
|
39
|
+
raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
|
40
|
+
serial = serial || BYTE_ZERO
|
41
|
+
_big_endian_to_int(serial)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
###
|
46
|
+
# private helpers
|
47
|
+
|
48
|
+
|
49
|
+
# Converts an integer to big endian.
|
50
|
+
#
|
51
|
+
# @param num [Integer] integer to be converted.
|
52
|
+
# @return [String] packed, big-endian integer string.
|
53
|
+
def _int_to_big_endian( num )
|
54
|
+
hex = num.to_s(16)
|
55
|
+
hex = "0#{hex}" if hex.size.odd?
|
56
|
+
|
57
|
+
[hex].pack("H*") ## note Util.hex_to_bin() "inline" shortcut
|
58
|
+
end
|
59
|
+
|
60
|
+
# Converts a big endian to an interger.
|
61
|
+
#
|
62
|
+
# @param str [String] big endian to be converted.
|
63
|
+
# @return [Integer] an unpacked integer number.
|
64
|
+
def _big_endian_to_int(str)
|
65
|
+
str.unpack("H*").first.to_i(16)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
end # class BigEndianInt
|
71
|
+
|
72
|
+
end # module Sedes
|
73
|
+
end # module Rlp
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Rlp
|
2
|
+
module Sedes
|
3
|
+
|
4
|
+
# A sedes type for binary values.
|
5
|
+
class Binary
|
6
|
+
|
7
|
+
# Create a serializable binary of fixed size.
|
8
|
+
#
|
9
|
+
# @param l [Integer] the fixed size of the binary.
|
10
|
+
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
11
|
+
# @return [Eth::Rlp::Sedes::Binary] a serializable binary of fixed size.
|
12
|
+
def self.fixed_length(l, allow_empty: false)
|
13
|
+
new(min_length: l, max_length: l, allow_empty: allow_empty)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks wether the given object is of a valid binary type.
|
17
|
+
#
|
18
|
+
# @param obj [Object] the supposed binary item to check.
|
19
|
+
# @return [Boolean] true if valid.
|
20
|
+
def self.valid_type?(obj)
|
21
|
+
obj.instance_of? String
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Create a serializable binary of variable size.
|
26
|
+
#
|
27
|
+
# @param min_length [Integer] the minimum size of the binary.
|
28
|
+
# @param max_length [Integer] the maximum size of the binary.
|
29
|
+
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
30
|
+
def initialize(min_length: 0, max_length: INFINITY, allow_empty: false)
|
31
|
+
@min_length = min_length
|
32
|
+
@max_length = max_length
|
33
|
+
@allow_empty = allow_empty
|
34
|
+
end
|
35
|
+
|
36
|
+
# Serializes a binary.
|
37
|
+
#
|
38
|
+
# @param obj [String] the binary to serialize.
|
39
|
+
# @return [Object] a serialized binary.
|
40
|
+
# @raise [SerializationError] if provided object is of invalid type.
|
41
|
+
# @raise [SerializationError] if provided binary is of invalid length.
|
42
|
+
def serialize(obj)
|
43
|
+
raise SerializationError, "Object is not a serializable (#{obj.class})" unless self.class.valid_type? obj
|
44
|
+
serial = _str_to_bytes( obj )
|
45
|
+
raise SerializationError, "Object has invalid length" unless valid_length? serial.size
|
46
|
+
serial
|
47
|
+
end
|
48
|
+
|
49
|
+
# Deserializes a binary.
|
50
|
+
#
|
51
|
+
# @param serial [Object] the serialized binary.
|
52
|
+
# @return [String] a deserialized binary.
|
53
|
+
# @raise [DeserializationError] if provided serial is of wrong type.
|
54
|
+
# @raise [DeserializationError] if provided serial is of wrong length.
|
55
|
+
def deserialize(serial)
|
56
|
+
raise DeserializationError, "Objects of type #{serial.class} cannot be deserialized" unless _is_primitive? serial
|
57
|
+
raise DeserializationError, "#{serial.class} has invalid length" unless valid_length? serial.size
|
58
|
+
serial
|
59
|
+
end
|
60
|
+
|
61
|
+
# Checks wether the given length fits the defined size boundaries of the
|
62
|
+
# binary type.
|
63
|
+
#
|
64
|
+
# @param length [Integer] the supposed length of the binary item.
|
65
|
+
# @return [Boolean] true if valid.
|
66
|
+
def valid_length?(length)
|
67
|
+
(@min_length <= length && length <= @max_length) ||
|
68
|
+
(@allow_empty && length == 0)
|
69
|
+
end
|
70
|
+
|
71
|
+
#######
|
72
|
+
# private helpers
|
73
|
+
|
74
|
+
# Converts a binary string to bytes.
|
75
|
+
#
|
76
|
+
# @param str [String] binary string to be converted.
|
77
|
+
# @return [Object] the string bytes.
|
78
|
+
def _str_to_bytes(str)
|
79
|
+
_is_bytes?(str) ? str : str.b
|
80
|
+
end
|
81
|
+
|
82
|
+
# Checks if a string is a byte-string.
|
83
|
+
#
|
84
|
+
# @param str [String] a string to check.
|
85
|
+
# @return [Boolean] true if it's an ASCII-8bit encoded byte-string.
|
86
|
+
def _is_bytes?(str)
|
87
|
+
str && str.instance_of?(String) && str.encoding.name == 'ASCII-8BIT'
|
88
|
+
end
|
89
|
+
|
90
|
+
# Checks if the given item is a string primitive.
|
91
|
+
#
|
92
|
+
# @param item [Object] the item to check.
|
93
|
+
# @return [Boolean] true if it's a string primitive.
|
94
|
+
def _is_primitive?(item)
|
95
|
+
item.instance_of?(String)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
end # class Binary
|
100
|
+
|
101
|
+
|
102
|
+
end # module Sedes
|
103
|
+
end # module Rlp
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
module Rlp
|
3
|
+
module Sedes
|
4
|
+
|
5
|
+
# A sedes type for lists of fixed length.
|
6
|
+
class List < Array
|
7
|
+
|
8
|
+
# Create a serializable list of fixed size.
|
9
|
+
#
|
10
|
+
# @param elements [Array] an array indicating the structure of the list.
|
11
|
+
# @param strict [Boolean] an option to enforce the given structure.
|
12
|
+
def initialize(elements: [], strict: true)
|
13
|
+
super()
|
14
|
+
|
15
|
+
@strict = strict
|
16
|
+
elements.each do |e|
|
17
|
+
if Sedes.is_sedes?(e)
|
18
|
+
push e
|
19
|
+
elsif Sedes.is_list?(e)
|
20
|
+
push List.new(elements: e)
|
21
|
+
else
|
22
|
+
raise TypeError, "Instances of List must only contain sedes objects or nested sequences thereof."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Serialize an array.
|
28
|
+
#
|
29
|
+
# @param obj [Array] the array to be serialized.
|
30
|
+
# @return [Array] a serialized list.
|
31
|
+
# @raise [SerializationError] if provided array is not a sequence.
|
32
|
+
# @raise [SerializationError] if provided array is of wrong length.
|
33
|
+
def serialize(obj)
|
34
|
+
raise SerializationError, "Can only serialize sequences" unless Sedes.is_list?(obj)
|
35
|
+
raise SerializationError, "List has wrong length" if (@strict && self.size != obj.size) || self.size < obj.size
|
36
|
+
result = []
|
37
|
+
obj.zip(self).each_with_index do |(element, sedes), i|
|
38
|
+
result.push sedes.serialize(element)
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
# Deserializes a list.
|
44
|
+
#
|
45
|
+
# @param serial [Array] the serialized list.
|
46
|
+
# @return [Array] a deserialized list.
|
47
|
+
# @raise [DeserializationError] if provided serial is not a sequence.
|
48
|
+
# @raise [DeserializationError] if provided serial is of wrong length.
|
49
|
+
def deserialize(serial)
|
50
|
+
raise DeserializationError, "Can only deserialize sequences" unless Sedes.is_list?(serial)
|
51
|
+
raise DeserializationError, "List has wrong length" if @strict && serial.size != self.size
|
52
|
+
result = []
|
53
|
+
len = [serial.size, self.size].min
|
54
|
+
len.times do |i|
|
55
|
+
sedes = self[i]
|
56
|
+
element = serial[i]
|
57
|
+
result.push sedes.deserialize(element)
|
58
|
+
end
|
59
|
+
result.freeze
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
end # class List
|
65
|
+
|
66
|
+
|
67
|
+
end # module Sedes
|
68
|
+
end # module Rlp
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rlp
|
4
|
+
module Sedes
|
5
|
+
|
6
|
+
# Provides a singleton {Rlp::Sedes} class to infer objects and types.
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Tries to find a sedes objects suitable for a given Ruby object.
|
10
|
+
#
|
11
|
+
# The sedes objects considered are `obj`'s class, {big_endian_int} and
|
12
|
+
# {binary}. If `obj` is a list, an {Rlp::Sedes::List} will be
|
13
|
+
# constructed recursively.
|
14
|
+
#
|
15
|
+
# @param obj [Object] the Ruby object for which to find a sedes object.
|
16
|
+
# @raise [TypeError] if no appropriate sedes could be found.
|
17
|
+
def infer(obj)
|
18
|
+
return obj.class if is_sedes?( obj.class )
|
19
|
+
return big_endian_int if obj.is_a?(Integer) && obj >= 0
|
20
|
+
return binary if Binary.valid_type? obj
|
21
|
+
return List.new(elements: obj.map { |item| infer( item ) }) if is_list?( obj )
|
22
|
+
raise TypeError, "Did not find sedes handling type #{obj.class.name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Determines if an object is a sedes object.
|
27
|
+
#
|
28
|
+
# @param obj [Object] the object to check.
|
29
|
+
# @return [Boolean] true if it's serializable and deserializable.
|
30
|
+
def is_sedes?(obj)
|
31
|
+
obj.respond_to?(:serialize) && obj.respond_to?(:deserialize)
|
32
|
+
end
|
33
|
+
|
34
|
+
# A utility to use a big-endian, unsigned integer sedes type with
|
35
|
+
# unspecified length.
|
36
|
+
#
|
37
|
+
# @return [Rlp::Sedes::BigEndianInt] a big-endian, unsigned integer sedes.
|
38
|
+
def big_endian_int
|
39
|
+
@big_endian_int ||= BigEndianInt.new
|
40
|
+
end
|
41
|
+
|
42
|
+
# A utility to use a binary sedes type.
|
43
|
+
#
|
44
|
+
# @return [Rlp::Sedes::Binary] a binary sedes.
|
45
|
+
def binary
|
46
|
+
@binary ||= Binary.new
|
47
|
+
end
|
48
|
+
|
49
|
+
##############################
|
50
|
+
### more helpers
|
51
|
+
|
52
|
+
# Checks if the given item is a string primitive.
|
53
|
+
#
|
54
|
+
# @param item [Object] the item to check.
|
55
|
+
# @return [Boolean] true if it's a string primitive.
|
56
|
+
def is_primitive?(item)
|
57
|
+
item.instance_of?(String)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Checks if the given item is a list.
|
61
|
+
#
|
62
|
+
# @param item [Object] the item to check.
|
63
|
+
# @return [Boolean] true if it's a list.
|
64
|
+
def is_list?(item)
|
65
|
+
!is_primitive?(item) && item.respond_to?(:each)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end # module Rlp
|
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
module Rlp
|
3
|
+
module Util
|
4
|
+
extend self
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
# Checks if a string is hex-adecimal.
|
10
|
+
#
|
11
|
+
# @param str [String] a string to be checked.
|
12
|
+
# @return [String] a match if true; `nil` if not.
|
13
|
+
def is_hex?(str)
|
14
|
+
return false unless str.is_a? String
|
15
|
+
str = remove_hex_prefix str
|
16
|
+
str.match /\A[0-9a-fA-F]*\z/
|
17
|
+
end
|
18
|
+
|
19
|
+
# Removes the `0x` prefix of a hexa-decimal string.
|
20
|
+
#
|
21
|
+
# @param hex [String] a prefixed hex-string.
|
22
|
+
# @return [String] an unprefixed hex-string.
|
23
|
+
def remove_hex_prefix(hex)
|
24
|
+
return hex[2..-1] if is_prefixed? hex
|
25
|
+
return hex
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks if a string is prefixed with `0x`.
|
29
|
+
#
|
30
|
+
# @param hex [String] a string to be checked.
|
31
|
+
# @return [String] a match if true; `nil` if not.
|
32
|
+
def is_prefixed?(hex)
|
33
|
+
hex.match /\A0x/
|
34
|
+
end
|
35
|
+
|
36
|
+
# Packs a hexa-decimal string into a binary string. Also works with
|
37
|
+
# `0x`-prefixed strings.
|
38
|
+
#
|
39
|
+
# @param hex [String] a hexa-decimal string to be packed.
|
40
|
+
# @return [String] a packed binary string.
|
41
|
+
# @raise [TypeError] if value is not a string or string is not hex.
|
42
|
+
def hex_to_bin(hex)
|
43
|
+
raise TypeError, "Value must be an instance of String" unless hex.instance_of? String
|
44
|
+
hex = remove_hex_prefix hex
|
45
|
+
raise TypeError, "Non-hexadecimal digit found" unless is_hex? hex
|
46
|
+
[hex].pack("H*")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Unpacks a binary string to a hexa-decimal string.
|
50
|
+
#
|
51
|
+
# @param bin [String] a binary string to be unpacked.
|
52
|
+
# @return [String] a hexa-decimal string.
|
53
|
+
# @raise [TypeError] if value is not a string.
|
54
|
+
def bin_to_hex(bin)
|
55
|
+
raise TypeError, "Value must be an instance of String" unless bin.instance_of? String
|
56
|
+
bin.unpack("H*").first
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Converts a binary string to bytes.
|
61
|
+
#
|
62
|
+
# @param str [String] binary string to be converted.
|
63
|
+
# @return [Object] the string bytes.
|
64
|
+
def str_to_bytes(str)
|
65
|
+
is_bytes?(str) ? str : str.b
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks if a string is a byte-string.
|
69
|
+
#
|
70
|
+
# @param str [String] a string to check.
|
71
|
+
# @return [Boolean] true if it's an ASCII-8bit encoded byte-string.
|
72
|
+
def is_bytes?(str)
|
73
|
+
str && str.instance_of?(String) && str.encoding.name == 'ASCII-8BIT'
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
# Converts an integer to big endian.
|
79
|
+
#
|
80
|
+
# @param num [Integer] integer to be converted.
|
81
|
+
# @return [String] packed, big-endian integer string.
|
82
|
+
def int_to_big_endian(num)
|
83
|
+
hex = num.to_s(16) unless is_hex? num
|
84
|
+
hex = "0#{hex}" if hex.size.odd?
|
85
|
+
hex_to_bin hex
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
# Converts a big endian to an interger.
|
91
|
+
#
|
92
|
+
# @param str [String] big endian to be converted.
|
93
|
+
# @return [Integer] an unpacked integer number.
|
94
|
+
def big_endian_to_int(str)
|
95
|
+
str.unpack("H*").first.to_i(16)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Deserializes big endian data string to integer.
|
99
|
+
#
|
100
|
+
# @param str [String] serialized big endian integer string.
|
101
|
+
# @return [Integer] an deserialized unsigned integer.
|
102
|
+
def deserialize_big_endian_to_int(str)
|
103
|
+
Sedes.big_endian_int.deserialize str.sub(/\A(\x00)+/, "")
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
end # module Util
|
108
|
+
end # module Rlp
|
data/lib/rlp-lite.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
require_relative "rlp-lite/version"
|
3
|
+
require_relative "rlp-lite/util"
|
4
|
+
|
5
|
+
require_relative "rlp-lite/decoder"
|
6
|
+
require_relative "rlp-lite/encoder"
|
7
|
+
|
8
|
+
require_relative "rlp-lite/sedes/big_endian_int"
|
9
|
+
require_relative "rlp-lite/sedes/binary"
|
10
|
+
require_relative "rlp-lite/sedes/list"
|
11
|
+
require_relative "rlp-lite/sedes"
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
# Provides an recursive-length prefix (RLP) encoder and decoder.
|
16
|
+
|
17
|
+
module Rlp
|
18
|
+
## add constants "inline" here
|
19
|
+
## (no need to use a Constant namespace - why? why not?)
|
20
|
+
|
21
|
+
|
22
|
+
## todo/check - use encoding -ascii-8bit for source file or ? - why? why not?
|
23
|
+
## use #b/.b to ensure binary encoding? - why? why not?
|
24
|
+
BYTE_EMPTY = "".freeze # The empty byte is defined as "".
|
25
|
+
BYTE_ZERO = "\x00".freeze # The zero byte is 0x00.
|
26
|
+
BYTE_ONE = "\x01".freeze # The byte one is 0x01.
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
# The RLP short length limit.
|
31
|
+
SHORT_LENGTH_LIMIT = 56
|
32
|
+
|
33
|
+
# The RLP long length limit.
|
34
|
+
LONG_LENGTH_LIMIT = (256 ** 8)
|
35
|
+
|
36
|
+
# The RLP primitive type offset.
|
37
|
+
PRIMITIVE_PREFIX_OFFSET = 0x80
|
38
|
+
|
39
|
+
# The RLP array type offset.
|
40
|
+
LIST_PREFIX_OFFSET = 0xc0
|
41
|
+
|
42
|
+
|
43
|
+
# Infinity as constant for convenience.
|
44
|
+
INFINITY = (1.0 / 0.0)
|
45
|
+
|
46
|
+
|
47
|
+
# The Rlp module exposes a variety of exceptions grouped as {RlpException}.
|
48
|
+
class RlpException < StandardError; end
|
49
|
+
|
50
|
+
# An error-type to point out RLP-encoding errors.
|
51
|
+
class EncodingError < RlpException; end
|
52
|
+
|
53
|
+
# An error-type to point out RLP-decoding errors.
|
54
|
+
class DecodingError < RlpException; end
|
55
|
+
|
56
|
+
# An error-type to point out RLP-type serialization errors.
|
57
|
+
class SerializationError < RlpException; end
|
58
|
+
|
59
|
+
# An error-type to point out RLP-type serialization errors.
|
60
|
+
class DeserializationError < RlpException; end
|
61
|
+
|
62
|
+
# A wrapper to represent already RLP-encoded data.
|
63
|
+
class Data < String; end
|
64
|
+
|
65
|
+
|
66
|
+
# Performes an {Rlp::Encoder} on any ruby object.
|
67
|
+
#
|
68
|
+
# @param obj [Object] any ruby object.
|
69
|
+
# @return [String] a packed, RLP-encoded item.
|
70
|
+
def self.encode(obj) Encoder.perform( obj ); end
|
71
|
+
|
72
|
+
|
73
|
+
# Performes an {Rlp::Decoder} on any RLP-encoded item.
|
74
|
+
#
|
75
|
+
# @param rlp [String] a packed, RLP-encoded item.
|
76
|
+
# @return [Object] a decoded ruby object.
|
77
|
+
def self.decode(rlp) Decoder.perform( rlp ); end
|
78
|
+
end # module Rlp
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rlp-lite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gerald Bauer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-11-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: hoe
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.23'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.23'
|
47
|
+
description: rlp-lite - light-weight machinery to serialize / deserialze via rlp (recursive
|
48
|
+
length prefix)
|
49
|
+
email: wwwmake@googlegroups.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- CHANGELOG.md
|
54
|
+
- Manifest.txt
|
55
|
+
- README.md
|
56
|
+
files:
|
57
|
+
- CHANGELOG.md
|
58
|
+
- Manifest.txt
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- lib/rlp-lite.rb
|
62
|
+
- lib/rlp-lite/decoder.rb
|
63
|
+
- lib/rlp-lite/encoder.rb
|
64
|
+
- lib/rlp-lite/sedes.rb
|
65
|
+
- lib/rlp-lite/sedes/big_endian_int.rb
|
66
|
+
- lib/rlp-lite/sedes/binary.rb
|
67
|
+
- lib/rlp-lite/sedes/list.rb
|
68
|
+
- lib/rlp-lite/util.rb
|
69
|
+
- lib/rlp-lite/version.rb
|
70
|
+
homepage: https://github.com/pixelartexchange/artbase
|
71
|
+
licenses:
|
72
|
+
- Public Domain
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options:
|
76
|
+
- "--main"
|
77
|
+
- README.md
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '2.3'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubygems_version: 3.3.7
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: rlp-lite - light-weight machinery to serialize / deserialze via rlp (recursive
|
95
|
+
length prefix)
|
96
|
+
test_files: []
|