ciri 0.0.1 → 0.0.2

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -3
  3. data/README.md +5 -0
  4. data/Rakefile +24 -2
  5. data/ciri-rlp/.gitignore +11 -0
  6. data/ciri-rlp/.rspec +3 -0
  7. data/ciri-rlp/.travis.yml +5 -0
  8. data/ciri-rlp/CODE_OF_CONDUCT.md +74 -0
  9. data/ciri-rlp/Gemfile +6 -0
  10. data/ciri-rlp/Gemfile.lock +39 -0
  11. data/ciri-rlp/LICENSE.txt +21 -0
  12. data/ciri-rlp/README.md +98 -0
  13. data/ciri-rlp/Rakefile +6 -0
  14. data/ciri-rlp/bin/console +14 -0
  15. data/ciri-rlp/bin/setup +8 -0
  16. data/ciri-rlp/ciri-rlp.gemspec +28 -0
  17. data/{lib → ciri-rlp/lib}/ciri/rlp.rb +2 -2
  18. data/ciri-rlp/lib/ciri/rlp/decode.rb +144 -0
  19. data/ciri-rlp/lib/ciri/rlp/encode.rb +140 -0
  20. data/ciri-rlp/lib/ciri/rlp/serializable.rb +241 -0
  21. data/ciri-rlp/lib/ciri/rlp/version.rb +5 -0
  22. data/ciri-rlp/spec/ciri/fixture_spec.rb +95 -0
  23. data/ciri-rlp/spec/ciri/rlp/decode_spec.rb +68 -0
  24. data/ciri-rlp/spec/ciri/rlp/encode_spec.rb +72 -0
  25. data/ciri-rlp/spec/ciri/rlp/serializable_spec.rb +147 -0
  26. data/ciri-rlp/spec/ciri/rlp_spec.rb +5 -0
  27. data/ciri-rlp/spec/spec_helper.rb +14 -0
  28. data/ciri-utils/.gitignore +11 -0
  29. data/ciri-utils/.rspec +3 -0
  30. data/ciri-utils/.travis.yml +5 -0
  31. data/ciri-utils/CODE_OF_CONDUCT.md +74 -0
  32. data/ciri-utils/Gemfile +6 -0
  33. data/ciri-utils/Gemfile.lock +37 -0
  34. data/ciri-utils/LICENSE.txt +21 -0
  35. data/ciri-utils/README.md +61 -0
  36. data/ciri-utils/Rakefile +6 -0
  37. data/ciri-utils/bin/console +14 -0
  38. data/ciri-utils/bin/setup +8 -0
  39. data/ciri-utils/ciri-utils.gemspec +28 -0
  40. data/{lib → ciri-utils/lib}/ciri/utils.rb +9 -33
  41. data/{lib → ciri-utils/lib}/ciri/utils/logger.rb +0 -0
  42. data/{lib → ciri-utils/lib}/ciri/utils/number.rb +15 -12
  43. data/ciri-utils/lib/ciri/utils/version.rb +5 -0
  44. data/ciri-utils/spec/ciri/utils_spec.rb +5 -0
  45. data/ciri-utils/spec/spec_helper.rb +14 -0
  46. data/ciri.gemspec +6 -3
  47. data/lib/ciri/bloom_filter.rb +83 -0
  48. data/lib/ciri/chain.rb +67 -130
  49. data/lib/ciri/chain/header.rb +2 -2
  50. data/lib/ciri/chain/header_chain.rb +136 -0
  51. data/lib/ciri/chain/transaction.rb +1 -1
  52. data/lib/ciri/crypto.rb +2 -2
  53. data/lib/ciri/db/account_db.rb +145 -0
  54. data/lib/ciri/db/backend/memory.rb +24 -1
  55. data/lib/ciri/devp2p/peer.rb +1 -1
  56. data/lib/ciri/devp2p/rlpx/connection.rb +5 -5
  57. data/lib/ciri/eth/peer.rb +3 -3
  58. data/lib/ciri/eth/protocol_manage.rb +3 -3
  59. data/lib/ciri/eth/protocol_messages.rb +27 -26
  60. data/lib/ciri/ethash.rb +18 -3
  61. data/lib/ciri/evm.rb +101 -56
  62. data/lib/ciri/{utils/lib_c.rb → evm/errors.rb} +28 -18
  63. data/lib/ciri/evm/instruction.rb +3 -1
  64. data/lib/ciri/evm/{serialize.rb → log_entry.rb} +9 -27
  65. data/lib/ciri/evm/machine_state.rb +21 -2
  66. data/lib/ciri/evm/op.rb +19 -16
  67. data/lib/ciri/evm/state.rb +61 -0
  68. data/lib/ciri/evm/vm.rb +78 -102
  69. data/lib/ciri/forks.rb +1 -4
  70. data/lib/ciri/forks/base.rb +55 -0
  71. data/lib/ciri/forks/frontier.rb +37 -10
  72. data/lib/ciri/forks/frontier/cost.rb +186 -0
  73. data/lib/ciri/key.rb +1 -1
  74. data/lib/ciri/pow.rb +1 -1
  75. data/lib/ciri/rlp/decode.rb +6 -3
  76. data/lib/ciri/rlp/encode.rb +10 -10
  77. data/lib/ciri/rlp/serializable.rb +12 -9
  78. data/lib/ciri/serialize.rb +58 -0
  79. data/lib/ciri/trie.rb +362 -0
  80. data/lib/ciri/trie/nibbles.rb +97 -0
  81. data/lib/ciri/trie/nodes.rb +187 -0
  82. data/lib/ciri/{evm → types}/account.rb +20 -13
  83. data/lib/ciri/types/address.rb +16 -11
  84. data/lib/ciri/types/number.rb +73 -0
  85. data/lib/ciri/types/receipt.rb +57 -0
  86. data/lib/ciri/version.rb +1 -1
  87. metadata +82 -19
  88. data/lib/ciri/evm/forks/frontier.rb +0 -183
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
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
13
+ # all 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
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/types/address'
25
+
26
+ module Ciri
27
+ module Serialize
28
+
29
+ extend self
30
+
31
+ def serialize(item)
32
+ case item
33
+ when Integer
34
+ Utils.big_endian_encode(item)
35
+ when Types::Address
36
+ item.to_s
37
+ else
38
+ item
39
+ end
40
+ end
41
+
42
+ def deserialize(type, item)
43
+ if type == Integer && !item.is_a?(Integer)
44
+ Utils.big_endian_decode(item.to_s)
45
+ elsif type == Types::Address && !item.is_a?(Types::Address)
46
+ # check if address represent in Integer
47
+ item = Utils.big_endian_encode(item) if item.is_a?(Integer)
48
+ Types::Address.new(item.size >= 20 ? item[-20..-1] : "\x00".b * 20)
49
+ elsif type.nil?
50
+ # get serialized word
51
+ serialize(item).rjust(32, "\x00".b)
52
+ else
53
+ item
54
+ end
55
+ end
56
+
57
+ end
58
+ end
data/lib/ciri/trie.rb ADDED
@@ -0,0 +1,362 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
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
13
+ # all 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
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/utils'
25
+ require 'ciri/rlp'
26
+ require_relative 'trie/nodes'
27
+
28
+ module Ciri
29
+ # copy py-trie implementation https://github.com/ethereum/py-trie/
30
+ class Trie
31
+
32
+ class BadProofError < StandardError
33
+ end
34
+
35
+ class << self
36
+ def proof(root_hash, key, proofs)
37
+ proof_nodes = proofs.map {|n| n.is_a?(Trie::Node) ? n : Trie::Node.decode(n)}
38
+ proof_with_nodes(root_hash, key, proof_nodes)
39
+ end
40
+
41
+ def proof_with_nodes(root_hash, key, proof_nodes)
42
+ trie = new
43
+ proof_nodes.each do |node|
44
+ trie.persist_node(node)
45
+ end
46
+ trie.root_hash = root_hash
47
+ begin
48
+ result = trie.fetch(key)
49
+ rescue KeyError => e
50
+ raise BadProofError.new("missing proof with hash #{e.message}")
51
+ end
52
+ result
53
+ end
54
+ end
55
+
56
+ include Nodes
57
+
58
+ attr_accessor :root_hash
59
+
60
+ def initialize(db: {}, root_hash: BLANK_NODE_HASH, prune: false)
61
+ @db = db
62
+ @root_hash = root_hash
63
+ @prune = prune
64
+ end
65
+
66
+ def put(key, value, node: root_node)
67
+ trie_key = Nibbles.bytes_to_nibbles(key)
68
+ new_node = put_without_update_root_node(trie_key, value, node: node)
69
+ update_root_node(new_node)
70
+ new_node
71
+ end
72
+
73
+ def []=(key, value)
74
+ put(key, value)
75
+ end
76
+
77
+ def get(trie_key, node: root_node)
78
+ trie_key = Nibbles.bytes_to_nibbles(trie_key) if trie_key.is_a?(String)
79
+ case node
80
+ when NullNode
81
+ NullNode::NULL.to_s
82
+ when Leaf
83
+ trie_key == node.extract_key ? node.value : NullNode::NULL.to_s
84
+ when Extension
85
+ if trie_key[0...node.extract_key.size] == node.extract_key
86
+ sub_node = get_node(node.node_hash)
87
+ get(trie_key[node.extract_key.size..-1], node: sub_node)
88
+ else
89
+ NullNode::NULL.to_s
90
+ end
91
+ when Branch
92
+ if trie_key.empty?
93
+ node.value
94
+ else
95
+ sub_node = get_node(node[trie_key[0]])
96
+ get(trie_key[1..-1], node: sub_node)
97
+ end
98
+ else
99
+ raise "unknown node type #{node}"
100
+ end
101
+ end
102
+
103
+ def fetch(trie_key)
104
+ result = get(trie_key)
105
+ raise KeyError.new("key not found: #{trie_key}") if result.nil?
106
+ result
107
+ end
108
+
109
+ def [](key)
110
+ get(key)
111
+ rescue KeyError
112
+ nil
113
+ end
114
+
115
+ def exists?(key)
116
+ get(key) != NullNode::NULL
117
+ end
118
+
119
+ alias include? exists?
120
+
121
+ def delete(key, node: root_node)
122
+ trie_key = Nibbles.bytes_to_nibbles(key)
123
+ new_node = delete_without_update_root_node(trie_key, node: node)
124
+ update_root_node(new_node)
125
+ new_node
126
+ end
127
+
128
+ def get_node(node_hash)
129
+ if node_hash == BLANK_NODE_HASH
130
+ return NullNode::NULL
131
+ elsif node_hash == NullNode::NULL
132
+ return NullNode::NULL
133
+ end
134
+ if node_hash.size < 32
135
+ encoded_node = node_hash
136
+ else
137
+ encoded_node = @db.fetch(node_hash)
138
+ end
139
+ Node.decode(encoded_node)
140
+ end
141
+
142
+ def root_node
143
+ get_node(@root_hash)
144
+ end
145
+
146
+ def root_node=(value)
147
+ update_root_node(value)
148
+ end
149
+
150
+ def persist_node(node)
151
+ key, value = node_to_db_mapping(node)
152
+ if value
153
+ @db[key] = value
154
+ end
155
+ key
156
+ end
157
+
158
+ private
159
+
160
+ def put_without_update_root_node(trie_key, value, node:)
161
+ prune_node(node)
162
+ case node
163
+ when NullNode
164
+ key = Node.compute_leaf_key(trie_key)
165
+ Leaf.new(key, value)
166
+ when Leaf, Extension
167
+ current_key = node.extract_key
168
+ common_prefix, current_key_remainder, trie_key_remainder = Node.consume_common_prefix(
169
+ current_key,
170
+ trie_key,
171
+ )
172
+
173
+ if current_key_remainder.empty? && trie_key_remainder.empty?
174
+ # put value to current leaf or extension
175
+ if node.is_a?(Leaf)
176
+ return Leaf.new(node.key, value)
177
+ else
178
+ sub_node = get_node(node.node_hash)
179
+ new_node = put_without_update_root_node(trie_key_remainder, value, node: sub_node)
180
+ end
181
+ elsif current_key_remainder.empty?
182
+ # put value to new sub_node
183
+ if node.is_a?(Extension)
184
+ sub_node = get_node(node.node_hash)
185
+ new_node = put_without_update_root_node(trie_key_remainder, value, node: sub_node)
186
+ else
187
+ subnode_position = trie_key_remainder[0]
188
+ subnode_key = Node.compute_leaf_key(trie_key_remainder[1..-1])
189
+ sub_node = Leaf.new(subnode_key, value)
190
+ # new leaf
191
+ new_node = Branch.new_with_value(value: node.value)
192
+ new_node[subnode_position] = persist_node(sub_node)
193
+ new_node
194
+ end
195
+ else
196
+ new_node = Branch.new
197
+ if current_key_remainder.size == 1 && node.is_a?(Extension)
198
+ new_node[current_key_remainder[0]] = node.node_hash
199
+ else
200
+ sub_node = if node.is_a?(Extension)
201
+ key = Node.compute_extension_key(current_key_remainder[1..-1])
202
+ Extension.new(key, node.node_hash)
203
+ else
204
+ key = Node.compute_leaf_key(current_key_remainder[1..-1])
205
+ Leaf.new(key, node.value)
206
+ end
207
+ new_node[current_key_remainder[0]] = persist_node(sub_node)
208
+ end
209
+
210
+ if !trie_key_remainder.empty?
211
+ sub_node = Leaf.new(Node.compute_leaf_key(trie_key_remainder[1..-1]), value)
212
+ new_node[trie_key_remainder[0]] = persist_node(sub_node)
213
+ else
214
+ new_node[-1] = value
215
+ end
216
+ new_node
217
+ end
218
+
219
+ if common_prefix.size > 0
220
+ Extension.new(Node.compute_extension_key(common_prefix), persist_node(new_node))
221
+ else
222
+ new_node
223
+ end
224
+ when Branch
225
+ if !trie_key.empty?
226
+ sub_node = get_node(node[trie_key[0]])
227
+ new_node = put_without_update_root_node(trie_key[1..-1], value, node: sub_node)
228
+ node[trie_key[0]] = persist_node(new_node)
229
+ else
230
+ node[-1] = value
231
+ end
232
+ node
233
+ else
234
+ raise "unknown node type #{node}"
235
+ end
236
+ end
237
+
238
+ def delete_without_update_root_node(trie_key, node:)
239
+ prune_node(node)
240
+ case node
241
+ when NullNode
242
+ NullNode::NULL
243
+ when Leaf, Extension
244
+ delete_kv_node(node, trie_key)
245
+ when Branch
246
+ delete_branch_node(node, trie_key)
247
+ else
248
+ raise "unknown node type #{node}"
249
+ end
250
+ end
251
+
252
+ def update_root_node(root_node)
253
+ if @prune
254
+ old_root_hash = @root_hash
255
+ if old_root_hash != BLANK_NODE_HASH && @db.include?(old_root_hash)
256
+ @db.delete(old_root_hash)
257
+ end
258
+ end
259
+
260
+ if root_node.null?
261
+ @root_hash = BLANK_NODE_HASH
262
+ else
263
+ encoded_root_node = RLP.encode_simple(root_node)
264
+ new_root_hash = Utils.sha3(encoded_root_node)
265
+ @db[new_root_hash] = encoded_root_node
266
+ @root_hash = new_root_hash
267
+ end
268
+ end
269
+
270
+ def node_to_db_mapping(node)
271
+ return [node, nil] if node.null?
272
+ encoded_node = RLP.encode_simple(node)
273
+ return [node, nil] if encoded_node.size < 32
274
+ encoded_node_hash = Utils.sha3(encoded_node)
275
+ [encoded_node_hash, encoded_node]
276
+ end
277
+
278
+ def prune_node(node)
279
+ if @prune
280
+ key, value = node_to_db_mapping node
281
+ @db.delete(key) if value
282
+ end
283
+ end
284
+
285
+ def normalize_branch_node(node)
286
+ sub_nodes = node[0..15].map {|n| get_node(n)}
287
+ return node if sub_nodes.select {|n| !n.null?}.size > 1
288
+ unless node.value.empty?
289
+ return Leaf.new(compute_leaf_key([]), node.value)
290
+ end
291
+ sub_node, sub_node_idx = sub_nodes.each_with_index.find {|v, i| v && !v.null?}
292
+ prune_node(sub_node)
293
+
294
+ case sub_node
295
+ when Leaf, Extension
296
+ new_subnode_key = Nibbles.encode_nibbles([sub_node_idx] + Nibbles.decode_nibbles(sub_node.key))
297
+ sub_node.is_a?(Leaf) ? Leaf.new(new_subnode_key, sub_node.value) : Extension.new(new_subnode_key, sub_node.node_hash)
298
+ when Branch
299
+ subnode_hash = persist_node(sub_node)
300
+ Extension.new(Nibbles.encode_nibbles([sub_node_idx]), subnode_hash)
301
+ else
302
+ raise "unknown sub_node type #{sub_node}"
303
+ end
304
+ end
305
+
306
+ def delete_branch_node(node, trie_key)
307
+ if trie_key.empty?
308
+ node[-1] = NullNode::NULL
309
+ return normalize_branch_node(node)
310
+ end
311
+ node_to_delete = get_node(node[trie_key[0]])
312
+ sub_node = delete_without_update_root_node(trie_key[1..-1], node: node_to_delete)
313
+ encoded_sub_node = persist_node(sub_node)
314
+ return node if encoded_sub_node == node[trie_key[0]]
315
+
316
+ node[trie_key[0]] = encoded_sub_node
317
+ return normalize_branch_node(node) if encoded_sub_node == NullNode::NULL
318
+
319
+ node
320
+ end
321
+
322
+ def delete_kv_node(node, trie_key)
323
+ current_key = node.extract_key
324
+ # key not exists
325
+ return node if trie_key[0...current_key.size] != current_key
326
+ if node.is_a?(Leaf)
327
+ if trie_key == current_key
328
+ return NullNode::NULL
329
+ else
330
+ return node
331
+ end
332
+ end
333
+
334
+ sub_node_key = trie_key[current_key.size..-1]
335
+ sub_node = get_node(node.node_hash)
336
+
337
+ new_sub_node = delete_without_update_root_node(sub_node_key, node: sub_node)
338
+ encoded_new_sub_node = persist_node(new_sub_node)
339
+
340
+ return node if encoded_new_sub_node == node.node_hash
341
+
342
+ return NullNode::NULL if new_sub_node.null?
343
+
344
+ if new_sub_node.is_a?(Leaf) || new_sub_node.is_a?(Extension)
345
+ prune_node(new_sub_node)
346
+ new_key = Nibbles.encode_nibbles(current_key + Nibbles.decode_nibbles(new_sub_node.key))
347
+ if new_sub_node.is_a?(Leaf)
348
+ return Leaf.new(new_key, new_sub_node.value)
349
+ else
350
+ return Extension.new(new_key, new_sub_node.node_hash)
351
+ end
352
+ end
353
+
354
+ if new_sub_node.is_a?(Branch)
355
+ return Extension.new(Nibbles.encode_nibbles(current_key), encoded_new_sub_node)
356
+ end
357
+
358
+ raise "can't correct delete kv node"
359
+ end
360
+
361
+ end
362
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
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
13
+ # all 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
21
+ # THE SOFTWARE.
22
+
23
+
24
+ module Ciri
25
+ class Trie
26
+ module Nibbles
27
+ NIBBLE_TERMINATOR = 16
28
+
29
+ HP_FLAG_2 = 2
30
+ HP_FLAG_0 = 0
31
+
32
+ extend self
33
+
34
+ def decode_nibbles(value)
35
+ nibbles_with_flag = bytes_to_nibbles(value)
36
+ flag = nibbles_with_flag[0]
37
+
38
+ needs_terminator = [HP_FLAG_2, HP_FLAG_2 + 1].include? flag
39
+ is_odd_length = [HP_FLAG_0 + 1, HP_FLAG_2 + 1].include? flag
40
+
41
+ raw_nibbles = if is_odd_length
42
+ nibbles_with_flag[1..-1]
43
+ else
44
+ nibbles_with_flag[2..-1]
45
+ end
46
+ if needs_terminator
47
+ add_nibbles_terminator(raw_nibbles)
48
+ else
49
+ raw_nibbles
50
+ end
51
+ end
52
+
53
+ def encode_nibbles(nibbles)
54
+ flag = if is_nibbles_terminated?(nibbles)
55
+ HP_FLAG_2
56
+ else
57
+ HP_FLAG_0
58
+ end
59
+ raw_nibbles = remove_nibbles_terminator(nibbles)
60
+ flagged_nibbles = if raw_nibbles.size.odd?
61
+ [flag + 1] + raw_nibbles
62
+ else
63
+ [flag, 0] + raw_nibbles
64
+ end
65
+ nibbles_to_bytes(flagged_nibbles)
66
+ end
67
+
68
+ def remove_nibbles_terminator(nibbles)
69
+ return nibbles[0..-2] if is_nibbles_terminated?(nibbles)
70
+ nibbles
71
+ end
72
+
73
+ def bytes_to_nibbles(value)
74
+ hex_s = Utils.to_hex(value)
75
+ hex_s = hex_s[2..-1] if hex_s.start_with?('0x')
76
+ hex_s.each_char.map {|c| c.to_i(16)}
77
+ end
78
+
79
+ def nibbles_to_bytes(nibbles)
80
+ Utils.to_bytes(nibbles.map {|n| n.to_s(16)}.join)
81
+ end
82
+
83
+ def is_nibbles_terminated?(nibbles)
84
+ nibbles && nibbles[-1] == NIBBLE_TERMINATOR
85
+ end
86
+
87
+ def add_nibbles_terminator(nibbles)
88
+ if is_nibbles_terminated?(nibbles)
89
+ nibbles
90
+ else
91
+ nibbles + [NIBBLE_TERMINATOR]
92
+ end
93
+ end
94
+
95
+ end
96
+ end
97
+ end