ciri 0.0.1 → 0.0.2

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