rlp 0.1.0 → 0.2.0

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