bcrypt-ruby 2.1.3-x86-mingw32

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.

Potentially problematic release.


This version of bcrypt-ruby might be problematic. Click here for more details.

@@ -0,0 +1,17 @@
1
+ if RUBY_PLATFORM == "java"
2
+ # Don't do anything when run in JRuby; this allows gem installation to pass.
3
+ # We need to write a dummy Makefile so that RubyGems doesn't think compilation
4
+ # failed.
5
+ File.open('Makefile', 'w') do |f|
6
+ f.puts "all:"
7
+ f.puts "\t@true"
8
+ f.puts "install:"
9
+ f.puts "\t@true"
10
+ end
11
+ exit 0
12
+ else
13
+ require "mkmf"
14
+ dir_config("bcrypt_ext")
15
+ CONFIG['CC'] << " -Wall "
16
+ create_makefile("bcrypt_ext")
17
+ end
data/lib/bcrypt.rb ADDED
@@ -0,0 +1,190 @@
1
+ # A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
2
+
3
+ if RUBY_PLATFORM == "java"
4
+ require 'java'
5
+ else
6
+ require "openssl"
7
+ end
8
+
9
+ require 'bcrypt_ext'
10
+
11
+ # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
12
+ # hashing passwords.
13
+ module BCrypt
14
+ module Errors
15
+ class InvalidSalt < StandardError; end # The salt parameter provided to bcrypt() is invalid.
16
+ class InvalidHash < StandardError; end # The hash parameter provided to bcrypt() is invalid.
17
+ class InvalidCost < StandardError; end # The cost parameter provided to bcrypt() is invalid.
18
+ class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid.
19
+ end
20
+
21
+ # A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
22
+ class Engine
23
+ # The default computational expense parameter.
24
+ DEFAULT_COST = 10
25
+ # The minimum cost supported by the algorithm.
26
+ MIN_COST = 4
27
+ # Maximum possible size of bcrypt() salts.
28
+ MAX_SALT_LENGTH = 16
29
+
30
+ if RUBY_PLATFORM != "java"
31
+ # C-level routines which, if they don't get the right input, will crash the
32
+ # hell out of the Ruby process.
33
+ private_class_method :__bc_salt
34
+ private_class_method :__bc_crypt
35
+ end
36
+
37
+ # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
38
+ # a bcrypt() password hash.
39
+ def self.hash_secret(secret, salt, cost = nil)
40
+ if valid_secret?(secret)
41
+ if valid_salt?(salt)
42
+ if cost.nil?
43
+ cost = autodetect_cost(salt)
44
+ end
45
+
46
+ if RUBY_PLATFORM == "java"
47
+ Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
48
+ else
49
+ __bc_crypt(secret.to_s, salt, cost)
50
+ end
51
+ else
52
+ raise Errors::InvalidSalt.new("invalid salt")
53
+ end
54
+ else
55
+ raise Errors::InvalidSecret.new("invalid secret")
56
+ end
57
+ end
58
+
59
+ # Generates a random salt with a given computational cost.
60
+ def self.generate_salt(cost = DEFAULT_COST)
61
+ cost = cost.to_i
62
+ if cost > 0
63
+ if cost < MIN_COST
64
+ cost = MIN_COST
65
+ end
66
+ if RUBY_PLATFORM == "java"
67
+ Java.bcrypt_jruby.BCrypt.gensalt(cost)
68
+ else
69
+ __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
70
+ end
71
+ else
72
+ raise Errors::InvalidCost.new("cost must be numeric and > 0")
73
+ end
74
+ end
75
+
76
+ # Returns true if +salt+ is a valid bcrypt() salt, false if not.
77
+ def self.valid_salt?(salt)
78
+ salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
79
+ end
80
+
81
+ # Returns true if +secret+ is a valid bcrypt() secret, false if not.
82
+ def self.valid_secret?(secret)
83
+ secret.respond_to?(:to_s)
84
+ end
85
+
86
+ # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
87
+ #
88
+ # Example:
89
+ #
90
+ # BCrypt.calibrate(200) #=> 10
91
+ # BCrypt.calibrate(1000) #=> 12
92
+ #
93
+ # # should take less than 200ms
94
+ # BCrypt::Password.create("woo", :cost => 10)
95
+ #
96
+ # # should take less than 1000ms
97
+ # BCrypt::Password.create("woo", :cost => 12)
98
+ def self.calibrate(upper_time_limit_in_ms)
99
+ 40.times do |i|
100
+ start_time = Time.now
101
+ Password.create("testing testing", :cost => i+1)
102
+ end_time = Time.now - start_time
103
+ return i if end_time * 1_000 > upper_time_limit_in_ms
104
+ end
105
+ end
106
+
107
+ # Autodetects the cost from the salt string.
108
+ def self.autodetect_cost(salt)
109
+ salt[4..5].to_i
110
+ end
111
+ end
112
+
113
+ # A password management class which allows you to safely store users' passwords and compare them.
114
+ #
115
+ # Example usage:
116
+ #
117
+ # include BCrypt
118
+ #
119
+ # # hash a user's password
120
+ # @password = Password.create("my grand secret")
121
+ # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
122
+ #
123
+ # # store it safely
124
+ # @user.update_attribute(:password, @password)
125
+ #
126
+ # # read it back
127
+ # @user.reload!
128
+ # @db_password = Password.new(@user.password)
129
+ #
130
+ # # compare it after retrieval
131
+ # @db_password == "my grand secret" #=> true
132
+ # @db_password == "a paltry guess" #=> false
133
+ #
134
+ class Password < String
135
+ # The hash portion of the stored password hash.
136
+ attr_reader :checksum
137
+ # The salt of the store password hash (including version and cost).
138
+ attr_reader :salt
139
+ # The version of the bcrypt() algorithm used to create the hash.
140
+ attr_reader :version
141
+ # The cost factor used to create the hash.
142
+ attr_reader :cost
143
+
144
+ class << self
145
+ # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
146
+ # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
147
+ # 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
148
+ # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
149
+ # users' passwords.
150
+ #
151
+ # Example:
152
+ #
153
+ # @password = BCrypt::Password.create("my secret", :cost => 13)
154
+ def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
155
+ Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
156
+ end
157
+ end
158
+
159
+ # Initializes a BCrypt::Password instance with the data from a stored hash.
160
+ def initialize(raw_hash)
161
+ if valid_hash?(raw_hash)
162
+ self.replace(raw_hash)
163
+ @version, @cost, @salt, @checksum = split_hash(self)
164
+ else
165
+ raise Errors::InvalidHash.new("invalid hash")
166
+ end
167
+ end
168
+
169
+ # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
170
+ def ==(secret)
171
+ super(BCrypt::Engine.hash_secret(secret, @salt))
172
+ end
173
+ alias_method :is_password?, :==
174
+
175
+ private
176
+ # Returns true if +h+ is a valid hash.
177
+ def valid_hash?(h)
178
+ h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
179
+ end
180
+
181
+ # call-seq:
182
+ # split_hash(raw_hash) -> version, cost, salt, hash
183
+ #
184
+ # Splits +h+ into version, cost, salt, and hash and returns them in that order.
185
+ def split_hash(h)
186
+ b, v, c, mash = h.split('$')
187
+ return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
188
+ end
189
+ end
190
+ end
data/lib/bcrypt_ext.rb ADDED
@@ -0,0 +1,2 @@
1
+ RUBY_VERSION =~ /(\d+.\d+)/
2
+ require "#{$1}/bcrypt_ext"
@@ -0,0 +1,175 @@
1
+ // Copyright (c) 2006 Damien Miller <djm@mindrot.org>
2
+ //
3
+ // Permission to use, copy, modify, and distribute this software for any
4
+ // purpose with or without fee is hereby granted, provided that the above
5
+ // copyright notice and this permission notice appear in all copies.
6
+ //
7
+ // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ import junit.framework.TestCase;
16
+
17
+ /**
18
+ * JUnit unit tests for BCrypt routines
19
+ * @author Damien Miller
20
+ * @version 0.2
21
+ */
22
+ public class TestBCrypt extends TestCase {
23
+ String test_vectors[][] = {
24
+ { "",
25
+ "$2a$06$DCq7YPn5Rq63x1Lad4cll.",
26
+ "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
27
+ { "",
28
+ "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
29
+ "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" },
30
+ { "",
31
+ "$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
32
+ "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
33
+ { "",
34
+ "$2a$12$k42ZFHFWqBp3vWli.nIn8u",
35
+ "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
36
+ { "a",
37
+ "$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
38
+ "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
39
+ { "a",
40
+ "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
41
+ "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." },
42
+ { "a",
43
+ "$2a$10$k87L/MF28Q673VKh8/cPi.",
44
+ "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
45
+ { "a",
46
+ "$2a$12$8NJH3LsPrANStV6XtBakCe",
47
+ "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
48
+ { "abc",
49
+ "$2a$06$If6bvum7DFjUnE9p2uDeDu",
50
+ "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
51
+ { "abc",
52
+ "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
53
+ "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" },
54
+ { "abc",
55
+ "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
56
+ "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
57
+ { "abc",
58
+ "$2a$12$EXRkfkdmXn2gzds2SSitu.",
59
+ "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
60
+ { "abcdefghijklmnopqrstuvwxyz",
61
+ "$2a$06$.rCVZVOThsIa97pEDOxvGu",
62
+ "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
63
+ { "abcdefghijklmnopqrstuvwxyz",
64
+ "$2a$08$aTsUwsyowQuzRrDqFflhge",
65
+ "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." },
66
+ { "abcdefghijklmnopqrstuvwxyz",
67
+ "$2a$10$fVH8e28OQRj9tqiDXs1e1u",
68
+ "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
69
+ { "abcdefghijklmnopqrstuvwxyz",
70
+ "$2a$12$D4G5f18o7aMMfwasBL7Gpu",
71
+ "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
72
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
73
+ "$2a$06$fPIsBO8qRqkjj273rfaOI.",
74
+ "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
75
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
76
+ "$2a$08$Eq2r4G/76Wv39MzSX262hu",
77
+ "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" },
78
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
79
+ "$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
80
+ "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
81
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
82
+ "$2a$12$WApznUOJfkEGSmYRfnkrPO",
83
+ "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
84
+ };
85
+
86
+ /**
87
+ * Entry point for unit tests
88
+ * @param args unused
89
+ */
90
+ public static void main(String[] args) {
91
+ junit.textui.TestRunner.run(TestBCrypt.class);
92
+ }
93
+
94
+ /**
95
+ * Test method for 'BCrypt.hashpw(String, String)'
96
+ */
97
+ public void testHashpw() {
98
+ System.out.print("BCrypt.hashpw(): ");
99
+ for (int i = 0; i < test_vectors.length; i++) {
100
+ String plain = test_vectors[i][0];
101
+ String salt = test_vectors[i][1];
102
+ String expected = test_vectors[i][2];
103
+ String hashed = BCrypt.hashpw(plain, salt);
104
+ assertEquals(hashed, expected);
105
+ System.out.print(".");
106
+ }
107
+ System.out.println("");
108
+ }
109
+
110
+ /**
111
+ * Test method for 'BCrypt.gensalt(int)'
112
+ */
113
+ public void testGensaltInt() {
114
+ System.out.print("BCrypt.gensalt(log_rounds):");
115
+ for (int i = 4; i <= 12; i++) {
116
+ System.out.print(" " + Integer.toString(i) + ":");
117
+ for (int j = 0; j < test_vectors.length; j += 4) {
118
+ String plain = test_vectors[j][0];
119
+ String salt = BCrypt.gensalt(i);
120
+ String hashed1 = BCrypt.hashpw(plain, salt);
121
+ String hashed2 = BCrypt.hashpw(plain, hashed1);
122
+ assertEquals(hashed1, hashed2);
123
+ System.out.print(".");
124
+ }
125
+ }
126
+ System.out.println("");
127
+ }
128
+
129
+ /**
130
+ * Test method for 'BCrypt.gensalt()'
131
+ */
132
+ public void testGensalt() {
133
+ System.out.print("BCrypt.gensalt(): ");
134
+ for (int i = 0; i < test_vectors.length; i += 4) {
135
+ String plain = test_vectors[i][0];
136
+ String salt = BCrypt.gensalt();
137
+ String hashed1 = BCrypt.hashpw(plain, salt);
138
+ String hashed2 = BCrypt.hashpw(plain, hashed1);
139
+ assertEquals(hashed1, hashed2);
140
+ System.out.print(".");
141
+ }
142
+ System.out.println("");
143
+ }
144
+
145
+ /**
146
+ * Test method for 'BCrypt.checkpw(String, String)'
147
+ * expecting success
148
+ */
149
+ public void testCheckpw_success() {
150
+ System.out.print("BCrypt.checkpw w/ good passwords: ");
151
+ for (int i = 0; i < test_vectors.length; i++) {
152
+ String plain = test_vectors[i][0];
153
+ String expected = test_vectors[i][2];
154
+ assertTrue(BCrypt.checkpw(plain, expected));
155
+ System.out.print(".");
156
+ }
157
+ System.out.println("");
158
+ }
159
+
160
+ /**
161
+ * Test method for 'BCrypt.checkpw(String, String)'
162
+ * expecting failure
163
+ */
164
+ public void testCheckpw_failure() {
165
+ System.out.print("BCrypt.checkpw w/ bad passwords: ");
166
+ for (int i = 0; i < test_vectors.length; i++) {
167
+ int broken_index = (i + 4) % test_vectors.length;
168
+ String plain = test_vectors[i][0];
169
+ String expected = test_vectors[broken_index][2];
170
+ assertFalse(BCrypt.checkpw(plain, expected));
171
+ System.out.print(".");
172
+ }
173
+ System.out.println("");
174
+ }
175
+ }
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "The BCrypt engine" do
4
+ specify "should calculate the optimal cost factor to fit in a specific time" do
5
+ first = BCrypt::Engine.calibrate(100)
6
+ second = BCrypt::Engine.calibrate(400)
7
+ second.should > first
8
+ end
9
+ end
10
+
11
+ describe "Generating BCrypt salts" do
12
+
13
+ specify "should produce strings" do
14
+ BCrypt::Engine.generate_salt.should be_an_instance_of(String)
15
+ end
16
+
17
+ specify "should produce random data" do
18
+ BCrypt::Engine.generate_salt.should_not equal(BCrypt::Engine.generate_salt)
19
+ end
20
+
21
+ specify "should raise a InvalidCostError if the cost parameter isn't numeric" do
22
+ lambda { BCrypt::Engine.generate_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost)
23
+ end
24
+
25
+ specify "should raise a InvalidCostError if the cost parameter isn't greater than 0" do
26
+ lambda { BCrypt::Engine.generate_salt(-1) }.should raise_error(BCrypt::Errors::InvalidCost)
27
+ end
28
+ end
29
+
30
+ describe "Autodetecting of salt cost" do
31
+
32
+ specify "should work" do
33
+ BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou").should == 8
34
+ BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu").should == 5
35
+ BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.").should == 13
36
+ end
37
+
38
+ end
39
+
40
+ describe "Generating BCrypt hashes" do
41
+
42
+ class MyInvalidSecret
43
+ undef to_s
44
+ end
45
+
46
+ before :each do
47
+ @salt = BCrypt::Engine.generate_salt(4)
48
+ @password = "woo"
49
+ end
50
+
51
+ specify "should produce a string" do
52
+ BCrypt::Engine.hash_secret(@password, @salt).should be_an_instance_of(String)
53
+ end
54
+
55
+ specify "should raise an InvalidSalt error if the salt is invalid" do
56
+ lambda { BCrypt::Engine.hash_secret(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt)
57
+ end
58
+
59
+ specify "should raise an InvalidSecret error if the secret is invalid" do
60
+ lambda { BCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.should raise_error(BCrypt::Errors::InvalidSecret)
61
+ lambda { BCrypt::Engine.hash_secret(nil, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret)
62
+ lambda { BCrypt::Engine.hash_secret(false, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret)
63
+ end
64
+
65
+ specify "should call #to_s on the secret and use the return value as the actual secret data" do
66
+ BCrypt::Engine.hash_secret(false, @salt).should == BCrypt::Engine.hash_secret("false", @salt)
67
+ end
68
+
69
+ specify "should be interoperable with other implementations" do
70
+ # test vectors from the OpenWall implementation <http://www.openwall.com/crypt/>
71
+ test_vectors = [
72
+ ["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"],
73
+ ["U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"],
74
+ ["U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"],
75
+ ["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"],
76
+ ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"]
77
+ ]
78
+ for secret, salt, test_vector in test_vectors
79
+ BCrypt::Engine.hash_secret(secret, salt).should eql(test_vector)
80
+ end
81
+ end
82
+ end