costan-tem_ruby 0.10.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 (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