rlp 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6b6741d638fbdc0a96769c278107372a0b7a8fd7
4
- data.tar.gz: 583c56e5f86fb18c21022ce5c9b12e42abd24b43
3
+ metadata.gz: 32e4d13b19619a0c3f381595d5942f9e915ee837
4
+ data.tar.gz: 3968b3f4dd481c15a878465214e1db635d42b616
5
5
  SHA512:
6
- metadata.gz: 28451e98078179c7b1290822cfbc83c28326f9b8a221973e2f24bfdbf839bd5d5eb3c6289231621c0c98e7928377d2bc5efdc9384493fe941e4168083c34a578
7
- data.tar.gz: 537920b429404476ef93c663aec0cb91f961e152c2645c4b4d77a2fe8ae9c8b3a7b7da775da3e2f502308450fe670bc5b5cce674d53374341e0747901e3ea3e2
6
+ metadata.gz: e0c09429505321937097e3dc71bbb1c4d3029ddffd9b9dfdbfaf1bdbdf9133b929b44141558ba435d720ae4f318e15615b5f41de41ebbea9963370fe2a9f9111
7
+ data.tar.gz: 9be9e4d4bb3d9f246f32440e1eb97f4a5d12c0de700007f21c6f0e41ab9546f54237e7aa756ee96e435de54f8892f10599a34d3059c4c74b0e9e21c762292781
data/lib/rlp.rb CHANGED
@@ -4,64 +4,12 @@ require 'rlp/error'
4
4
  require 'rlp/utils'
5
5
  require 'rlp/sedes'
6
6
 
7
+ require 'rlp/encode'
8
+ require 'rlp/decode'
9
+
7
10
  module RLP
8
- include Constant
9
- include Error
10
- include Utils
11
+ include Encode
12
+ include Decode
11
13
 
12
14
  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
15
  end
@@ -5,5 +5,8 @@ module RLP
5
5
 
6
6
  PRIMITIVE_PREFIX_OFFSET = 0x80
7
7
  LIST_PREFIX_OFFSET = 0xc0
8
+
9
+ BYTE_ZERO = "\x00".force_encoding('ascii-8bit').freeze
10
+ BYTE_EMPTY = ''.force_encoding('ascii-8bit').freeze
8
11
  end
9
12
  end
@@ -0,0 +1,119 @@
1
+ module RLP
2
+ module Decode
3
+ include Constant
4
+ include Error
5
+ include Utils
6
+
7
+ def decode(rlp, options={})
8
+ sedes = options.delete(:sedes)
9
+ strict = options.delete(:strict) {|k| true }
10
+
11
+ rlp = str_to_bytes(rlp)
12
+
13
+ begin
14
+ item, next_start = consume_item(rlp, 0)
15
+ rescue Exception => e
16
+ raise DecodingError.new("Cannot decode rlp string: #{e}", rlp)
17
+ end
18
+
19
+ raise DecodingError.new("RLP string ends with #{rlp.size - next_start} superfluous bytes", rlp) if next_start != rlp.size && strict
20
+
21
+ if sedes
22
+ obj = sedes.deserialize(item) # TODO: (options)
23
+ #TODO: cache flow
24
+ obj
25
+ else
26
+ item
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ ##
33
+ # Read an item from an RLP string.
34
+ #
35
+ # * `rlp` - the string to read from
36
+ # * `start` - the position at which to start reading`
37
+ #
38
+ # Returns a pair `[item, end]` where `item` is the read item and `end` is
39
+ # the position of the first unprocessed byte.
40
+ #
41
+ def consume_item(rlp, start)
42
+ t, l, s = consume_length_prefix(rlp, start)
43
+ consume_payload(rlp, s, t, l)
44
+ end
45
+
46
+ ##
47
+ # Read a length prefix from an RLP string.
48
+ #
49
+ # * `rlp` - the rlp string to read from
50
+ # * `start` - the position at which to start reading
51
+ #
52
+ # Returns an array `[type, length, end]`, where `type` is either `:str`
53
+ # or `:list` depending on the type of the following payload, `length` is
54
+ # the length of the payload in bytes, and `end` is the position of the
55
+ # first payload byte in the rlp string (thus the end of length prefix).
56
+ #
57
+ def consume_length_prefix(rlp, start)
58
+ b0 = rlp[start].ord
59
+
60
+ if b0 < PRIMITIVE_PREFIX_OFFSET # single byte
61
+ [:str, 1, start]
62
+ elsif b0 < PRIMITIVE_PREFIX_OFFSET + SHORT_LENGTH_LIMIT # short string
63
+ raise DecodingError.new("Encoded as short string although single byte was possible", rlp) if (b0 - PRIMITIVE_PREFIX_OFFSET == 1) && rlp[start+1].ord < PRIMITIVE_PREFIX_OFFSET
64
+
65
+ [:str, b0 - PRIMITIVE_PREFIX_OFFSET, start + 1]
66
+ elsif b0 < LIST_PREFIX_OFFSET # long string
67
+ raise DecodingError.new("Length starts with zero bytes", rlp) if rlp.slice(start+1) == BYTE_ZERO
68
+
69
+ ll = b0 - PRIMITIVE_PREFIX_OFFSET - SHORT_LENGTH_LIMIT + 1
70
+ l = big_endian_to_int rlp[(start+1)...(start+1+ll)]
71
+
72
+ [:str, l, start+1+ll]
73
+ elsif b0 < LIST_PREFIX_OFFSET + SHORT_LENGTH_LIMIT # short list
74
+ [:list, b0 - LIST_PREFIX_OFFSET, start + 1]
75
+ else # long list
76
+ raise DecodingError.new('Length starts with zero bytes", rlp') if rlp.slice(start+1) == BYTE_ZERO
77
+
78
+ ll = b0 - LIST_PREFIX_OFFSET - SHORT_LENGTH_LIMIT + 1
79
+ l = big_endian_to_int rlp[(start+1)...(start+1+ll)]
80
+ raise DecodingError.new('Long list prefix used for short list', rlp) if l < 56
81
+
82
+ [:list, l, start+1+ll]
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Read the payload of an item from an RLP string.
88
+ #
89
+ # * `rlp` - the rlp string to read from
90
+ # * `type` - the type of the payload (`:str` or `:list`)
91
+ # * `start` - the position at which to start reading
92
+ # * `length` - the length of the payload in bytes
93
+ #
94
+ # Returns a pair `[item, end]`, where `item` is the read item and `end` is
95
+ # the position of the first unprocessed byte.
96
+ #
97
+ def consume_payload(rlp, start, type, length)
98
+ case type
99
+ when :str
100
+ [rlp[start...(start+length)], start+length]
101
+ when :list
102
+ items = []
103
+ next_item_start = start
104
+ payload_end = next_item_start + length
105
+
106
+ while next_item_start < payload_end
107
+ item, next_item_start = consume_item rlp, next_item_start
108
+ items.push item
109
+ end
110
+
111
+ raise DecodingError.new('List length prefix announced a too small length', rlp) if next_item_start > payload_end
112
+
113
+ [items, next_item_start]
114
+ else
115
+ raise TypeError, 'Type must be either :str or :list'
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,61 @@
1
+ module RLP
2
+ module Encode
3
+ include Constant
4
+ include Error
5
+ include Utils
6
+
7
+ def encode(obj, sedes: nil, infer_serializer: true, cache: false)
8
+ # TODO: cache flow
9
+
10
+ if sedes
11
+ item = sedes.serialize(obj)
12
+ elsif infer_serializer
13
+ item = Sedes.infer(obj).serialize(obj)
14
+ else
15
+ item = obj
16
+ end
17
+
18
+ result = encode_raw(item)
19
+ result
20
+ end
21
+
22
+ private
23
+
24
+ def encode_raw(item)
25
+ return item if item.instance_of?(RLP::Data)
26
+ return encode_primitive(item) if primitive?(item)
27
+ return encode_list(item) if list?(item)
28
+
29
+ msg = "Cannot encode object of type #{item.class.name}"
30
+ raise EncodingError.new(msg, item)
31
+ end
32
+
33
+ def encode_primitive(item)
34
+ return str_to_bytes(item) if item.size == 1 && item.ord < 0x80
35
+
36
+ payload = str_to_bytes item
37
+ prefix = length_prefix payload.size, PRIMITIVE_PREFIX_OFFSET
38
+
39
+ "#{prefix}#{payload}"
40
+ end
41
+
42
+ def encode_list(list)
43
+ payload = list.map {|item| encode_raw(item) }.join
44
+ prefix = length_prefix payload.size, LIST_PREFIX_OFFSET
45
+
46
+ "#{prefix}#{payload}"
47
+ end
48
+
49
+ def length_prefix(length, offset)
50
+ if length < SHORT_LENGTH_LIMIT
51
+ (offset+length).chr
52
+ elsif length < LONG_LENGTH_LIMIT
53
+ length_string = int_to_big_endian(length)
54
+ length_len = (offset + SHORT_LENGTH_LIMIT - 1 + length_string.size).chr
55
+ "#{length_len}#{length_string}"
56
+ else
57
+ raise ArgumentError, "Length greater than 256**8"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -39,7 +39,7 @@ module RLP
39
39
  def initialize(message: nil, obj: nil, element_exception: nil, index: nil)
40
40
  if message.nil?
41
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]
42
+ message = "Serialization failed because of element at index #{index} ('#{element_exception}')"
43
43
  end
44
44
 
45
45
  super(message, obj)
@@ -53,7 +53,7 @@ module RLP
53
53
  def initialize(message: nil, serial: nil, element_exception: nil, index: nil)
54
54
  if message.nil?
55
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]
56
+ message = "Deserialization failed because of element at index #{index} ('#{element_exception}')"
57
57
  end
58
58
 
59
59
  super(message, serial)
@@ -1,6 +1,8 @@
1
1
  require_relative 'sedes/big_endian_int'
2
2
  require_relative 'sedes/binary'
3
3
  require_relative 'sedes/list'
4
+ require_relative 'sedes/countable_list'
5
+ require_relative 'sedes/raw'
4
6
 
5
7
  module RLP
6
8
  module Sedes
@@ -12,8 +14,7 @@ module RLP
12
14
  return binary if Binary.valid_type?(obj)
13
15
  return List.new(elements: obj.map {|item| infer(item) }) if RLP.list?(obj)
14
16
 
15
- msg = "Did not find sedes handling type %s" % obj.class.name
16
- raise ArgumentError, msg
17
+ raise TypeError, "Did not find sedes handling type #{obj.class.name}"
17
18
  end
18
19
 
19
20
  def sedes?(obj)
@@ -27,6 +28,10 @@ module RLP
27
28
  def binary
28
29
  @binary ||= Binary.new
29
30
  end
31
+
32
+ def raw
33
+ @raw ||= Raw.new
34
+ end
30
35
  end
31
36
 
32
37
  end
@@ -1,34 +1,33 @@
1
1
  module RLP
2
2
  module Sedes
3
3
  class BigEndianInt
4
- include RLP::Utils
4
+ include Constant
5
+ include Error
6
+ include Utils
5
7
 
6
- ZERO = "\x00".force_encoding('ascii-8bit').freeze
7
- EMPTY = ''.force_encoding('ascii-8bit').freeze
8
-
9
- def initialize(size: nil)
8
+ def initialize(size=nil)
10
9
  @size = size
11
10
  end
12
11
 
13
12
  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
13
+ raise SerializationError.new("Can only serialize integers", obj) unless obj.is_a?(Integer)
14
+ raise SerializationError.new("Cannot serialize negative integers", obj) if obj < 0
16
15
 
17
16
  if @size && obj >= 256**@size
18
- msg = "Integer too large (does not fit in %s bytes)" % @size
19
- raise ArgumentError, msg
17
+ msg = "Integer too large (does not fit in #{@size} bytes)"
18
+ raise SerializationError.new(msg, obj)
20
19
  end
21
20
 
22
- s = obj == 0 ? ZERO : int_to_big_endian(obj)
21
+ s = obj == 0 ? BYTE_EMPTY : int_to_big_endian(obj)
23
22
 
24
- @size ? "#{ZERO * [0, @size-s.size].max}#{s}" : s
23
+ @size ? "#{BYTE_ZERO * [0, @size-s.size].max}#{s}" : s
25
24
  end
26
25
 
27
26
  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
27
+ raise DeserializationError.new("Invalid serialization (wrong size)", serial) if @size && serial.size != @size
28
+ raise DeserializationError.new("Invalid serialization (not minimal length)", serial) if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
30
29
 
31
- serial = serial || ZERO
30
+ serial = serial || BYTE_ZERO
32
31
  big_endian_to_int(serial)
33
32
  end
34
33
 
@@ -18,7 +18,7 @@ module RLP
18
18
  end
19
19
 
20
20
  def serialize(obj)
21
- raise SerializationError.new("Object is not a serializable (%s)" % obj.class, obj) unless self.class.valid_type?(obj)
21
+ raise SerializationError.new("Object is not a serializable (#{obj.class})", obj) unless self.class.valid_type?(obj)
22
22
 
23
23
  serial = str_to_bytes obj
24
24
  raise SerializationError.new("Object has invalid length", serial) unless valid_length?(serial.size)
@@ -27,8 +27,8 @@ module RLP
27
27
  end
28
28
 
29
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)
30
+ raise DeserializationError.new("Objects of type #{serial.class} cannot be deserialized", serial) unless primitive?(serial)
31
+ raise DeserializationError.new("#{serial.class} has invalid length", serial) unless valid_length?(serial.size)
32
32
 
33
33
  serial
34
34
  end
@@ -0,0 +1,57 @@
1
+ module RLP
2
+ module Sedes
3
+ ##
4
+ # A sedes for lists of arbitrary length.
5
+ #
6
+ class CountableList
7
+ include Error
8
+ include Utils
9
+
10
+ def initialize(element_sedes, max_length: nil)
11
+ @element_sedes = element_sedes
12
+ @max_length = max_length
13
+ end
14
+
15
+ def serialize(obj)
16
+ raise ListSerializationError.new(message: "Can only serialize sequences", obj: obj) unless list?(obj)
17
+
18
+ result = []
19
+ obj.each_with_index do |e, i|
20
+ begin
21
+ result.push @element_sedes.serialize(e)
22
+ rescue SerializationError => e
23
+ raise ListSerializationError.new(obj: obj, element_exception: e, index: i)
24
+ end
25
+
26
+ if @max_length && result.size > @max_length
27
+ msg = "Too many elements (#{result.size}, allowed #{@max_length})"
28
+ raise ListSerializationError.new(message: msg, obj: obj)
29
+ end
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ def deserialize(serial)
36
+ raise ListDeserializationError.new(message: 'Can only deserialize sequences', serial: serial) unless list?(serial)
37
+
38
+ result = []
39
+ serial.each_with_index do |e, i|
40
+ begin
41
+ result.push @element_sedes.deserialize(e)
42
+ rescue DeserializationError => e
43
+ raise ListDeserializationError.new(serial: serial, element_exception: e, index: i)
44
+ end
45
+
46
+ if @max_length && result.size > @max_length
47
+ msg = "Too many elements (#{result.size}, allowed #{@max_length})"
48
+ raise ListDeserializationError.new(message: msg, serial: serial)
49
+ end
50
+ end
51
+
52
+ result.freeze
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -1,8 +1,11 @@
1
1
  module RLP
2
2
  module Sedes
3
+ ##
4
+ # A sedes for lists of fixed length
5
+ #
3
6
  class List < Array
4
- include RLP::Error
5
- include RLP::Utils
7
+ include Error
8
+ include Utils
6
9
 
7
10
  def initialize(elements: [], strict: true)
8
11
  super()
@@ -53,11 +56,8 @@ module RLP
53
56
  end
54
57
  end
55
58
 
56
- result
59
+ result.freeze
57
60
  end
58
61
  end
59
-
60
- #TODO: CountableList
61
-
62
62
  end
63
63
  end
@@ -0,0 +1,31 @@
1
+ module RLP
2
+ module Sedes
3
+ ##
4
+ # A sedes that does nothing. Thus, everything that can be directly encoded
5
+ # by RLP is serializable. This sedes can be used as a placeholder when
6
+ # deserializing larger structures.
7
+ #
8
+ class Raw
9
+ include Error
10
+ include Utils
11
+
12
+ def serialize(obj)
13
+ raise SerializationError.new("Can only serialize nested lists of strings", obj) unless serializable?(obj)
14
+ obj
15
+ end
16
+
17
+ def deserialize(serial)
18
+ serial
19
+ end
20
+
21
+ private
22
+
23
+ def serializable?(obj)
24
+ return true if primitive?(obj)
25
+ return obj.all? {|item| serializable?(item) } if list?(obj)
26
+ false
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -27,7 +27,7 @@ module RLP
27
27
  end
28
28
 
29
29
  def encode_hex(b)
30
- raise ArgumentError, "Value must be an instance of String" unless b.instance_of?(String)
30
+ raise TypeError, "Value must be an instance of String" unless b.instance_of?(String)
31
31
  b = str_to_bytes(b) unless b.encoding.name == 'ASCII-8BIT'
32
32
  b.unpack("H*").first
33
33
  end
@@ -1,3 +1,3 @@
1
1
  module RLP
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rlp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Xie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-23 00:00:00.000000000 Z
11
+ date: 2016-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -51,11 +51,15 @@ files:
51
51
  - lib/rlp.rb
52
52
  - lib/rlp/constant.rb
53
53
  - lib/rlp/data.rb
54
+ - lib/rlp/decode.rb
55
+ - lib/rlp/encode.rb
54
56
  - lib/rlp/error.rb
55
57
  - lib/rlp/sedes.rb
56
58
  - lib/rlp/sedes/big_endian_int.rb
57
59
  - lib/rlp/sedes/binary.rb
60
+ - lib/rlp/sedes/countable_list.rb
58
61
  - lib/rlp/sedes/list.rb
62
+ - lib/rlp/sedes/raw.rb
59
63
  - lib/rlp/utils.rb
60
64
  - lib/rlp/version.rb
61
65
  homepage: https://github.com/janx/ruby-rlp