osp 0.1.0.pre.dev.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/osp/host.rb ADDED
@@ -0,0 +1,146 @@
1
+
2
+ module TheFox
3
+ module OSP
4
+
5
+ class Host
6
+
7
+ # attr_accessor :osp
8
+ # attr_accessor :version
9
+ attr_accessor :created_at
10
+ attr_accessor :updated_at
11
+ # attr_accessor :name
12
+ # attr_accessor :generation
13
+ # attr_accessor :length
14
+ # attr_accessor :symbols
15
+ # attr_accessor :hashes
16
+ # attr_accessor :password
17
+
18
+ def initialize(osp = nil)
19
+ @osp = osp
20
+
21
+ @version = 1
22
+ @created_at = DateTime.now
23
+ @updated_at = DateTime.now
24
+
25
+ @name = nil
26
+ @generation = 1
27
+ @length = 16
28
+ @symbols = 1
29
+ @hashes = !@osp.nil? ? @osp.hashes : nil
30
+ @password = nil
31
+ end
32
+
33
+ def osp=(v)
34
+ raise ArgumentError, 'Wrong type.' if !v.is_a?(TheFox::OSP::OSP)
35
+
36
+ @osp = v
37
+ end
38
+
39
+ def osp
40
+ @osp
41
+ end
42
+
43
+ def version=(v)
44
+ @version = v.to_i
45
+ end
46
+
47
+ def version
48
+ @version.to_i
49
+ end
50
+
51
+ # def created_at=(v)
52
+ # @created_at = v
53
+ # end
54
+
55
+ # def created_at
56
+ # @created_at
57
+ # end
58
+
59
+ def name=(v)
60
+ v = nil if v == ''
61
+ @name = v
62
+ end
63
+
64
+ def name
65
+ @name
66
+ end
67
+
68
+ def generation=(v)
69
+ @generation = v.to_i
70
+ end
71
+
72
+ def generation
73
+ @generation.to_i
74
+ end
75
+
76
+ def length=(v)
77
+ @length = v.to_i
78
+ end
79
+
80
+ def length
81
+ @length.to_i
82
+ end
83
+
84
+ def symbols=(v)
85
+ @symbols = v.to_i
86
+ end
87
+
88
+ def symbols
89
+ @symbols.to_i
90
+ end
91
+
92
+ def hashes=(v)
93
+ @hashes = v
94
+ end
95
+
96
+ def hashes
97
+ @hashes
98
+ end
99
+
100
+ def generate_password(regenerate = false)
101
+ if @password.nil? && !@osp.nil? || regenerate
102
+ # puts "host name: '#{@name}'"
103
+ @password = @osp.password(@name, @length, @generation, @symbols)
104
+ end
105
+ end
106
+
107
+ def password=(v)
108
+ @password = v
109
+ end
110
+
111
+ def password
112
+ generate_password
113
+ @password
114
+ end
115
+
116
+ def has_generated_password?
117
+ !@password.nil?
118
+ end
119
+
120
+ def to_h
121
+ {
122
+ 'version' => @version,
123
+ 'created_at' => @created_at.to_s,
124
+
125
+ 'name' => @name,
126
+ 'generation' => @generation,
127
+ 'length' => @length,
128
+ 'symbols' => @symbols,
129
+ 'hashes' => @hashes,
130
+
131
+ #'password' => @password,
132
+ }
133
+ end
134
+
135
+ def self.from_h(host)
136
+ h = self.new
137
+ host.each do |k, v|
138
+ h.method("#{k}=").call(v)
139
+ end
140
+ h
141
+ end
142
+
143
+ end
144
+ end
145
+ end
146
+
data/lib/osp/osp.rb ADDED
@@ -0,0 +1,183 @@
1
+
2
+ require 'base64'
3
+ require 'openssl'
4
+ require 'msgpack'
5
+ require 'pp'
6
+
7
+ require 'thefox-ext'
8
+
9
+ module TheFox
10
+ module OSP
11
+
12
+ ID = 'TheFox-OSP'
13
+ HASHES_EXP = 24
14
+ HASHES_N = 2 ** HASHES_EXP
15
+ PASSWORD_MIN_SIZE = 8
16
+ PASSWORD_MAX_SIZE = 32
17
+ SYMBOLS = 1
18
+
19
+ class OSP
20
+
21
+ attr_accessor :hashes
22
+
23
+ def initialize(email, password, hashes = HASHES_N)
24
+ @email = email
25
+ @password = password
26
+ @hashes = hashes
27
+ @dk = nil
28
+ @password_callback_method = nil
29
+ end
30
+
31
+ def key_derivation
32
+ # puts "password: #{@password}"
33
+ # puts "email: #{@email}"
34
+ # puts "hashes: #{@hashes}"
35
+
36
+ @dk = OpenSSL::PKCS5.pbkdf2_hmac(@password, @email, @hashes, 64, OpenSSL::Digest::SHA512.new)
37
+ # pp @dk.length
38
+ # pp @dk.length * 8
39
+ # pp @dk.to_hex
40
+ end
41
+
42
+ def password(host_name, length = 16, generation = 1, symbols = 1)
43
+ raise ArgumentError, "'host_name' can't be '' or nil" if host_name.nil? || host_name == '' || !host_name
44
+
45
+ key_derivation if @dk.nil?
46
+
47
+ pw = nil
48
+ step = 0
49
+ while pw.nil?
50
+ raw = [ID, @email, host_name, generation, step]
51
+ # pp raw
52
+ data = raw.to_msgpack
53
+ hmac_p = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA512.new, @dk, data)
54
+ hmac_b64 = Base64.strict_encode64(hmac_p)
55
+ pw = hmac_b64 if is_ok_pw(hmac_b64)
56
+
57
+ # pp hmac_b64[0..16]
58
+ #sleep 0.1
59
+
60
+ @password_callback_method.call(step, hmac_b64) if !@password_callback_method.nil?
61
+ step += 1
62
+ end
63
+
64
+ if symbols > 0
65
+ sub_method = find_method_to_sub(pw)
66
+ #puts "sub_method = #{sub_method}"
67
+
68
+ _b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
69
+
70
+ indices = []
71
+ (0..PASSWORD_MIN_SIZE).each do |n|
72
+ c = pw[n]
73
+ #puts "#{n} = #{c}"
74
+ if c.method(sub_method).call
75
+ indices << n
76
+ if indices.count >= symbols
77
+ break
78
+ end
79
+ end
80
+ end
81
+
82
+ #pp indices
83
+
84
+ _map = "`~!@#$%^&*()-_+={}[]|;:,<>.?/"
85
+ _map_len = _map.length
86
+
87
+ last = 0
88
+ arr = []
89
+ indices.each do |index|
90
+ arr << pw[last...index]
91
+ c = pw[index]
92
+ i = _b64map.index(c)
93
+ x = _map[i % _map_len]
94
+ arr << x
95
+ last = index + 1
96
+
97
+ #puts "#{index} = '#{c}' '#{i}' '#{x}' #{last}"
98
+ end
99
+ arr << pw[last..-1]
100
+ #pp arr
101
+ pw = arr.join
102
+ end
103
+
104
+ #puts "length: #{length}"
105
+ pw[0...length]
106
+ end
107
+
108
+ def password_callback_method=(m)
109
+ @password_callback_method = m
110
+ end
111
+
112
+ private
113
+
114
+ def is_ok_pw(pw)
115
+ caps = 0
116
+ lowers = 0
117
+ digits = 0
118
+
119
+ (0...PASSWORD_MIN_SIZE).each do |n|
120
+ c = pw[n]
121
+
122
+ # puts "#{n} = #{c}"
123
+
124
+ if c.is_digit?
125
+ digits += 1
126
+ elsif c.is_upper?
127
+ caps += 1
128
+ elsif c.is_lower?
129
+ lowers += 1
130
+ else
131
+ return false
132
+ end
133
+ end
134
+
135
+ bad = lambda { |x| x == 0 || x > 5 }
136
+
137
+ if bad.call(caps) || bad.call(lowers) || bad.call(digits)
138
+ #puts 'return false'
139
+ return false
140
+ end
141
+
142
+ (PASSWORD_MIN_SIZE...PASSWORD_MAX_SIZE).each do |n|
143
+ if !pw[n].is_valid?
144
+ return false
145
+ end
146
+ end
147
+
148
+ true
149
+ end
150
+
151
+ def find_method_to_sub(pw)
152
+ caps = 0
153
+ lowers = 0
154
+ digits = 0
155
+
156
+ (0...PASSWORD_MIN_SIZE).each do |n|
157
+ c = pw[n]
158
+ if c.is_digit?
159
+ digits += 1
160
+ elsif c.is_upper?
161
+ caps += 1
162
+ elsif c.is_lower?
163
+ lowers += 1
164
+ end
165
+
166
+ #puts "#{n} = #{c} #{digits} #{caps} #{lowers}"
167
+ end
168
+
169
+ rv = ''
170
+ if lowers >= caps && lowers >= digits then
171
+ rv = 'is_lower?'
172
+ elsif digits > lowers && digits >= caps
173
+ rv = 'is_digit?'
174
+ else
175
+ rv = 'is_upper?'
176
+ end
177
+ #rv = 'is_upper?'
178
+ rv
179
+ end
180
+
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,9 @@
1
+
2
+ module TheFox
3
+ module OSP
4
+ RELEASE_ID = 0
5
+ VERSION = '0.1.0-dev.1'
6
+ DATE = '2015-12-17'
7
+ HOMEPAGE = 'https://github.com/TheFox/osp'
8
+ end
9
+ end
data/lib/osp.rb ADDED
@@ -0,0 +1,6 @@
1
+
2
+ require 'ext/string'
3
+
4
+ require 'osp/version'
5
+ require 'osp/osp'
6
+ require 'osp/host'
data/osp.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: UTF-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'osp/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'osp'
10
+ spec.version = TheFox::OSP::VERSION
11
+ spec.date = TheFox::OSP::DATE
12
+ spec.author = 'Christian Mayer'
13
+ spec.email = 'christian@fox21.at'
14
+
15
+ spec.summary = %q{One Shall Pass}
16
+ spec.description = %q{One Shall Pass}
17
+ spec.homepage = TheFox::OSP::HOMEPAGE
18
+ spec.license = 'GPL-3.0'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject{ |f| f.match(%r{^(test|spec|features)/}) }
21
+ spec.bindir = 'bin'
22
+ spec.executables = ['osp', 'catmp']
23
+ spec.require_paths = ['lib']
24
+ spec.required_ruby_version = '>=2.1.0'
25
+
26
+ #spec.requirements << ''
27
+
28
+ spec.add_development_dependency 'bundler', '~>1.10'
29
+ spec.add_development_dependency 'minitest', '~>5.8'
30
+
31
+ spec.add_dependency 'highline', '~>1.7'
32
+ spec.add_dependency 'rainbow', '~>2.0'
33
+ spec.add_dependency 'msgpack', '~>0.7'
34
+
35
+ spec.add_dependency 'thefox-ext', '~>1.2'
36
+ end
@@ -0,0 +1,10 @@
1
+ {
2
+ "folders":[
3
+ {
4
+ "path": ".",
5
+ "name": "OneShallPass",
6
+ "folder_exclude_patterns": [ ],
7
+ "file_exclude_patterns": [ ]
8
+ }
9
+ ]
10
+ }
data/tests/tc_host.rb ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'minitest/autorun'
5
+ require 'osp'
6
+
7
+ class TestHost < MiniTest::Test
8
+ def test_base
9
+ osp = TheFox::OSP::Host.new
10
+
11
+ assert_equal('TheFox::OSP::Host', osp.class.to_s)
12
+ end
13
+
14
+ def test_initialize
15
+ host = TheFox::OSP::Host.new
16
+
17
+ assert_equal(nil, host.osp)
18
+ assert_equal(1, host.version)
19
+ # assert_equal(, host.created_at)
20
+ # assert_equal(, host.updated_at)
21
+ assert_equal(nil, host.name)
22
+ assert_equal(1, host.generation)
23
+ assert_equal(16, host.length)
24
+ assert_equal(1, host.symbols)
25
+ assert_equal(nil, host.hashes)
26
+ assert_equal(nil, host.password)
27
+ end
28
+
29
+ def test_version
30
+ host = TheFox::OSP::Host.new
31
+
32
+ host.version = 2
33
+ assert_equal(2, host.version)
34
+
35
+ host.version = '3'
36
+ assert_equal(3, host.version)
37
+ end
38
+
39
+ def test_created_at
40
+ host = TheFox::OSP::Host.new
41
+
42
+ now = DateTime.now
43
+ host.created_at = now
44
+ assert_equal(now, host.created_at)
45
+ end
46
+
47
+ def test_updated_at
48
+ host = TheFox::OSP::Host.new
49
+
50
+ now = DateTime.now
51
+ host.updated_at = now
52
+ assert_equal(now, host.updated_at)
53
+ end
54
+
55
+ def test_name
56
+ host = TheFox::OSP::Host.new
57
+
58
+ host.name = ''
59
+ assert_equal(nil, host.name)
60
+
61
+ host.name = 'host1'
62
+ assert_equal('host1', host.name)
63
+ end
64
+
65
+ def test_generation
66
+ host = TheFox::OSP::Host.new
67
+
68
+ host.generation = 2
69
+ assert_equal(2, host.generation)
70
+
71
+ host.generation = '3'
72
+ assert_equal(3, host.generation)
73
+ end
74
+
75
+ def test_length
76
+ host = TheFox::OSP::Host.new
77
+
78
+ host.length = 2
79
+ assert_equal(2, host.length)
80
+
81
+ host.length = '3'
82
+ assert_equal(3, host.length)
83
+ end
84
+
85
+ def test_symbols
86
+ host = TheFox::OSP::Host.new
87
+
88
+ host.symbols = 2
89
+ assert_equal(2, host.symbols)
90
+
91
+ host.symbols = '3'
92
+ assert_equal(3, host.symbols)
93
+ end
94
+
95
+ def test_hashes
96
+ host = TheFox::OSP::Host.new
97
+
98
+ host.hashes = nil
99
+ assert_equal(nil, host.hashes)
100
+
101
+ host.hashes = 2
102
+ assert_equal(2, host.hashes)
103
+
104
+ host.hashes = '3'
105
+ assert_equal('3', host.hashes)
106
+ end
107
+
108
+ def test_password
109
+ host = TheFox::OSP::Host.new
110
+
111
+ host.password = nil
112
+ assert_equal(nil, host.password)
113
+
114
+ host.password = 2
115
+ assert_equal(2, host.password)
116
+
117
+ host.password = '3'
118
+ assert_equal('3', host.password)
119
+ end
120
+ end
data/tests/tc_osp.rb ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'minitest/autorun'
5
+ require 'osp'
6
+
7
+ class TestOsp < MiniTest::Test
8
+ def test_that_it_has_a_version_number
9
+ refute_nil ::TheFox::OSP::VERSION
10
+ end
11
+
12
+ def test_base
13
+ osp = TheFox::OSP::OSP.new('example@example.com', 'test1', 2 ** 10)
14
+
15
+ assert_equal('TheFox::OSP::OSP', osp.class.to_s)
16
+ end
17
+
18
+ def test_hashes
19
+ osp = TheFox::OSP::OSP.new('example@example.com', 'test1', 2 ** 10)
20
+ assert_equal(2 ** 10, osp.hashes)
21
+
22
+ osp = TheFox::OSP::OSP.new('example@example.com', 'test1', 2 ** 20)
23
+ assert_equal(2 ** 20, osp.hashes)
24
+ end
25
+
26
+ # def test_password1
27
+ # #ID = 'OneShallPass v2.0'
28
+ # osp = TheFox::OSP::OSP.new('example@example.com', 'test1', 2 ** 10)
29
+ # osp.key_derivation
30
+
31
+ # assert_equal('TA24hNn0', osp.password('host1', 8, 1, 0))
32
+ # assert_equal('TA>4hNn0', osp.password('host1', 8, 1, 1))
33
+ # assert_equal('TA>?hNn0', osp.password('host1', 8, 1, 2))
34
+ # assert_equal('TA>?hNn,', osp.password('host1', 8, 1, 3))
35
+
36
+ # assert_equal('DHKzN7uY', osp.password('host2', 8, 1, 0))
37
+ # assert_equal('@HKzN7uY', osp.password('host2', 8, 1, 1))
38
+ # assert_equal('@^KzN7uY', osp.password('host2', 8, 1, 2))
39
+ # assert_equal('@^(zN7uY', osp.password('host2', 8, 1, 3))
40
+
41
+ # assert_equal('Qf4lvgE7', osp.password('host3', 8, 1, 0))
42
+ # assert_equal('Q!4lvgE7', osp.password('host3', 8, 1, 1))
43
+ # assert_equal('Q!4&vgE7', osp.password('host3', 8, 1, 2))
44
+ # assert_equal('Q!4&[gE7', osp.password('host3', 8, 1, 3))
45
+
46
+ # assert_equal('DHKzN7uYUBc3l0wi', osp.password('host2', 16, 1, 0))
47
+ # assert_equal('@HKzN7uYUBc3l0wi', osp.password('host2', 16, 1, 1))
48
+ # assert_equal('@^KzN7uYUBc3l0wi', osp.password('host2', 16, 1, 2))
49
+ # assert_equal('@^(zN7uYUBc3l0wi', osp.password('host2', 16, 1, 3))
50
+
51
+ # assert_equal('wU0t38KE4tDQb3c0', osp.password('host2', 16, 2, 0))
52
+ # assert_equal('wU,t38KE4tDQb3c0', osp.password('host2', 16, 2, 1))
53
+ # assert_equal('wU,t.8KE4tDQb3c0', osp.password('host2', 16, 2, 2))
54
+ # assert_equal('wU,t.!KE4tDQb3c0', osp.password('host2', 16, 2, 3))
55
+
56
+ # assert_equal('OezcZk881M3Jxw9Z', osp.password('host2', 16, 3, 0))
57
+ # assert_equal('O~zcZk881M3Jxw9Z', osp.password('host2', 16, 3, 1))
58
+ # assert_equal('O~:cZk881M3Jxw9Z', osp.password('host2', 16, 3, 2))
59
+ # assert_equal('O~:/Zk881M3Jxw9Z', osp.password('host2', 16, 3, 3))
60
+ # end
61
+
62
+ # def test_password2
63
+ # #PASSWORD_MIN_SIZE = 8
64
+ # #PASSWORD_MAX_SIZE = 16
65
+
66
+ # osp = TheFox::OSP::OSP.new('example@example.com', 'test1', 2 ** 10)
67
+ # osp.key_derivation
68
+
69
+ # assert_equal('cXyE2Dq1', osp.password('host1', 8, 1, 0))
70
+ # assert_equal('/XyE2Dq1', osp.password('host1', 8, 1, 1))
71
+ # assert_equal('/X;E2Dq1', osp.password('host1', 8, 1, 2))
72
+ # assert_equal('/X;E2D_1', osp.password('host1', 8, 1, 3))
73
+
74
+ # assert_equal('kT455AIH', osp.password('host2', 8, 1, 0))
75
+ # assert_equal('k]455AIH', osp.password('host2', 8, 1, 1))
76
+ # assert_equal('k]455`IH', osp.password('host2', 8, 1, 2))
77
+ # assert_equal('k]455`&H', osp.password('host2', 8, 1, 3))
78
+
79
+ # assert_equal('RN1vFCCx', osp.password('host3', 8, 1, 0))
80
+ # assert_equal('}N1vFCCx', osp.password('host3', 8, 1, 1))
81
+ # assert_equal('}_1vFCCx', osp.password('host3', 8, 1, 2))
82
+ # assert_equal('}_1v$CCx', osp.password('host3', 8, 1, 3))
83
+
84
+ # assert_equal('kT455AIHkWRmXRN9', osp.password('host2', 16, 1, 0))
85
+ # assert_equal('k]455AIHkWRmXRN9', osp.password('host2', 16, 1, 1))
86
+ # assert_equal('k]455`IHkWRmXRN9', osp.password('host2', 16, 1, 2))
87
+ # assert_equal('k]455`&HkWRmXRN9', osp.password('host2', 16, 1, 3))
88
+
89
+ # assert_equal('SDBNsS7tMF4HjpPz', osp.password('host2', 16, 2, 0))
90
+ # assert_equal('[DBNsS7tMF4HjpPz', osp.password('host2', 16, 2, 1))
91
+ # assert_equal('[@BNsS7tMF4HjpPz', osp.password('host2', 16, 2, 2))
92
+ # assert_equal('[@~NsS7tMF4HjpPz', osp.password('host2', 16, 2, 3))
93
+
94
+ # assert_equal('GZ8SAox0xBvFM9QL', osp.password('host2', 16, 3, 0))
95
+ # assert_equal('%Z8SAox0xBvFM9QL', osp.password('host2', 16, 3, 1))
96
+ # assert_equal('%>8SAox0xBvFM9QL', osp.password('host2', 16, 3, 2))
97
+ # assert_equal('%>8[Aox0xBvFM9QL', osp.password('host2', 16, 3, 3))
98
+ # end
99
+
100
+ def test_password3
101
+ #PASSWORD_MIN_SIZE = 8
102
+ #PASSWORD_MAX_SIZE = 32
103
+
104
+ osp = TheFox::OSP::OSP.new('example@example.com', 'test1', 2 ** 10)
105
+ osp.key_derivation
106
+
107
+ assert_equal('cXyE2Dq1', osp.password('host1', 8, 1, 0))
108
+ assert_equal('/XyE2Dq1', osp.password('host1', 8, 1, 1))
109
+ assert_equal('/X;E2Dq1', osp.password('host1', 8, 1, 2))
110
+ assert_equal('/X;E2D_1', osp.password('host1', 8, 1, 3))
111
+
112
+ assert_equal('ar7JQegF', osp.password('host2', 8, 1, 0))
113
+ assert_equal('.r7JQegF', osp.password('host2', 8, 1, 1))
114
+ assert_equal('.+7JQegF', osp.password('host2', 8, 1, 2))
115
+ assert_equal('.+7JQ~gF', osp.password('host2', 8, 1, 3))
116
+
117
+ assert_equal('RN1vFCCx', osp.password('host3', 8, 1, 0))
118
+ assert_equal('}N1vFCCx', osp.password('host3', 8, 1, 1))
119
+ assert_equal('}_1vFCCx', osp.password('host3', 8, 1, 2))
120
+ assert_equal('}_1v$CCx', osp.password('host3', 8, 1, 3))
121
+
122
+ assert_equal('ar7JQegFyBk737gQ', osp.password('host2', 16, 1, 0))
123
+ assert_equal('.r7JQegFyBk737gQ', osp.password('host2', 16, 1, 1))
124
+ assert_equal('.+7JQegFyBk737gQ', osp.password('host2', 16, 1, 2))
125
+ assert_equal('.+7JQ~gFyBk737gQ', osp.password('host2', 16, 1, 3))
126
+
127
+ assert_equal('E3uJvG0rGFKwgpHD', osp.password('host2', 16, 2, 0))
128
+ assert_equal('E3}JvG0rGFKwgpHD', osp.password('host2', 16, 2, 1))
129
+ assert_equal('E3}J[G0rGFKwgpHD', osp.password('host2', 16, 2, 2))
130
+ assert_equal('E3}J[G0+GFKwgpHD', osp.password('host2', 16, 2, 3))
131
+
132
+ assert_equal('7p51OI3QRAKGUcUc', osp.password('host2', 16, 3, 0))
133
+ assert_equal('~p51OI3QRAKGUcUc', osp.password('host2', 16, 3, 1))
134
+ assert_equal('~p/1OI3QRAKGUcUc', osp.password('host2', 16, 3, 2))
135
+ assert_equal('~p/<OI3QRAKGUcUc', osp.password('host2', 16, 3, 3))
136
+ end
137
+ end
data/tests/ts_all.rb ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'tc_osp'
4
+ require_relative 'tc_host'