tem_ruby 0.10.0 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +11 -6
- data/lib/tem/{buffers.rb → apdus/buffers.rb} +6 -1
- data/lib/tem/{keys.rb → apdus/keys.rb} +9 -4
- data/lib/tem/{lifecycle.rb → apdus/lifecycle.rb} +6 -1
- data/lib/tem/{tag.rb → apdus/tag.rb} +6 -1
- data/lib/tem/builders/abi.rb +482 -0
- data/lib/tem/builders/crypto.rb +115 -0
- data/lib/tem/definitions/abi.rb +67 -0
- data/lib/tem/ecert.rb +1 -1
- data/lib/tem/keys/asymmetric.rb +116 -0
- data/lib/tem/keys/key.rb +48 -0
- data/lib/tem/keys/symmetric.rb +47 -0
- data/lib/tem/sec_assembler.rb +1 -2
- data/lib/tem/secpack.rb +5 -4
- data/lib/tem/tem.rb +5 -5
- data/lib/tem/toolkit.rb +2 -1
- data/lib/tem_ruby.rb +14 -6
- data/tem_ruby.gemspec +7 -11
- data/test/builders/test_abi_builder.rb +298 -0
- data/test/test_driver.rb +4 -4
- data/test/test_tem.rb +4 -3
- metadata +28 -26
- data/lib/tem/abi.rb +0 -55
- data/lib/tem/crypto_abi.rb +0 -264
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -9,21 +9,25 @@ dev_ca/ca_cert.pem
|
|
9
9
|
dev_ca/ca_key.pem
|
10
10
|
dev_ca/config.yml
|
11
11
|
lib/tem/_cert.rb
|
12
|
-
lib/tem/
|
12
|
+
lib/tem/apdus/buffers.rb
|
13
|
+
lib/tem/apdus/keys.rb
|
14
|
+
lib/tem/apdus/lifecycle.rb
|
15
|
+
lib/tem/apdus/tag.rb
|
13
16
|
lib/tem/auto_conf.rb
|
14
|
-
lib/tem/
|
17
|
+
lib/tem/builders/abi.rb
|
18
|
+
lib/tem/builders/crypto.rb
|
15
19
|
lib/tem/ca.rb
|
16
|
-
lib/tem/
|
20
|
+
lib/tem/definitions/abi.rb
|
17
21
|
lib/tem/ecert.rb
|
18
22
|
lib/tem/hive.rb
|
19
|
-
lib/tem/keys.rb
|
20
|
-
lib/tem/
|
23
|
+
lib/tem/keys/asymmetric.rb
|
24
|
+
lib/tem/keys/key.rb
|
25
|
+
lib/tem/keys/symmetric.rb
|
21
26
|
lib/tem/sec_assembler.rb
|
22
27
|
lib/tem/sec_exec_error.rb
|
23
28
|
lib/tem/sec_opcodes.rb
|
24
29
|
lib/tem/seclosures.rb
|
25
30
|
lib/tem/secpack.rb
|
26
|
-
lib/tem/tag.rb
|
27
31
|
lib/tem/tem.rb
|
28
32
|
lib/tem/toolkit.rb
|
29
33
|
lib/tem/transport/auto_configurator.rb
|
@@ -39,6 +43,7 @@ Manifest
|
|
39
43
|
Rakefile
|
40
44
|
README
|
41
45
|
test/_test_cert.rb
|
46
|
+
test/builders/test_abi_builder.rb
|
42
47
|
test/tem_test_case.rb
|
43
48
|
test/test_driver.rb
|
44
49
|
test/test_exceptions.rb
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Tem::Apdus
|
3
|
+
|
4
|
+
module Buffers
|
2
5
|
def alloc_buffer(length)
|
3
6
|
response = @transport.applet_apdu! :ins => 0x20,
|
4
7
|
:p12 => to_tem_short(length)
|
@@ -83,3 +86,5 @@ module Tem::Buffers
|
|
83
86
|
return buffer_id
|
84
87
|
end
|
85
88
|
end
|
89
|
+
|
90
|
+
end # namespace Tem::Apdus
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Tem::Apdus
|
3
|
+
|
4
|
+
module Keys
|
2
5
|
def devchip_generate_key_pair
|
3
6
|
response = @transport.applet_apdu! :ins => 0x40
|
4
7
|
return { :privkey_id => read_tem_byte(response, 0),
|
@@ -11,13 +14,13 @@ module Tem::Keys
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def devchip_save_key(key_id)
|
14
|
-
response = @transport.applet_apdu! :ins => 0x43, :p1 => key_id
|
17
|
+
response = @transport.applet_apdu! :ins => 0x43, :p1 => key_id
|
15
18
|
buffer_id = read_tem_byte response, 0
|
16
19
|
buffer_length = read_tem_short response, 1
|
17
20
|
key_buffer = read_buffer buffer_id
|
18
21
|
release_buffer buffer_id
|
19
22
|
|
20
|
-
read_tem_key key_buffer[0
|
23
|
+
read_tem_key key_buffer[0, buffer_length], 0
|
21
24
|
end
|
22
25
|
|
23
26
|
def devchip_encrypt_decrypt(data, key_id, opcode)
|
@@ -34,7 +37,7 @@ module Tem::Keys
|
|
34
37
|
data_buffer = read_buffer buffer_id
|
35
38
|
release_buffer buffer_id
|
36
39
|
|
37
|
-
return data_buffer[0
|
40
|
+
return data_buffer[0, buffer_length]
|
38
41
|
end
|
39
42
|
def devchip_encrypt(data, key_id)
|
40
43
|
devchip_encrypt_decrypt data, key_id, 0x44
|
@@ -57,3 +60,5 @@ module Tem::Keys
|
|
57
60
|
return stat
|
58
61
|
end
|
59
62
|
end
|
63
|
+
|
64
|
+
end # namespace Tem::Apdus
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Tem::Apdus
|
3
|
+
|
4
|
+
module Lifecycle
|
2
5
|
def activate
|
3
6
|
@transport.applet_apdu(:ins => 0x10)[:status] == 0x9000
|
4
7
|
end
|
@@ -6,3 +9,5 @@ module Tem::Lifecycle
|
|
6
9
|
@transport.applet_apdu(:ins => 0x11)[:status] == 0x9000
|
7
10
|
end
|
8
11
|
end
|
12
|
+
|
13
|
+
end # namespace Tem::Apdus
|
@@ -0,0 +1,482 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Tem::Builders
|
6
|
+
|
7
|
+
# Builder class and namespace for the ABI builder.
|
8
|
+
class Abi
|
9
|
+
# Creates a builder targeting a module / class.
|
10
|
+
#
|
11
|
+
# The given parameter should be a class or module
|
12
|
+
def self.define_abi(class_or_module) # :yields: abi
|
13
|
+
yield new(class_or_module)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Defines the methods for handling a fixed-length number type in an ABI.
|
17
|
+
#
|
18
|
+
# The |options| hash supports the following keys:
|
19
|
+
# signed:: if false, the value type cannot hold negative numbers;
|
20
|
+
# signed values are stored using 2s-complement; defaults to true
|
21
|
+
# big_endian:: if true, bytes are sent over the wire in big-endian order
|
22
|
+
#
|
23
|
+
# The following methods are defined for a type named 'name':
|
24
|
+
# * read_name(array, offset) -> number
|
25
|
+
# * to_name(number) -> array
|
26
|
+
# * signed_to_name(number) -> array # takes signed inputs on unsigned types
|
27
|
+
# * name_length -> number # the number of bytes in the number type
|
28
|
+
def fixed_length_number(name, bytes, options = {})
|
29
|
+
impl = Tem::Builders::Abi::Impl
|
30
|
+
signed = options.fetch :signed, true
|
31
|
+
big_endian = options.fetch :big_endian, true
|
32
|
+
|
33
|
+
defines = Proc.new do
|
34
|
+
define_method :"read_#{name}" do |array, offset|
|
35
|
+
impl.number_from_array array, offset, bytes, signed, big_endian
|
36
|
+
end
|
37
|
+
define_method :"to_#{name}" do |n|
|
38
|
+
impl.check_number_range n, bytes, signed
|
39
|
+
impl.number_to_array n, bytes, signed, big_endian
|
40
|
+
end
|
41
|
+
define_method :"signed_to_#{name}" do |n|
|
42
|
+
impl.number_to_array n, bytes, signed, big_endian
|
43
|
+
end
|
44
|
+
define_method(:"#{name}_length") { bytes }
|
45
|
+
end
|
46
|
+
|
47
|
+
@target.class_eval &defines
|
48
|
+
(class << @target; self; end).module_eval &defines
|
49
|
+
end
|
50
|
+
|
51
|
+
# Defines the methods for handling a variable-length number type in an ABI.
|
52
|
+
#
|
53
|
+
# The length_type argument holds the name of a fixed-length number type that
|
54
|
+
# will be used to store the length of hte variable-length number.
|
55
|
+
#
|
56
|
+
# The |options| hash supports the following keys:
|
57
|
+
# signed:: if false, the value type cannot hold negative numbers;
|
58
|
+
# signed values are stored using 2s-complement; defaults to true
|
59
|
+
# big_endian:: if true, bytes are sent over the wire in big-endian order
|
60
|
+
#
|
61
|
+
# The following methods are defined for a type named 'name':
|
62
|
+
# * read_name(array, offset) -> number
|
63
|
+
# * read_name_length(array, offset) -> number
|
64
|
+
# * to_name(number) -> array
|
65
|
+
def variable_length_number(name, length_type, options = {})
|
66
|
+
impl = Tem::Builders::Abi::Impl
|
67
|
+
signed = options.fetch :signed, true
|
68
|
+
big_endian = options.fetch :big_endian, true
|
69
|
+
length_bytes = @target.send :"#{length_type}_length"
|
70
|
+
read_length_msg = :"read_#{length_type}"
|
71
|
+
write_length_msg = :"to_#{length_type}"
|
72
|
+
|
73
|
+
defines = Proc.new do
|
74
|
+
define_method :"read_#{name}" do |array, offset|
|
75
|
+
length = self.send read_length_msg, array, offset
|
76
|
+
impl.number_from_array array, offset + length_bytes, length, signed,
|
77
|
+
big_endian
|
78
|
+
end
|
79
|
+
define_method :"to_#{name}" do |n|
|
80
|
+
number_data = impl.number_to_array n, nil, signed, big_endian
|
81
|
+
length_data = self.send write_length_msg, number_data.length
|
82
|
+
length_data + number_data
|
83
|
+
end
|
84
|
+
define_method :"read_#{name}_length" do |array, offset|
|
85
|
+
length_bytes + self.send(read_length_msg, array, offset)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@target.class_eval &defines
|
90
|
+
(class << @target; self; end).module_eval &defines
|
91
|
+
end
|
92
|
+
|
93
|
+
# Defines the methods for handling a group of packed variable-length numbers
|
94
|
+
# in the ABI.
|
95
|
+
#
|
96
|
+
# When serializing a group of variable-length numbers, it's desirable to have
|
97
|
+
# the lengths of all the numbers grouped before the number data. This makes
|
98
|
+
# reading and writing easier & faster for embedded code. The optimization is
|
99
|
+
# important enough that it's made its way into the API.
|
100
|
+
#
|
101
|
+
# All the numbers' lengths are assumed to be represented by the same
|
102
|
+
# fixed-length type, whose name is given as the length_type parameter.
|
103
|
+
#
|
104
|
+
# The numbers are de-serialized into a hash, where each number is associated
|
105
|
+
# with a key. The components argument specifies the names of the keys, in the
|
106
|
+
# order that the numbers are serialized in.
|
107
|
+
#
|
108
|
+
# The |options| hash supports the following keys:
|
109
|
+
# signed:: if false, the value type cannot hold negative numbers;
|
110
|
+
# signed values are stored using 2s-complement; defaults to true
|
111
|
+
# big_endian:: if true, bytes are sent over the wire in big-endian order
|
112
|
+
#
|
113
|
+
# The following methods are defined for a type named 'name':
|
114
|
+
# * read_name(array, offset) -> hash
|
115
|
+
# * read_name_length(array, offset) -> number
|
116
|
+
# * to_name(hash) -> array
|
117
|
+
# * name_components -> array
|
118
|
+
def packed_variable_length_numbers(name, length_type, components,
|
119
|
+
options = {})
|
120
|
+
impl = Tem::Builders::Abi::Impl
|
121
|
+
sub_names = components.freeze
|
122
|
+
signed = options.fetch :signed, true
|
123
|
+
big_endian = options.fetch :big_endian, true
|
124
|
+
length_bytes = @target.send :"#{length_type}_length"
|
125
|
+
read_length_msg = :"read_#{length_type}"
|
126
|
+
write_length_msg = :"to_#{length_type}"
|
127
|
+
|
128
|
+
defines = Proc.new do
|
129
|
+
define_method :"read_#{name}" do |array, offset|
|
130
|
+
response = {}
|
131
|
+
data_offset = offset + length_bytes * sub_names.length
|
132
|
+
sub_names.each_with_index do |sub_name, i|
|
133
|
+
length = self.send read_length_msg, array, offset + i * length_bytes
|
134
|
+
response[sub_name] =
|
135
|
+
impl.number_from_array array, data_offset, length, signed,
|
136
|
+
big_endian
|
137
|
+
data_offset += length
|
138
|
+
end
|
139
|
+
response
|
140
|
+
end
|
141
|
+
define_method :"to_#{name}" do |numbers|
|
142
|
+
number_data = sub_names.map do |sub_name|
|
143
|
+
impl.number_to_array numbers[sub_name], nil, signed, big_endian
|
144
|
+
end
|
145
|
+
length_data = number_data.map do |number|
|
146
|
+
self.send write_length_msg, number.length
|
147
|
+
end
|
148
|
+
# Concatenate all the arrays without using flatten.
|
149
|
+
lengths = length_data.inject([]) { |acc, i| acc += i }
|
150
|
+
number_data.inject(lengths) { |acc, i| acc += i }
|
151
|
+
end
|
152
|
+
define_method :"read_#{name}_length" do |array, offset|
|
153
|
+
response = sub_names.length * length_bytes
|
154
|
+
0.upto(sub_names.length - 1) do |i|
|
155
|
+
response += self.send read_length_msg, array,
|
156
|
+
offset + i * length_bytes
|
157
|
+
end
|
158
|
+
response
|
159
|
+
end
|
160
|
+
define_method(:"#{name}_components") { sub_names }
|
161
|
+
end
|
162
|
+
|
163
|
+
@target.class_eval &defines
|
164
|
+
(class << @target; self; end).module_eval &defines
|
165
|
+
end
|
166
|
+
|
167
|
+
# Defines the methods for handling a fixed-length string type in an ABI.
|
168
|
+
#
|
169
|
+
# The |options| hash supports the following keys:
|
170
|
+
# signed:: if false, the value type cannot hold negative numbers;
|
171
|
+
# signed values are stored using 2s-complement; defaults to true
|
172
|
+
# big_endian:: if true, bytes are sent over the wire in big-endian order
|
173
|
+
#
|
174
|
+
# The following methods are defined for a type named 'name':
|
175
|
+
# * read_name(array, offset) -> string
|
176
|
+
# * to_name(string or array) -> array
|
177
|
+
# * name_length -> number # the number of bytes in the string type
|
178
|
+
def fixed_length_string(name, bytes, options = {})
|
179
|
+
impl = Tem::Builders::Abi::Impl
|
180
|
+
signed = options.fetch :signed, true
|
181
|
+
big_endian = options.fetch :big_endian, true
|
182
|
+
|
183
|
+
defines = Proc.new do
|
184
|
+
define_method :"read_#{name}" do |array, offset|
|
185
|
+
impl.string_from_array array, offset, bytes
|
186
|
+
end
|
187
|
+
define_method :"to_#{name}" do |n|
|
188
|
+
impl.string_to_array n, bytes
|
189
|
+
end
|
190
|
+
define_method(:"#{name}_length") { bytes }
|
191
|
+
end
|
192
|
+
|
193
|
+
@target.class_eval &defines
|
194
|
+
(class << @target; self; end).module_eval &defines
|
195
|
+
end
|
196
|
+
|
197
|
+
# Defines methods for handling a complex ABI structure wrapped into an object.
|
198
|
+
#
|
199
|
+
# Objects are assumed to be of the object_class type. The objects are
|
200
|
+
# serialized according to a schema, which is an array of 2-element directives.
|
201
|
+
# The first element in a directive indicates the lower-level ABI type to be
|
202
|
+
# serialized, and the 2nd element indicates the mapping between the object and
|
203
|
+
# the higher level ABI. The mapping can be:
|
204
|
+
# * a symbol - the lower level ABI output is assigned to an object property
|
205
|
+
# * a hash - the keys in the lower level ABI output are assigned to the
|
206
|
+
# object properties indicated by the values
|
207
|
+
# * nil - the components of the lower level ABI type (which should act like
|
208
|
+
# packed_variable_length_numbers types) are mapped to identically
|
209
|
+
# named object properties
|
210
|
+
#
|
211
|
+
# The following methods are defined for a type named 'name':
|
212
|
+
# * read_name(array, offset) -> object
|
213
|
+
# * read_name_length(array, offset) -> number
|
214
|
+
# * to_name(object) -> array
|
215
|
+
# * name_class -> Class
|
216
|
+
#
|
217
|
+
# The following hooks (Procs in the hooks argument) are supported:
|
218
|
+
# new(object_class) -> object:: called to instantiate a new object in read_;
|
219
|
+
# if the hook is not present, object_class.new is used instead
|
220
|
+
# read(object) -> object:: called after the object is de-serialized using
|
221
|
+
# the lower-level ABI; if the hook is present, its value is returned
|
222
|
+
# from the read_ method
|
223
|
+
# to(object) -> object:: called before the object is serialized using the
|
224
|
+
# lower-level ABI; if the hook is present, its value is used for
|
225
|
+
# serialization
|
226
|
+
def object_wrapper(name, object_class, schema, hooks = {})
|
227
|
+
if hooks[:new]
|
228
|
+
read_body = "r = #{name}_newhook(#{name}_class);"
|
229
|
+
else
|
230
|
+
read_body = "r = #{name}_class.new;"
|
231
|
+
end
|
232
|
+
|
233
|
+
to_body = "r = [];"
|
234
|
+
to_body << "value = #{name}_tohook(value);" if hooks[:to]
|
235
|
+
|
236
|
+
readlen_body = "old_offset = offset;"
|
237
|
+
|
238
|
+
0.upto schema.length / 2 - 1 do |i|
|
239
|
+
abi_type = schema[i * 2]
|
240
|
+
type_mapping = schema[i * 2 + 1]
|
241
|
+
|
242
|
+
# Set up the translation table.
|
243
|
+
if type_mapping.nil?
|
244
|
+
type_mapping = {}
|
245
|
+
components = @target.send :"#{abi_type}_components"
|
246
|
+
components.each { |c| type_mapping[c] = c }
|
247
|
+
end
|
248
|
+
|
249
|
+
# Set up the read_ and read_name_length methods.
|
250
|
+
if abi_type.kind_of? Symbol
|
251
|
+
read_body << "v = read_#{abi_type}(array,offset);"
|
252
|
+
else
|
253
|
+
read_body << "v = #{abi_type.inspect};"
|
254
|
+
end
|
255
|
+
case type_mapping
|
256
|
+
when Symbol
|
257
|
+
read_body << "r.#{type_mapping} = v;"
|
258
|
+
when Hash, nil
|
259
|
+
type_mapping.each do |k, v|
|
260
|
+
read_body << "r.#{v} = v[:#{k}];"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
if abi_type.kind_of? Symbol
|
264
|
+
if @target.respond_to? :"#{abi_type}_length"
|
265
|
+
read_body << "offset += #{@target.send :"#{abi_type}_length"};"
|
266
|
+
readlen_body << "offset += #{@target.send :"#{abi_type}_length"};"
|
267
|
+
elsif @target.respond_to? :"read_#{abi_type}_length"
|
268
|
+
read_body << "offset += read_#{abi_type}_length(array,offset);"
|
269
|
+
readlen_body << "offset += read_#{abi_type}_length(array,offset);"
|
270
|
+
else
|
271
|
+
raise "#{abi_type} doesn't support _length or read_#{abi_type}_length"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Set up the to_ method.
|
276
|
+
next unless abi_type.kind_of? Symbol
|
277
|
+
to_body << "r += to_#{abi_type}("
|
278
|
+
case type_mapping
|
279
|
+
when Symbol
|
280
|
+
to_body << "value.#{type_mapping}"
|
281
|
+
when Hash
|
282
|
+
to_body << type_mapping.map { |k, v| ":#{k} => value.#{v}" }.join(', ')
|
283
|
+
end
|
284
|
+
to_body << ");"
|
285
|
+
end
|
286
|
+
read_body << "r = self.#{name}_readhook(r);" if hooks[:read]
|
287
|
+
read_body << "r;"
|
288
|
+
to_body << "r;"
|
289
|
+
readlen_body << "offset - old_offset;"
|
290
|
+
|
291
|
+
define_str = "def read_#{name}(array,offset);#{read_body}end;"
|
292
|
+
define_str << "def read_#{name}_length(array,offset);#{readlen_body}end;"
|
293
|
+
define_str << "def to_#{name}(value);#{to_body}end;"
|
294
|
+
|
295
|
+
defines = Proc.new do
|
296
|
+
define_method(:"#{name}_class") { object_class }
|
297
|
+
define_method(:"#{name}_newhook", &hooks[:new]) if hooks[:new]
|
298
|
+
define_method(:"#{name}_readhook", &hooks[:read]) if hooks[:read]
|
299
|
+
define_method(:"#{name}_tohook", &hooks[:to]) if hooks[:to]
|
300
|
+
end
|
301
|
+
|
302
|
+
@target.class_eval &defines
|
303
|
+
@target.class_eval define_str
|
304
|
+
(class << @target; self; end).module_eval &defines
|
305
|
+
(class << @target; self; end).module_eval define_str
|
306
|
+
end
|
307
|
+
|
308
|
+
# Defines methods for handling an 'enum'-like ABI type whose type is
|
309
|
+
# determined by a fixed-length tag that is prefixed to the data.
|
310
|
+
#
|
311
|
+
# tag_length indicates the tag's length, in bytes. The mapping between tags
|
312
|
+
# and lower-level ABI types is expressed as an array of rules. Each rule is a
|
313
|
+
# hash, and the following attributes are supported.
|
314
|
+
# tag:: an array of numbers; the tag must match this array (de-serializing)
|
315
|
+
# type:: the lower-level ABI type used for serialization/de-serialization
|
316
|
+
# class:: a ruby Class; if present, the value must be a kind of the given
|
317
|
+
# class to match the rule (serializing)
|
318
|
+
# predicate:: a Proc; if present, the Proc is given the value, and must
|
319
|
+
# return a true value for the value to match the rule (serializing)
|
320
|
+
def conditional_wrapper(name, tag_length, rules)
|
321
|
+
impl = Tem::Builders::Abi::Impl
|
322
|
+
|
323
|
+
defines = Proc.new do
|
324
|
+
define_method :"read_#{name}" do |array, offset|
|
325
|
+
tag = array[offset, tag_length]
|
326
|
+
matching_rule = rules.find { |rule| rule[:tag] == tag }
|
327
|
+
raise "Rules don't cover tag #{tag.inspect}" unless matching_rule
|
328
|
+
self.send :"read_#{matching_rule[:type]}", array, offset + tag_length
|
329
|
+
end
|
330
|
+
define_method :"read_#{name}_length" do |array, offset|
|
331
|
+
tag = array[offset, tag_length]
|
332
|
+
matching_rule = rules.find { |rule| rule[:tag] == tag }
|
333
|
+
raise "Rules don't cover tag #{tag.inspect}" unless matching_rule
|
334
|
+
if self.respond_to? :"#{matching_rule[:type]}_length"
|
335
|
+
tag_length + self.send(:"#{matching_rule[:type]}_length")
|
336
|
+
else
|
337
|
+
tag_length + self.send(:"read_#{matching_rule[:type]}_length", array,
|
338
|
+
offset + tag_length)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
define_method :"to_#{name}" do |value|
|
342
|
+
matching_rule = rules.find do |rule|
|
343
|
+
next false if rule[:class] && !value.kind_of?(rule[:class])
|
344
|
+
next false if rule[:predicate] && !rule[:predicate].call(value)
|
345
|
+
true
|
346
|
+
end
|
347
|
+
|
348
|
+
raise "Rules don't cover #{value.inspect}" unless matching_rule
|
349
|
+
matching_rule[:tag] + self.send(:"to_#{matching_rule[:type]}", value)
|
350
|
+
end
|
351
|
+
define_method(:"#{name}_length") { bytes }
|
352
|
+
end
|
353
|
+
|
354
|
+
@target.class_eval &defines
|
355
|
+
(class << @target; self; end).module_eval &defines
|
356
|
+
end
|
357
|
+
|
358
|
+
# The module / class impacted by the builder.
|
359
|
+
attr_reader :target
|
360
|
+
|
361
|
+
# Creates a builder targeting a module / class.
|
362
|
+
def initialize(target)
|
363
|
+
@target = target
|
364
|
+
end
|
365
|
+
private_class_method :new
|
366
|
+
end # class Abi
|
367
|
+
|
368
|
+
|
369
|
+
# Implementation code for the ABI methods.
|
370
|
+
module Abi::Impl
|
371
|
+
# Reads a variable-length number serialized to an array.
|
372
|
+
#
|
373
|
+
# The source is indicated by the array and offset parameters. The number's
|
374
|
+
# length is given in bytes.
|
375
|
+
def self.number_from_array(array, offset, length, is_signed, is_big_endian)
|
376
|
+
# Read the raw number from the array.
|
377
|
+
number = 0
|
378
|
+
if is_big_endian
|
379
|
+
0.upto length - 1 do |i|
|
380
|
+
number = (number << 8) | array[offset + i]
|
381
|
+
end
|
382
|
+
else
|
383
|
+
(length - 1).downto 0 do |i|
|
384
|
+
number = (number << 8) | array[offset + i]
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
if is_signed # Add the sign if necessary.
|
389
|
+
range = 1 << (8 * length)
|
390
|
+
max_value = (range >> 1) - 1
|
391
|
+
number -= range if number > max_value
|
392
|
+
end
|
393
|
+
|
394
|
+
number
|
395
|
+
end
|
396
|
+
|
397
|
+
# Writes a fixed or variable-length number to an array.
|
398
|
+
#
|
399
|
+
# The source is indicated by the array and offset parameters. The number's
|
400
|
+
# length is given in bytes.
|
401
|
+
def self.number_to_array(number, length, is_signed, is_big_endian)
|
402
|
+
bytes = []
|
403
|
+
if number.kind_of? OpenSSL::BN # OpenSSL key material
|
404
|
+
if is_signed and number < 0 # normalize number
|
405
|
+
range = OpenSSL::BN.new("1") << (8 * length)
|
406
|
+
number += range
|
407
|
+
end
|
408
|
+
|
409
|
+
length ||= [number.num_bytes, 1].max
|
410
|
+
v = 0
|
411
|
+
(length * 8 - 1).downto 0 do |i|
|
412
|
+
v = (v << 1) | (number.bit_set?(i) ? 1 : 0)
|
413
|
+
if i & 7 == 0
|
414
|
+
bytes << v
|
415
|
+
v = 0
|
416
|
+
end
|
417
|
+
end
|
418
|
+
bytes.reverse! unless is_big_endian
|
419
|
+
else # Ruby number.
|
420
|
+
if length
|
421
|
+
length.times do
|
422
|
+
bytes << (number & 0xFF)
|
423
|
+
number >>= 8
|
424
|
+
end
|
425
|
+
else
|
426
|
+
loop do
|
427
|
+
bytes << (number & 0xFF)
|
428
|
+
number >>= 8
|
429
|
+
break if number == 0
|
430
|
+
end
|
431
|
+
end
|
432
|
+
bytes.reverse! if is_big_endian
|
433
|
+
end
|
434
|
+
bytes
|
435
|
+
end
|
436
|
+
|
437
|
+
# Checks the range of a number for fixed-length encoding.
|
438
|
+
def self.check_number_range(number, length, is_signed)
|
439
|
+
range = 1 << (8 * length)
|
440
|
+
if is_signed
|
441
|
+
min_value, max_value = -(range >> 1), (range >> 1) - 1
|
442
|
+
else
|
443
|
+
min_value, max_value = 0, range - 1
|
444
|
+
end
|
445
|
+
|
446
|
+
exception_string = "Number #{number} exceeds #{min_value}-#{max_value}"
|
447
|
+
raise exception_string if number < min_value or number > max_value
|
448
|
+
end
|
449
|
+
|
450
|
+
# Reads a variable-length string serialized to an array.
|
451
|
+
#
|
452
|
+
# The source is indicated by the array and offset parameters. The number's
|
453
|
+
# length is given in bytes.
|
454
|
+
def self.string_from_array(array, offset, length)
|
455
|
+
array[offset, length].pack('C*')
|
456
|
+
end
|
457
|
+
|
458
|
+
# Writes a fixed or variable-length string to an array.
|
459
|
+
#
|
460
|
+
# The source is indicated by the array and offset parameters. The number's
|
461
|
+
# length is given in bytes.
|
462
|
+
def self.string_to_array(array_or_string, length)
|
463
|
+
if array_or_string.respond_to? :to_str
|
464
|
+
# array_or_string is String-like
|
465
|
+
string = array_or_string.to_str
|
466
|
+
array = string.unpack('C*')
|
467
|
+
else
|
468
|
+
# array_or_string is Array-like
|
469
|
+
array = array_or_string
|
470
|
+
end
|
471
|
+
|
472
|
+
if length and array.length > length
|
473
|
+
raise "Cannot fit #{array_or_string.inspect} into a #{length}-byte string"
|
474
|
+
end
|
475
|
+
# Pad the array with zeros up to the fixed length.
|
476
|
+
length ||= 0
|
477
|
+
array << 0 while array.length < length
|
478
|
+
array
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
end # namespace Tem::Builders
|