costan-tem_ruby 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +45 -0
- data/LICENSE +21 -0
- data/Manifest +75 -0
- data/README +8 -0
- data/Rakefile +23 -0
- data/bin/tem_bench +9 -0
- data/bin/tem_ca +13 -0
- data/bin/tem_irb +11 -0
- data/bin/tem_proxy +65 -0
- data/bin/tem_stat +35 -0
- data/dev_ca/ca_cert.cer +0 -0
- data/dev_ca/ca_cert.pem +32 -0
- data/dev_ca/ca_key.pem +27 -0
- data/dev_ca/config.yml +14 -0
- data/lib/tem/_cert.rb +158 -0
- data/lib/tem/apdus/buffers.rb +89 -0
- data/lib/tem/apdus/keys.rb +64 -0
- data/lib/tem/apdus/lifecycle.rb +13 -0
- data/lib/tem/apdus/tag.rb +38 -0
- data/lib/tem/auto_conf.rb +25 -0
- data/lib/tem/builders/abi.rb +482 -0
- data/lib/tem/builders/assembler.rb +314 -0
- data/lib/tem/builders/crypto.rb +124 -0
- data/lib/tem/builders/isa.rb +120 -0
- data/lib/tem/ca.rb +114 -0
- data/lib/tem/definitions/abi.rb +65 -0
- data/lib/tem/definitions/assembler.rb +23 -0
- data/lib/tem/definitions/isa.rb +188 -0
- data/lib/tem/ecert.rb +77 -0
- data/lib/tem/hive.rb +18 -0
- 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_exec_error.rb +63 -0
- data/lib/tem/seclosures.rb +81 -0
- data/lib/tem/secpack.rb +107 -0
- data/lib/tem/tem.rb +31 -0
- data/lib/tem/toolkit.rb +101 -0
- data/lib/tem/transport/auto_configurator.rb +87 -0
- data/lib/tem/transport/java_card_mixin.rb +99 -0
- data/lib/tem/transport/jcop_remote_protocol.rb +59 -0
- data/lib/tem/transport/jcop_remote_server.rb +171 -0
- data/lib/tem/transport/jcop_remote_transport.rb +65 -0
- data/lib/tem/transport/pcsc_transport.rb +87 -0
- data/lib/tem/transport/transport.rb +10 -0
- data/lib/tem_ruby.rb +47 -0
- data/tem_ruby.gemspec +35 -0
- data/test/_test_cert.rb +70 -0
- data/test/builders/test_abi_builder.rb +298 -0
- data/test/tem_test_case.rb +26 -0
- data/test/tem_unit/test_tem_alu.rb +33 -0
- data/test/tem_unit/test_tem_bound_secpack.rb +51 -0
- data/test/tem_unit/test_tem_branching.rb +56 -0
- data/test/tem_unit/test_tem_crypto_asymmetric.rb +123 -0
- data/test/tem_unit/test_tem_crypto_hash.rb +35 -0
- data/test/tem_unit/test_tem_crypto_pstore.rb +53 -0
- data/test/tem_unit/test_tem_crypto_random.rb +25 -0
- data/test/tem_unit/test_tem_emit.rb +23 -0
- data/test/tem_unit/test_tem_memory.rb +48 -0
- data/test/tem_unit/test_tem_memory_compare.rb +65 -0
- data/test/tem_unit/test_tem_output.rb +32 -0
- data/test/tem_unit/test_tem_yaml_secpack.rb +47 -0
- data/test/test_driver.rb +108 -0
- data/test/test_exceptions.rb +35 -0
- data/test/transport/test_auto_configurator.rb +114 -0
- data/test/transport/test_java_card_mixin.rb +90 -0
- data/test/transport/test_jcop_remote.rb +82 -0
- data/timings/blank_bound_secpack.rb +18 -0
- data/timings/blank_sec.rb +14 -0
- data/timings/devchip_decrypt.rb +9 -0
- data/timings/post_buffer.rb +10 -0
- data/timings/simple_apdu.rb +5 -0
- data/timings/timings.rb +64 -0
- data/timings/vm_perf.rb +140 -0
- data/timings/vm_perf_bound.rb +141 -0
- 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
|