rlp-lite 0.0.1 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +230 -3
- data/Rakefile +8 -0
- data/lib/rlp-lite/decoder.rb +6 -5
- data/lib/rlp-lite/encoder.rb +20 -13
- data/lib/rlp-lite/sedes/big_endian_int.rb +2 -2
- data/lib/rlp-lite/sedes/binary.rb +18 -40
- data/lib/rlp-lite/sedes/list.rb +5 -5
- data/lib/rlp-lite/sedes.rb +18 -33
- data/lib/rlp-lite/util.rb +46 -18
- data/lib/rlp-lite/version.rb +1 -1
- data/lib/rlp-lite.rb +17 -18
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ee2acc28b11b60da5de5a0609e09ab49f4b256b8cd18a78b56cc5fbf3608620
|
4
|
+
data.tar.gz: 8cc0a1436872cf91951b860e6302b5ef71369cb7e429037c1ae81f5697eb7acd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d652299872dfa39154cf5799123763acd24d4292e41519409db8d9e77f72d7e4f5ff1b38e1c090138fd2f046048899fda8bb8987f8cb64e1bdf1408af749e882
|
7
|
+
data.tar.gz: 620a9e400cbf5c915ddbc81ae0c8e11e0d6991c4feade7cd5c2d2f1801aa75745bd4ec1490e14ec27c86d058f2d19642dc421de0d6ea52396b5cc519e531a47c
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Recursive
|
1
|
+
# Recursive-Length Prefix (RLP) Serialization Lite
|
2
2
|
|
3
3
|
|
4
|
-
rlp-lite - light-weight machinery to serialize / deserialze
|
4
|
+
rlp-lite - light-weight ("lite") machinery to serialize / deserialze using the recursive-length prefix (rlp) scheme
|
5
5
|
|
6
6
|
|
7
7
|
|
@@ -15,8 +15,235 @@ rlp-lite - light-weight machinery to serialize / deserialze via rlp
|
|
15
15
|
|
16
16
|
## Usage
|
17
17
|
|
18
|
-
to be done
|
19
18
|
|
19
|
+
``` ruby
|
20
|
+
require 'rlp-lite'
|
21
|
+
|
22
|
+
######
|
23
|
+
## encode
|
24
|
+
|
25
|
+
list = ['ruby', 'rlp', 255]
|
26
|
+
|
27
|
+
encoded = Rlp.encode( list )
|
28
|
+
#=> "\xCB\x84ruby\x83rlp\x81\xFF".b
|
29
|
+
|
30
|
+
#######
|
31
|
+
## decode
|
32
|
+
|
33
|
+
decoded = Rlp.decode( "\xCB\x84ruby\x83rlp\x81\xFF".b )
|
34
|
+
#=> ["ruby", "rlp", "\xFF".b]
|
35
|
+
```
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
Note: All integers get returned (decoded) as big-endian integers in binary buffers (that is, string with binary "ASCII-8BIT" encoding)
|
40
|
+
e.g. `"\xFF".b` and not `255`.
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
More examples from the official ethereum rlp tests,
|
45
|
+
see [test/data/rlptest.json](test/data/rlptest.json).
|
46
|
+
|
47
|
+
|
48
|
+
``` ruby
|
49
|
+
######
|
50
|
+
# lists of lists
|
51
|
+
obj = [ [ [], [] ], [] ]
|
52
|
+
encoded = Rlp.encode( obj )
|
53
|
+
#=> "\xC4\xC2\xC0\xC0\xC0".b
|
54
|
+
|
55
|
+
decoded = Rlp.decode( "\xC4\xC2\xC0\xC0\xC0".b )
|
56
|
+
#=> [[[], []], []]
|
57
|
+
|
58
|
+
# or using a hex string (not a binary buffer)
|
59
|
+
decoded = Rlp.decode( "0xc4c2c0c0c0" )
|
60
|
+
#=> [[[], []], []]
|
61
|
+
|
62
|
+
|
63
|
+
#####
|
64
|
+
# dict(onary)
|
65
|
+
obj = [["key1", "val1"],
|
66
|
+
["key2", "val2"],
|
67
|
+
["key3", "val3"],
|
68
|
+
["key4", "val4"]]
|
69
|
+
encoded = Rlp.encode( obj )
|
70
|
+
#=> "\xEC\xCA\x84key1\x84val1\xCA\x84key2\x84val2\xCA\x84key3\x84val3\xCA\x84key4\x84val4".b
|
71
|
+
|
72
|
+
decoded = Rlp.decode( "\xEC\xCA\x84key1\x84val1\xCA\x84key2\x84val2\xCA\x84key3\x84val3\xCA\x84key4\x84val4".b )
|
73
|
+
#=> [["key1", "val1"], ["key2", "val2"], ["key3", "val3"], ["key4", "val4"]]
|
74
|
+
|
75
|
+
# or using a hex string (not a binary buffer)
|
76
|
+
decoded = Rlp.decode( "0xecca846b6579318476616c31ca846b6579328476616c32ca846b6579338476616c33ca846b6579348476616c34" )
|
77
|
+
#=> [["key1", "val1"], ["key2", "val2"], ["key3", "val3"], ["key4", "val4"]]
|
78
|
+
```
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
## More About the Recursive-Length Prefix (RLP) Serialization
|
83
|
+
|
84
|
+
via [Recursive-Length Prefix (RLP) Serialization](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/)
|
85
|
+
|
86
|
+
|
87
|
+
The RLP encoding function takes in an item. An item is defined as follows:
|
88
|
+
|
89
|
+
- a string (i.e. byte array) is an item
|
90
|
+
- a list of items is an item
|
91
|
+
|
92
|
+
For example, all of the following are items:
|
93
|
+
|
94
|
+
- an empty string;
|
95
|
+
- the string containing the word "cat";
|
96
|
+
- a list containing any number of strings;
|
97
|
+
- and a more complex data structures like `["cat", ["puppy", "cow"], "horse", [[]], "pig", [""], "sheep"]`.
|
98
|
+
|
99
|
+
Note that in the context of the rest of this page, 'string' means "a certain number of bytes of binary data"; no special encodings are used, and no knowledge about the content of the strings is implied.
|
100
|
+
|
101
|
+
RLP encoding is defined as follows:
|
102
|
+
|
103
|
+
- For a single byte whose value is in the `[0x00, 0x7f]` (decimal `[0, 127]`) range, that byte is its own RLP encoding.
|
104
|
+
- Otherwise, if a string is 0-55 bytes long, the RLP encoding consists of a single byte with value **0x80** (dec. 128) plus the length of the string followed by the string. The range of the first byte is thus `[0x80, 0xb7]` (dec. `[128, 183]`).
|
105
|
+
- If a string is more than 55 bytes long, the RLP encoding consists of a single byte with value **0xb7** (dec. 183) plus the length in bytes of the length of the string in binary form, followed by the length of the string, followed by the string. For example, a 1024 byte long string would be encoded as `\xb9\x04\x00` (dec. `185, 4, 0`) followed by the string. Here, `0xb9` (183 + 2 = 185) as the first byte, followed by the 2 bytes `0x0400` (dec. 1024) that denote the length of the actual string. The range of the first byte is thus `[0xb8, 0xbf]` (dec. `[184, 191]`).
|
106
|
+
- If the total payload of a list (i.e. the combined length of all its items being RLP encoded) is 0-55 bytes long, the RLP encoding consists of a single byte with value **0xc0** plus the length of the list followed by the concatenation of the RLP encodings of the items. The range of the first byte is thus `[0xc0, 0xf7]` (dec. `[192, 247]`).
|
107
|
+
- If the total payload of a list is more than 55 bytes long, the RLP encoding consists of a single byte with value **0xf7** plus the length in bytes of the length of the payload in binary form, followed by the length of the payload, followed by the concatenation of the RLP encodings of the items. The range of the first byte is thus `[0xf8, 0xff]` (dec. `[248, 255]`).
|
108
|
+
|
109
|
+
In code, this is:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
PRIMITIVE_PREFIX_OFFSET = 0x80 # The RLP primitive type offset (dec. 128).
|
113
|
+
LIST_PREFIX_OFFSET = 0xc0 # The RLP array type offset (dec. 192).
|
114
|
+
|
115
|
+
def rlp_encode( input )
|
116
|
+
if input.instance_of?( String )
|
117
|
+
if input.length == 1 && input.ord < PRIMITIVE_PREFIX_OFFSET
|
118
|
+
input
|
119
|
+
else
|
120
|
+
encode_length( input.length, PRIMITIVE_PREFIX_OFFSET ) + input
|
121
|
+
end
|
122
|
+
elsif input.instance_of?( Array )
|
123
|
+
output = ''
|
124
|
+
input.each do |item|
|
125
|
+
output += rlp_encode( item )
|
126
|
+
end
|
127
|
+
encode_length( output.length, LIST_PREFIX_OFFSET ) + output
|
128
|
+
else
|
129
|
+
raise ArgumentError, "type error"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def encode_length( l, offset )
|
134
|
+
if l < 56
|
135
|
+
(l + offset).chr
|
136
|
+
elsif l < 256**8 ## 256**8 = 18446744073709551616
|
137
|
+
bl = to_binary( l )
|
138
|
+
(bl.length + offset + 55).chr + bl
|
139
|
+
else
|
140
|
+
raise ArgumentError, "input too long"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_binary(x)
|
145
|
+
x == 0 ? '' : to_binary( x / 256 ) + (x % 256).chr
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
|
150
|
+
**Examples**
|
151
|
+
|
152
|
+
- the string "dog" = [ 0x83, 'd', 'o', 'g' ]
|
153
|
+
- the list [ "cat", "dog" ] = `[ 0xc8, 0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g' ]`
|
154
|
+
- the empty string ('null') = `[ 0x80 ]`
|
155
|
+
- the empty list = `[ 0xc0 ]`
|
156
|
+
- the integer 0 = `[ 0x80 ]`
|
157
|
+
- the encoded integer 0 ('\\x00') = `[ 0x00 ]`
|
158
|
+
- the encoded integer 15 ('\\x0f') = `[ 0x0f ]`
|
159
|
+
- the encoded integer 1024 ('\\x04\\x00') = `[ 0x82, 0x04, 0x00 ]`
|
160
|
+
- the [set theoretical representation](http://en.wikipedia.org/wiki/Set-theoretic_definition_of_natural_numbers) of three, `[ [], [[]], [ [], [[]] ] ] = [ 0xc7, 0xc0, 0xc1, 0xc0, 0xc3, 0xc0, 0xc1, 0xc0 ]`
|
161
|
+
- the string "Lorem ipsum dolor sit amet, consectetur adipisicing elit" = `[ 0xb8, 0x38, 'L', 'o', 'r', 'e', 'm', ' ', ... , 'e', 'l', 'i', 't' ]`
|
162
|
+
|
163
|
+
|
164
|
+
**RLP decoding**
|
165
|
+
|
166
|
+
According to the rules and process of RLP encoding, the input of RLP decode is regarded as an array of binary data. The RLP decoding process is as follows:
|
167
|
+
|
168
|
+
1. according to the first byte (i.e. prefix) of input data and decoding the data type, the length of the actual data and offset;
|
169
|
+
|
170
|
+
2. according to the type and offset of data, decode the data correspondingly;
|
171
|
+
|
172
|
+
3. continue to decode the rest of the input;
|
173
|
+
|
174
|
+
Among them, the rules of decoding data types and offset is as follows:
|
175
|
+
|
176
|
+
1. the data is a string if the range of the first byte (i.e. prefix) is [0x00, 0x7f], and the string is the first byte itself exactly;
|
177
|
+
|
178
|
+
2. the data is a string if the range of the first byte is [0x80, 0xb7], and the string whose length is equal to the first byte minus 0x80 follows the first byte;
|
179
|
+
|
180
|
+
3. the data is a string if the range of the first byte is [0xb8, 0xbf], and the length of the string whose length in bytes is equal to the first byte minus 0xb7 follows the first byte, and the string follows the length of the string;
|
181
|
+
|
182
|
+
4. the data is a list if the range of the first byte is [0xc0, 0xf7], and the concatenation of the RLP encodings of all items of the list which the total payload is equal to the first byte minus 0xc0 follows the first byte;
|
183
|
+
|
184
|
+
5. the data is a list if the range of the first byte is [0xf8, 0xff], and the total payload of the list whose length is equal to the first byte minus 0xf7 follows the first byte, and the concatenation of the RLP encodings of all items of the list follows the total payload of the list;
|
185
|
+
|
186
|
+
In code, this is:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
def rlp_decode( input, output=[] )
|
190
|
+
return output[0] if input.length == 0
|
191
|
+
|
192
|
+
offset, dataLen, type = decode_length( input )
|
193
|
+
|
194
|
+
if type == String
|
195
|
+
output << input[ offset, dataLen ]
|
196
|
+
elsif type == Array
|
197
|
+
list = []
|
198
|
+
rlp_decode( input[ offset, dataLen], list )
|
199
|
+
output << list
|
200
|
+
else
|
201
|
+
raise ArgumentError, "type error"
|
202
|
+
end
|
203
|
+
|
204
|
+
rlp_decode( input[ (offset + dataLen)..-1], output )
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
def decode_length( input )
|
209
|
+
length = input.length
|
210
|
+
|
211
|
+
raise ArgumentError, "input is null" if length == 0
|
212
|
+
|
213
|
+
prefix = input[0].ord
|
214
|
+
if prefix <= 0x7f
|
215
|
+
[0, 1, String]
|
216
|
+
elsif prefix <= 0xb7 && length > prefix - 0x80
|
217
|
+
strLen = prefix - 0x80
|
218
|
+
[1, strLen, String]
|
219
|
+
elsif prefix <= 0xbf && length > prefix - 0xb7 && length > prefix - 0xb7 + to_integer( input[1, prefix - 0xb7] )
|
220
|
+
lenOfStrLen = prefix - 0xb7
|
221
|
+
strLen = to_integer( input[1, lenOfStrLen] )
|
222
|
+
[1 + lenOfStrLen, strLen, String]
|
223
|
+
elsif prefix <= 0xf7 && length > prefix - 0xc0
|
224
|
+
listLen = prefix - 0xc0
|
225
|
+
[1, listLen, Array]
|
226
|
+
elsif prefix <= 0xff && length > prefix - 0xf7 && length > prefix - 0xf7 + to_integer( input[1, prefix - 0xf7])
|
227
|
+
lenOfListLen = prefix - 0xf7
|
228
|
+
listLen = to_integer( input[1, lenOfListLen] )
|
229
|
+
[1 + lenOfListLen, listLen, Array]
|
230
|
+
else
|
231
|
+
raise ArgumentError, "input don't conform RLP encoding form"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
def to_integer( b )
|
237
|
+
length = b.length
|
238
|
+
if length == 0
|
239
|
+
raise ArgumentError, "input is null"
|
240
|
+
elsif length == 1
|
241
|
+
b[0].ord
|
242
|
+
else
|
243
|
+
b[-1].ord + to_integer( b[0, length-1] ) * 256
|
244
|
+
end
|
245
|
+
end
|
246
|
+
```
|
20
247
|
|
21
248
|
|
22
249
|
|
data/Rakefile
CHANGED
data/lib/rlp-lite/decoder.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
module Rlp
|
2
2
|
|
3
|
+
|
3
4
|
# Provides an RLP-decoder.
|
4
|
-
|
5
|
-
extend self
|
5
|
+
class Decoder
|
6
6
|
|
7
7
|
# Decodes an RLP-encoded object.
|
8
8
|
#
|
9
9
|
# @param rlp [String] an RLP-encoded object.
|
10
10
|
# @return [Object] the decoded and maybe deserialized object.
|
11
|
-
# @raise [
|
11
|
+
# @raise [Rlp::DecodingError] if the input string does not end after
|
12
12
|
# the root item.
|
13
|
-
def perform(rlp)
|
14
|
-
rlp = Util.hex_to_bin( rlp )
|
13
|
+
def perform( rlp )
|
14
|
+
rlp = Util.hex_to_bin( rlp ) if Util.is_hex?( rlp )
|
15
15
|
rlp = Util.str_to_bytes( rlp )
|
16
16
|
begin
|
17
17
|
item, next_start = consume_item( rlp, 0 )
|
@@ -23,6 +23,7 @@
|
|
23
23
|
end
|
24
24
|
|
25
25
|
|
26
|
+
|
26
27
|
private
|
27
28
|
|
28
29
|
# Consume an RLP-encoded item from the given start.
|
data/lib/rlp-lite/encoder.rb
CHANGED
@@ -1,33 +1,40 @@
|
|
1
1
|
|
2
|
-
# Provides an recursive-length prefix (RLP) encoder and decoder.
|
3
2
|
module Rlp
|
4
3
|
|
4
|
+
|
5
5
|
# Provides an RLP-encoder.
|
6
|
-
|
7
|
-
|
6
|
+
class Encoder
|
7
|
+
|
8
8
|
|
9
9
|
# Encodes a Ruby object in RLP format.
|
10
10
|
#
|
11
11
|
# @param obj [Object] a Ruby object.
|
12
12
|
# @return [String] the RLP encoded item.
|
13
|
-
# @raise [
|
13
|
+
# @raise [Rlp::EncodingError] in the rather unlikely case that the item
|
14
14
|
# is too big to encode (will not happen).
|
15
|
-
# @raise [
|
16
|
-
def perform(obj)
|
15
|
+
# @raise [Rlp::SerializationError] if the serialization fails.
|
16
|
+
def perform( obj )
|
17
17
|
item = Sedes.infer(obj).serialize(obj)
|
18
18
|
result = encode_raw( item )
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
|
22
|
+
private
|
22
23
|
|
23
24
|
# Encodes the raw item.
|
24
|
-
def encode_raw(item)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
def encode_raw( item )
|
26
|
+
if item.instance_of?( Rlp::Data )
|
27
|
+
item
|
28
|
+
elsif Util.is_primitive?( item )
|
29
|
+
encode_primitive item
|
30
|
+
elsif Util.is_list?( item )
|
31
|
+
encode_list item
|
32
|
+
else
|
33
|
+
raise EncodingError "Cannot encode object of type #{item.class.name}"
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
37
|
+
|
31
38
|
# Encodes a single primitive.
|
32
39
|
def encode_primitive(item)
|
33
40
|
return Util.str_to_bytes item if item.size == 1 && item.ord < PRIMITIVE_PREFIX_OFFSET
|
@@ -56,4 +63,4 @@
|
|
56
63
|
end
|
57
64
|
end
|
58
65
|
end
|
59
|
-
|
66
|
+
end # module Rlp
|
@@ -24,7 +24,7 @@
|
|
24
24
|
raise SerializationError, "Can only serialize integers" unless obj.is_a?(Integer)
|
25
25
|
raise SerializationError, "Cannot serialize negative integers" if obj < 0
|
26
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)
|
27
|
+
s = obj == 0 ? BYTE_EMPTY : _int_to_big_endian( obj )
|
28
28
|
@size ? "#{BYTE_ZERO * [0, @size - s.size].max}#{s}" : s
|
29
29
|
end
|
30
30
|
|
@@ -38,7 +38,7 @@
|
|
38
38
|
raise DeserializationError, "Invalid serialization (wrong size)" if @size && serial.size != @size
|
39
39
|
raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
|
40
40
|
serial = serial || BYTE_ZERO
|
41
|
-
_big_endian_to_int(serial)
|
41
|
+
_big_endian_to_int( serial )
|
42
42
|
end
|
43
43
|
|
44
44
|
|
@@ -9,16 +9,18 @@
|
|
9
9
|
# @param l [Integer] the fixed size of the binary.
|
10
10
|
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
11
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,
|
12
|
+
def self.fixed_length( l, allow_empty: false )
|
13
|
+
new( min_length: l,
|
14
|
+
max_length: l,
|
15
|
+
allow_empty: allow_empty )
|
14
16
|
end
|
15
17
|
|
16
18
|
# Checks wether the given object is of a valid binary type.
|
17
19
|
#
|
18
20
|
# @param obj [Object] the supposed binary item to check.
|
19
21
|
# @return [Boolean] true if valid.
|
20
|
-
def self.valid_type?(obj)
|
21
|
-
obj.instance_of? String
|
22
|
+
def self.valid_type?( obj )
|
23
|
+
obj.instance_of?( String )
|
22
24
|
end
|
23
25
|
|
24
26
|
|
@@ -27,7 +29,7 @@
|
|
27
29
|
# @param min_length [Integer] the minimum size of the binary.
|
28
30
|
# @param max_length [Integer] the maximum size of the binary.
|
29
31
|
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
30
|
-
def initialize(min_length: 0, max_length: INFINITY, allow_empty: false)
|
32
|
+
def initialize( min_length: 0, max_length: INFINITY, allow_empty: false )
|
31
33
|
@min_length = min_length
|
32
34
|
@max_length = max_length
|
33
35
|
@allow_empty = allow_empty
|
@@ -39,13 +41,17 @@
|
|
39
41
|
# @return [Object] a serialized binary.
|
40
42
|
# @raise [SerializationError] if provided object is of invalid type.
|
41
43
|
# @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
|
-
|
45
|
-
|
44
|
+
def serialize( obj )
|
45
|
+
raise SerializationError, "Object is not a serializable (#{obj.class})" unless self.class.valid_type?( obj )
|
46
|
+
|
47
|
+
## make sure string is with binary encoding (ASCII-8BIT)
|
48
|
+
## note: was Util.str_to_bytes( obj )
|
49
|
+
serial = obj.encoding.name == 'ASCII-8BIT' ? obj : obj.b
|
50
|
+
raise SerializationError, "Object has invalid length" unless valid_length?( serial.size )
|
46
51
|
serial
|
47
52
|
end
|
48
53
|
|
54
|
+
|
49
55
|
# Deserializes a binary.
|
50
56
|
#
|
51
57
|
# @param serial [Object] the serialized binary.
|
@@ -53,8 +59,8 @@
|
|
53
59
|
# @raise [DeserializationError] if provided serial is of wrong type.
|
54
60
|
# @raise [DeserializationError] if provided serial is of wrong length.
|
55
61
|
def deserialize(serial)
|
56
|
-
raise DeserializationError, "Objects of type #{serial.class} cannot be deserialized" unless
|
57
|
-
raise DeserializationError, "#{serial.class} has invalid length" unless valid_length? serial.size
|
62
|
+
raise DeserializationError, "Objects of type #{serial.class} cannot be deserialized" unless serial.instance_of?(String)
|
63
|
+
raise DeserializationError, "#{serial.class} has invalid length" unless valid_length?( serial.size )
|
58
64
|
serial
|
59
65
|
end
|
60
66
|
|
@@ -63,39 +69,11 @@
|
|
63
69
|
#
|
64
70
|
# @param length [Integer] the supposed length of the binary item.
|
65
71
|
# @return [Boolean] true if valid.
|
66
|
-
def valid_length?(length)
|
72
|
+
def valid_length?( length )
|
67
73
|
(@min_length <= length && length <= @max_length) ||
|
68
74
|
(@allow_empty && length == 0)
|
69
75
|
end
|
70
76
|
|
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
77
|
end # class Binary
|
100
78
|
|
101
79
|
|
data/lib/rlp-lite/sedes/list.rb
CHANGED
@@ -9,14 +9,14 @@
|
|
9
9
|
#
|
10
10
|
# @param elements [Array] an array indicating the structure of the list.
|
11
11
|
# @param strict [Boolean] an option to enforce the given structure.
|
12
|
-
def initialize(elements: [], strict: true)
|
12
|
+
def initialize( elements: [], strict: true )
|
13
13
|
super()
|
14
14
|
|
15
15
|
@strict = strict
|
16
16
|
elements.each do |e|
|
17
17
|
if Sedes.is_sedes?(e)
|
18
18
|
push e
|
19
|
-
elsif
|
19
|
+
elsif Util.is_list?(e)
|
20
20
|
push List.new(elements: e)
|
21
21
|
else
|
22
22
|
raise TypeError, "Instances of List must only contain sedes objects or nested sequences thereof."
|
@@ -30,8 +30,8 @@
|
|
30
30
|
# @return [Array] a serialized list.
|
31
31
|
# @raise [SerializationError] if provided array is not a sequence.
|
32
32
|
# @raise [SerializationError] if provided array is of wrong length.
|
33
|
-
def serialize(obj)
|
34
|
-
raise SerializationError, "Can only serialize sequences" unless
|
33
|
+
def serialize( obj )
|
34
|
+
raise SerializationError, "Can only serialize sequences" unless Util.is_list?(obj)
|
35
35
|
raise SerializationError, "List has wrong length" if (@strict && self.size != obj.size) || self.size < obj.size
|
36
36
|
result = []
|
37
37
|
obj.zip(self).each_with_index do |(element, sedes), i|
|
@@ -47,7 +47,7 @@
|
|
47
47
|
# @raise [DeserializationError] if provided serial is not a sequence.
|
48
48
|
# @raise [DeserializationError] if provided serial is of wrong length.
|
49
49
|
def deserialize(serial)
|
50
|
-
raise DeserializationError, "Can only deserialize sequences" unless
|
50
|
+
raise DeserializationError, "Can only deserialize sequences" unless Util.is_list?(serial)
|
51
51
|
raise DeserializationError, "List has wrong length" if @strict && serial.size != self.size
|
52
52
|
result = []
|
53
53
|
len = [serial.size, self.size].min
|
data/lib/rlp-lite/sedes.rb
CHANGED
@@ -3,8 +3,7 @@
|
|
3
3
|
module Rlp
|
4
4
|
module Sedes
|
5
5
|
|
6
|
-
# Provides a
|
7
|
-
class << self
|
6
|
+
# Provides a {Rlp::Sedes} module to infer objects and types.
|
8
7
|
|
9
8
|
# Tries to find a sedes objects suitable for a given Ruby object.
|
10
9
|
#
|
@@ -14,12 +13,18 @@
|
|
14
13
|
#
|
15
14
|
# @param obj [Object] the Ruby object for which to find a sedes object.
|
16
15
|
# @raise [TypeError] if no appropriate sedes could be found.
|
17
|
-
def infer(obj)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
def self.infer( obj )
|
17
|
+
if is_sedes?( obj.class )
|
18
|
+
obj.class
|
19
|
+
elsif obj.is_a?(Integer) && obj >= 0
|
20
|
+
big_endian_int
|
21
|
+
elsif Binary.valid_type?( obj ) ## note: same as obj.is_a?( String )
|
22
|
+
binary
|
23
|
+
elsif Util.is_list?( obj )
|
24
|
+
List.new( elements: obj.map { |item| infer( item ) } )
|
25
|
+
else
|
26
|
+
raise TypeError, "Did not find sedes handling type #{obj.class.name}"
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
|
@@ -27,7 +32,7 @@
|
|
27
32
|
#
|
28
33
|
# @param obj [Object] the object to check.
|
29
34
|
# @return [Boolean] true if it's serializable and deserializable.
|
30
|
-
def is_sedes?(obj)
|
35
|
+
def self.is_sedes?(obj)
|
31
36
|
obj.respond_to?(:serialize) && obj.respond_to?(:deserialize)
|
32
37
|
end
|
33
38
|
|
@@ -35,39 +40,19 @@
|
|
35
40
|
# unspecified length.
|
36
41
|
#
|
37
42
|
# @return [Rlp::Sedes::BigEndianInt] a big-endian, unsigned integer sedes.
|
38
|
-
def big_endian_int
|
43
|
+
def self.big_endian_int
|
39
44
|
@big_endian_int ||= BigEndianInt.new
|
40
45
|
end
|
41
46
|
|
42
47
|
# A utility to use a binary sedes type.
|
43
48
|
#
|
44
49
|
# @return [Rlp::Sedes::Binary] a binary sedes.
|
45
|
-
def binary
|
50
|
+
def self.binary
|
46
51
|
@binary ||= Binary.new
|
47
52
|
end
|
48
53
|
|
49
|
-
##############################
|
50
|
-
### more helpers
|
51
54
|
|
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
55
|
|
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
56
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end # module Rlp
|
57
|
+
end # module Sedes
|
58
|
+
end # module Rlp
|
data/lib/rlp-lite/util.rb
CHANGED
@@ -4,15 +4,13 @@ module Util
|
|
4
4
|
extend self
|
5
5
|
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
# Checks if a string is hex-adecimal.
|
7
|
+
# Checks if a string is hex-adecimal (string).
|
10
8
|
#
|
11
9
|
# @param str [String] a string to be checked.
|
12
10
|
# @return [String] a match if true; `nil` if not.
|
13
|
-
def is_hex?(str)
|
14
|
-
return false unless str.is_a? String
|
15
|
-
str =
|
11
|
+
def is_hex?( str )
|
12
|
+
return false unless str.is_a?( String )
|
13
|
+
str = strip_hex_prefix( str )
|
16
14
|
str.match /\A[0-9a-fA-F]*\z/
|
17
15
|
end
|
18
16
|
|
@@ -20,18 +18,25 @@ module Util
|
|
20
18
|
#
|
21
19
|
# @param hex [String] a prefixed hex-string.
|
22
20
|
# @return [String] an unprefixed hex-string.
|
23
|
-
def
|
24
|
-
|
25
|
-
return hex
|
21
|
+
def strip_hex_prefix(hex)
|
22
|
+
is_prefixed?( hex ) ? hex[2..-1] : hex
|
26
23
|
end
|
24
|
+
alias_method :remove_hex_prefix, :strip_hex_prefix
|
25
|
+
alias_method :strip_0x, :strip_hex_prefix ## add more alias - why? why not?
|
27
26
|
|
28
27
|
# Checks if a string is prefixed with `0x`.
|
29
28
|
#
|
30
29
|
# @param hex [String] a string to be checked.
|
31
30
|
# @return [String] a match if true; `nil` if not.
|
32
31
|
def is_prefixed?(hex)
|
33
|
-
hex.match /\A0x/
|
32
|
+
## was: hex.match /\A0x/
|
33
|
+
## tood/check: add support for (upcase) 0X too - why? why not?
|
34
|
+
hex.start_with?( '0x' ) ||
|
35
|
+
hex.start_with?( '0X' )
|
34
36
|
end
|
37
|
+
alias_method :is_hex_prefixed?, :is_prefixed?
|
38
|
+
alias_method :start_with_0x?, :is_prefixed?
|
39
|
+
|
35
40
|
|
36
41
|
# Packs a hexa-decimal string into a binary string. Also works with
|
37
42
|
# `0x`-prefixed strings.
|
@@ -39,9 +44,9 @@ module Util
|
|
39
44
|
# @param hex [String] a hexa-decimal string to be packed.
|
40
45
|
# @return [String] a packed binary string.
|
41
46
|
# @raise [TypeError] if value is not a string or string is not hex.
|
42
|
-
def hex_to_bin(hex)
|
47
|
+
def hex_to_bin( hex )
|
43
48
|
raise TypeError, "Value must be an instance of String" unless hex.instance_of? String
|
44
|
-
hex = remove_hex_prefix hex
|
49
|
+
hex = remove_hex_prefix( hex )
|
45
50
|
raise TypeError, "Non-hexadecimal digit found" unless is_hex? hex
|
46
51
|
[hex].pack("H*")
|
47
52
|
end
|
@@ -64,6 +69,7 @@ module Util
|
|
64
69
|
def str_to_bytes(str)
|
65
70
|
is_bytes?(str) ? str : str.b
|
66
71
|
end
|
72
|
+
## todo/check - rename to str_to_binary - why? why not?
|
67
73
|
|
68
74
|
# Checks if a string is a byte-string.
|
69
75
|
#
|
@@ -72,6 +78,7 @@ module Util
|
|
72
78
|
def is_bytes?(str)
|
73
79
|
str && str.instance_of?(String) && str.encoding.name == 'ASCII-8BIT'
|
74
80
|
end
|
81
|
+
## todo/check - rename to is binary? is_binary?
|
75
82
|
|
76
83
|
|
77
84
|
|
@@ -79,10 +86,10 @@ module Util
|
|
79
86
|
#
|
80
87
|
# @param num [Integer] integer to be converted.
|
81
88
|
# @return [String] packed, big-endian integer string.
|
82
|
-
def int_to_big_endian(num)
|
83
|
-
hex = num.to_s(16)
|
89
|
+
def int_to_big_endian( num )
|
90
|
+
hex = num.to_s(16)
|
84
91
|
hex = "0#{hex}" if hex.size.odd?
|
85
|
-
hex_to_bin hex
|
92
|
+
hex_to_bin( hex )
|
86
93
|
end
|
87
94
|
|
88
95
|
|
@@ -91,18 +98,39 @@ module Util
|
|
91
98
|
#
|
92
99
|
# @param str [String] big endian to be converted.
|
93
100
|
# @return [Integer] an unpacked integer number.
|
94
|
-
def big_endian_to_int(str)
|
95
|
-
str.unpack("H*").
|
101
|
+
def big_endian_to_int( str )
|
102
|
+
str.unpack("H*")[0].to_i(16)
|
96
103
|
end
|
97
104
|
|
105
|
+
|
106
|
+
|
98
107
|
# Deserializes big endian data string to integer.
|
99
108
|
#
|
100
109
|
# @param str [String] serialized big endian integer string.
|
101
110
|
# @return [Integer] an deserialized unsigned integer.
|
102
111
|
def deserialize_big_endian_to_int(str)
|
103
|
-
Sedes.big_endian_int.deserialize str.sub(/\A(\x00)+/,
|
112
|
+
Sedes.big_endian_int.deserialize str.sub( /\A(\x00)+/, '' )
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Checks if the given item is a string primitive.
|
117
|
+
#
|
118
|
+
# @param item [Object] the item to check.
|
119
|
+
# @return [Boolean] true if it's a string primitive.
|
120
|
+
def is_primitive?( item )
|
121
|
+
item.instance_of?(String)
|
104
122
|
end
|
105
123
|
|
124
|
+
# Checks if the given item is a list.
|
125
|
+
#
|
126
|
+
# @param item [Object] the item to check.
|
127
|
+
# @return [Boolean] true if it's a list.
|
128
|
+
def is_list?( item )
|
129
|
+
!is_primitive?(item) && item.respond_to?(:each)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
|
106
134
|
|
107
135
|
end # module Util
|
108
136
|
end # module Rlp
|
data/lib/rlp-lite/version.rb
CHANGED
data/lib/rlp-lite.rb
CHANGED
@@ -21,27 +21,20 @@ module Rlp
|
|
21
21
|
|
22
22
|
## todo/check - use encoding -ascii-8bit for source file or ? - why? why not?
|
23
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
24
|
|
25
|
+
## todo/check: use auto-freeze string literals magic comment - why? why not?
|
26
|
+
BYTE_EMPTY = "".b.freeze # The empty byte is defined as "".
|
27
|
+
BYTE_ZERO = "\x00".b.freeze # The zero byte is 0x00.
|
28
|
+
BYTE_ONE = "\x01".b.freeze # The byte one is 0x01.
|
28
29
|
|
29
30
|
|
30
|
-
# The RLP short length limit.
|
31
|
-
|
31
|
+
SHORT_LENGTH_LIMIT = 56 # The RLP short length limit.
|
32
|
+
LONG_LENGTH_LIMIT = (256 ** 8) # The RLP long length limit.
|
33
|
+
PRIMITIVE_PREFIX_OFFSET = 0x80 # The RLP primitive type offset.
|
34
|
+
LIST_PREFIX_OFFSET = 0xc0 # The RLP array type offset.
|
32
35
|
|
33
|
-
# The RLP long length limit.
|
34
|
-
LONG_LENGTH_LIMIT = (256 ** 8)
|
35
36
|
|
36
|
-
#
|
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)
|
37
|
+
INFINITY = (1.0 / 0.0) # Infinity as constant for convenience.
|
45
38
|
|
46
39
|
|
47
40
|
# The Rlp module exposes a variety of exceptions grouped as {RlpException}.
|
@@ -63,18 +56,24 @@ module Rlp
|
|
63
56
|
class Data < String; end
|
64
57
|
|
65
58
|
|
59
|
+
def self.encoder() @encoder ||= Encoder.new; end
|
60
|
+
def self.decoder() @decoder ||= Decoder.new; end
|
61
|
+
|
62
|
+
|
66
63
|
# Performes an {Rlp::Encoder} on any ruby object.
|
67
64
|
#
|
68
65
|
# @param obj [Object] any ruby object.
|
69
66
|
# @return [String] a packed, RLP-encoded item.
|
70
|
-
def self.encode(obj)
|
67
|
+
def self.encode(obj) encoder.perform( obj ); end
|
71
68
|
|
72
69
|
|
73
70
|
# Performes an {Rlp::Decoder} on any RLP-encoded item.
|
74
71
|
#
|
75
72
|
# @param rlp [String] a packed, RLP-encoded item.
|
76
73
|
# @return [Object] a decoded ruby object.
|
77
|
-
def self.decode(rlp)
|
74
|
+
def self.decode(rlp) decoder.perform( rlp ); end
|
75
|
+
|
76
|
+
|
78
77
|
end # module Rlp
|
79
78
|
|
80
79
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rlp-lite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|