costan-tem_ruby 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG +45 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +75 -0
  4. data/README +8 -0
  5. data/Rakefile +23 -0
  6. data/bin/tem_bench +9 -0
  7. data/bin/tem_ca +13 -0
  8. data/bin/tem_irb +11 -0
  9. data/bin/tem_proxy +65 -0
  10. data/bin/tem_stat +35 -0
  11. data/dev_ca/ca_cert.cer +0 -0
  12. data/dev_ca/ca_cert.pem +32 -0
  13. data/dev_ca/ca_key.pem +27 -0
  14. data/dev_ca/config.yml +14 -0
  15. data/lib/tem/_cert.rb +158 -0
  16. data/lib/tem/apdus/buffers.rb +89 -0
  17. data/lib/tem/apdus/keys.rb +64 -0
  18. data/lib/tem/apdus/lifecycle.rb +13 -0
  19. data/lib/tem/apdus/tag.rb +38 -0
  20. data/lib/tem/auto_conf.rb +25 -0
  21. data/lib/tem/builders/abi.rb +482 -0
  22. data/lib/tem/builders/assembler.rb +314 -0
  23. data/lib/tem/builders/crypto.rb +124 -0
  24. data/lib/tem/builders/isa.rb +120 -0
  25. data/lib/tem/ca.rb +114 -0
  26. data/lib/tem/definitions/abi.rb +65 -0
  27. data/lib/tem/definitions/assembler.rb +23 -0
  28. data/lib/tem/definitions/isa.rb +188 -0
  29. data/lib/tem/ecert.rb +77 -0
  30. data/lib/tem/hive.rb +18 -0
  31. data/lib/tem/keys/asymmetric.rb +116 -0
  32. data/lib/tem/keys/key.rb +48 -0
  33. data/lib/tem/keys/symmetric.rb +47 -0
  34. data/lib/tem/sec_exec_error.rb +63 -0
  35. data/lib/tem/seclosures.rb +81 -0
  36. data/lib/tem/secpack.rb +107 -0
  37. data/lib/tem/tem.rb +31 -0
  38. data/lib/tem/toolkit.rb +101 -0
  39. data/lib/tem/transport/auto_configurator.rb +87 -0
  40. data/lib/tem/transport/java_card_mixin.rb +99 -0
  41. data/lib/tem/transport/jcop_remote_protocol.rb +59 -0
  42. data/lib/tem/transport/jcop_remote_server.rb +171 -0
  43. data/lib/tem/transport/jcop_remote_transport.rb +65 -0
  44. data/lib/tem/transport/pcsc_transport.rb +87 -0
  45. data/lib/tem/transport/transport.rb +10 -0
  46. data/lib/tem_ruby.rb +47 -0
  47. data/tem_ruby.gemspec +35 -0
  48. data/test/_test_cert.rb +70 -0
  49. data/test/builders/test_abi_builder.rb +298 -0
  50. data/test/tem_test_case.rb +26 -0
  51. data/test/tem_unit/test_tem_alu.rb +33 -0
  52. data/test/tem_unit/test_tem_bound_secpack.rb +51 -0
  53. data/test/tem_unit/test_tem_branching.rb +56 -0
  54. data/test/tem_unit/test_tem_crypto_asymmetric.rb +123 -0
  55. data/test/tem_unit/test_tem_crypto_hash.rb +35 -0
  56. data/test/tem_unit/test_tem_crypto_pstore.rb +53 -0
  57. data/test/tem_unit/test_tem_crypto_random.rb +25 -0
  58. data/test/tem_unit/test_tem_emit.rb +23 -0
  59. data/test/tem_unit/test_tem_memory.rb +48 -0
  60. data/test/tem_unit/test_tem_memory_compare.rb +65 -0
  61. data/test/tem_unit/test_tem_output.rb +32 -0
  62. data/test/tem_unit/test_tem_yaml_secpack.rb +47 -0
  63. data/test/test_driver.rb +108 -0
  64. data/test/test_exceptions.rb +35 -0
  65. data/test/transport/test_auto_configurator.rb +114 -0
  66. data/test/transport/test_java_card_mixin.rb +90 -0
  67. data/test/transport/test_jcop_remote.rb +82 -0
  68. data/timings/blank_bound_secpack.rb +18 -0
  69. data/timings/blank_sec.rb +14 -0
  70. data/timings/devchip_decrypt.rb +9 -0
  71. data/timings/post_buffer.rb +10 -0
  72. data/timings/simple_apdu.rb +5 -0
  73. data/timings/timings.rb +64 -0
  74. data/timings/vm_perf.rb +140 -0
  75. data/timings/vm_perf_bound.rb +141 -0
  76. metadata +201 -0
@@ -0,0 +1,89 @@
1
+ # :nodoc: namespace
2
+ module Tem::Apdus
3
+
4
+ module Buffers
5
+ def alloc_buffer(length)
6
+ response = @transport.applet_apdu! :ins => 0x20,
7
+ :p12 => to_tem_short(length)
8
+ return read_tem_byte(response, 0)
9
+ end
10
+
11
+ def release_buffer(buffer_id)
12
+ @transport.applet_apdu! :ins => 0x21, :p1 => buffer_id
13
+ end
14
+
15
+ def flush_buffers
16
+ @transport.applet_apdu! :ins => 0x26
17
+ end
18
+
19
+ def get_buffer_length(buffer_id)
20
+ response = @transport.applet_apdu! :ins => 0x22, :p1 => buffer_id
21
+ return read_tem_short(response, 0)
22
+ end
23
+
24
+ def read_buffer(buffer_id)
25
+ guess_buffer_chunk_size
26
+
27
+ buffer = []
28
+ chunk_id = 0
29
+ while true do
30
+ response = @transport.applet_apdu! :ins => 0x23, :p1 => buffer_id,
31
+ :p2 => chunk_id
32
+ buffer += response
33
+ break if response.length != @buffer_chunk_size
34
+ chunk_id += 1
35
+ end
36
+ return buffer
37
+ end
38
+
39
+ def write_buffer(buffer_id, data)
40
+ guess_buffer_chunk_size
41
+
42
+ chunk_id, offset = 0, 0
43
+ while offset < data.length do
44
+ write_size = [data.length - offset, @buffer_chunk_size].min
45
+ @transport.applet_apdu! :ins => 0x24, :p1 => buffer_id, :p2 => chunk_id,
46
+ :data => data[offset, write_size]
47
+ chunk_id += 1
48
+ offset += write_size
49
+ end
50
+ end
51
+
52
+ def guess_buffer_chunk_size
53
+ @buffer_chunk_size ||= guess_buffer_chunk_size!
54
+ end
55
+
56
+ def guess_buffer_chunk_size!
57
+ response = @transport.applet_apdu! :ins => 0x25
58
+ return read_tem_short(response, 0)
59
+ end
60
+
61
+ def stat_buffers
62
+ response = @transport.applet_apdu! :ins => 0x27
63
+
64
+ memory_types = [:persistent, :clear_on_reset, :clear_on_deselect]
65
+ stat = {:free => {}, :buffers => []}
66
+ memory_types.each_with_index { |mt, i| stat[:free][mt] = read_tem_short(response, i * 2) }
67
+ offset = 6
68
+ i = 0
69
+ while offset < response.length do
70
+ stat[:buffers][i] =
71
+ {:type => memory_types[read_tem_ubyte(response, offset) & 0x3f],
72
+ :pinned => (read_tem_ubyte(response, offset) & 0x80) != 0,
73
+ :free => (read_tem_ubyte(response, offset) & 0x40) == 0,
74
+ :length => read_tem_ushort(response, offset + 1),
75
+ :xlength => read_tem_ushort(response, offset + 3)}
76
+ offset += 5
77
+ i += 1
78
+ end
79
+ return stat
80
+ end
81
+
82
+ def post_buffer(data)
83
+ buffer_id = alloc_buffer data.length
84
+ write_buffer buffer_id, data
85
+ return buffer_id
86
+ end
87
+ end
88
+
89
+ end # namespace Tem::Apdus
@@ -0,0 +1,64 @@
1
+ # :nodoc: namespace
2
+ module Tem::Apdus
3
+
4
+ module Keys
5
+ def devchip_generate_key_pair
6
+ response = @transport.applet_apdu! :ins => 0x40
7
+ return { :privkey_id => read_tem_byte(response, 0),
8
+ :pubkey_id => read_tem_byte(response, 1) }
9
+ end
10
+
11
+ def devchip_release_key(key_id)
12
+ @transport.applet_apdu! :ins => 0x41, :p1 => key_id
13
+ return true
14
+ end
15
+
16
+ def devchip_save_key(key_id)
17
+ response = @transport.applet_apdu! :ins => 0x43, :p1 => key_id
18
+ buffer_id = read_tem_byte response, 0
19
+ buffer_length = read_tem_short response, 1
20
+ key_buffer = read_buffer buffer_id
21
+ release_buffer buffer_id
22
+
23
+ read_tem_key key_buffer[0, buffer_length], 0
24
+ end
25
+
26
+ def devchip_encrypt_decrypt(data, key_id, opcode)
27
+ buffer_id = post_buffer data
28
+ begin
29
+ response = @transport.applet_apdu! :ins => opcode, :p1 => key_id,
30
+ :p2 => buffer_id
31
+ ensure
32
+ release_buffer buffer_id
33
+ end
34
+
35
+ buffer_id = read_tem_byte response, 0
36
+ buffer_length = read_tem_short response, 1
37
+ data_buffer = read_buffer buffer_id
38
+ release_buffer buffer_id
39
+
40
+ return data_buffer[0, buffer_length]
41
+ end
42
+ def devchip_encrypt(data, key_id)
43
+ devchip_encrypt_decrypt data, key_id, 0x44
44
+ end
45
+ def devchip_decrypt(data, key_id)
46
+ devchip_encrypt_decrypt data, key_id, 0x45
47
+ end
48
+
49
+ def stat_keys
50
+ response = @transport.applet_apdu! :ins => 0x27, :p1 => 0x01
51
+ key_types = { 0x99 => :symmetric, 0x55 => :private, 0xAA => :public }
52
+ stat = {:keys => {}}
53
+ offset = 0
54
+ while offset < response.length do
55
+ stat[:keys][read_tem_ubyte(response, offset)] =
56
+ { :type => key_types[read_tem_ubyte(response, offset + 1)],
57
+ :bits => read_tem_ushort(response, offset + 2) }
58
+ offset += 4
59
+ end
60
+ return stat
61
+ end
62
+ end
63
+
64
+ end # namespace Tem::Apdus
@@ -0,0 +1,13 @@
1
+ # :nodoc: namespace
2
+ module Tem::Apdus
3
+
4
+ module Lifecycle
5
+ def activate
6
+ @transport.applet_apdu(:ins => 0x10)[:status] == 0x9000
7
+ end
8
+ def kill
9
+ @transport.applet_apdu(:ins => 0x11)[:status] == 0x9000
10
+ end
11
+ end
12
+
13
+ end # namespace Tem::Apdus
@@ -0,0 +1,38 @@
1
+ # :nodoc: namespace
2
+ module Tem::Apdus
3
+
4
+ module Tag
5
+ def set_tag(tag_data)
6
+ buffer_id = post_buffer tag_data
7
+ begin
8
+ @transport.applet_apdu! :ins => 0x30, :p1 => buffer_id
9
+ ensure
10
+ release_buffer buffer_id
11
+ end
12
+ end
13
+
14
+ def get_tag_length
15
+ response = @transport.applet_apdu! :ins => 0x31
16
+ return read_tem_short(response, 0)
17
+ end
18
+
19
+ def get_tag_data(offset, length)
20
+ buffer_id = alloc_buffer length
21
+ begin
22
+ @transport.applet_apdu! :ins => 0x32, :p1 => buffer_id,
23
+ :data => [to_tem_short(offset),
24
+ to_tem_short(length)].flatten
25
+ tag_data = read_buffer buffer_id
26
+ ensure
27
+ release_buffer buffer_id
28
+ end
29
+ tag_data
30
+ end
31
+
32
+ def get_tag
33
+ tag_length = self.get_tag_length
34
+ get_tag_data 0, tag_length
35
+ end
36
+ end
37
+
38
+ end # namespace Tem::Apdus
@@ -0,0 +1,25 @@
1
+ module Tem
2
+ # Automatically configures a TEM.
3
+ #
4
+ # In case of success, the $tem global variable is set to a Tem::Session that
5
+ # can be used to talk to some TEM. An exception will be raised if the session
6
+ # creation fails.
7
+ #
8
+ # It is safe to call auto_conf multiple times. A single session will be open.
9
+ def self.auto_conf
10
+ return $tem if $tem
11
+ $tem = auto_tem
12
+ end
13
+
14
+ # Creates a new session to a TEM, using an automatically-configured transport.
15
+ # :call-seq:
16
+ # Tem.auto_tem -> Tem::Session
17
+ #
18
+ # In case of success, returns a Tem::Session that can be used to talk to some
19
+ # TEM. An exception will be raised if the session creation fails.
20
+ def self.auto_tem
21
+ transport = Tem::Transport.auto_transport
22
+ raise 'No suitable TEM was found' unless transport
23
+ Tem::Session.new transport
24
+ end
25
+ end
@@ -0,0 +1,482 @@
1
+ require 'openssl'
2
+
3
+
4
+ # :nodoc: namespace
5
+ module Tem::Builders
6
+
7
+ # Builder class for the ABI (Abstract Binary Interface) 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