rlp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: