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 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
@@ -0,0 +1,3 @@
1
+ # ruby-rlp
2
+
3
+ A Ruby implementation of Ethereum's Recursive Length Prefix encoding (RLP). You can find the specification of the standard in the [Ethereum wiki](https://github.com/ethereum/wiki/wiki/RLP).
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
@@ -0,0 +1,9 @@
1
+ module RLP
2
+ module Constant
3
+ SHORT_LENGTH_LIMIT = 56
4
+ LONG_LENGTH_LIMIT = 256**8
5
+
6
+ PRIMITIVE_PREFIX_OFFSET = 0x80
7
+ LIST_PREFIX_OFFSET = 0xc0
8
+ end
9
+ end
data/lib/rlp/data.rb ADDED
@@ -0,0 +1,7 @@
1
+ ##
2
+ # A wrapper to represent already RLP encoded data
3
+
4
+ module RLP
5
+ class Data < String
6
+ end
7
+ end
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
@@ -0,0 +1,3 @@
1
+ module RLP
2
+ VERSION = '0.1.0'
3
+ end
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: