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.
- 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
|