gabe-uuid 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Gabriel Boyer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,13 @@
1
+ UUID
2
+ ====
3
+
4
+ A simple library for generating/using Universally Unique Identifiers, as defined
5
+ in RFC 4122 (http://www.ietf.org/rfc/rfc4122.txt).
6
+
7
+ This library generates version 4 UUIDs, which are based on random bytes read from
8
+ SecureRandom (included for Ruby <1.8.7).
9
+
10
+ Author
11
+ ------
12
+
13
+ Gabriel Boyer (gboyer@gmail.com)
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require "rake/gempackagetask"
2
+ require "spec/rake/spectask"
3
+
4
+ task :default => :spec
5
+
6
+ desc "Run all specs"
7
+ Spec::Rake::SpecTask.new("spec") do |task|
8
+ task.spec_opts = ["--format", "specdoc", "--colour"]
9
+ task.spec_files = ["spec/**/*_spec.rb"]
10
+ end
11
+
12
+ spec = Gem::Specification.new do |s|
13
+ s.name = 'uuid'
14
+ s.version = '0.3'
15
+ s.platform = Gem::Platform::RUBY
16
+ s.author = 'Gabriel Boyer'
17
+ s.email = 'gboyer@gmail.com'
18
+ s.homepage = 'http://github.com/gabe/uuid/'
19
+ s.summary = 'Simple UUID implementation'
20
+ s.description = 'Simple UUID implementation, as per RFC 4122'
21
+ s.require_path = "lib"
22
+ s.files = %w[ LICENSE README.markdown Rakefile
23
+ spec/spec_helper.rb spec/uuid_spec.rb
24
+ lib/uuid.rb lib/compat/securerandom.rb ]
25
+ s.required_ruby_version = ">= 1.8.6"
26
+ end
27
+
28
+ Rake::GemPackageTask.new(spec) do |package|
29
+ package.gem_spec = spec
30
+ end
@@ -0,0 +1,182 @@
1
+ # = Secure random number generator interface.
2
+ #
3
+ # This library is an interface for secure random number generator which is
4
+ # suitable for generating session key in HTTP cookies, etc.
5
+ #
6
+ # It supports following secure random number generators.
7
+ #
8
+ # * openssl
9
+ # * /dev/urandom
10
+ # * Win32
11
+ #
12
+ # == Example
13
+ #
14
+ # # random hexadecimal string.
15
+ # p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
16
+ # p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
17
+ # p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
18
+ # p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
19
+ # p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
20
+ # ...
21
+ #
22
+ # # random base64 string.
23
+ # p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
24
+ # p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
25
+ # p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
26
+ # p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
27
+ # p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
28
+ # p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
29
+ # ...
30
+ #
31
+ # # random binary string.
32
+ # p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
33
+ # p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
34
+ # ...
35
+
36
+ begin
37
+ require 'openssl'
38
+ rescue LoadError
39
+ end
40
+
41
+ module SecureRandom
42
+ # SecureRandom.random_bytes generates a random binary string.
43
+ #
44
+ # The argument n specifies the length of the result string.
45
+ #
46
+ # If n is not specified, 16 is assumed.
47
+ # It may be larger in future.
48
+ #
49
+ # If secure random number generator is not available,
50
+ # NotImplementedError is raised.
51
+ def self.random_bytes(n=nil)
52
+ n ||= 16
53
+
54
+ if defined? OpenSSL::Random
55
+ return OpenSSL::Random.random_bytes(n)
56
+ end
57
+
58
+ if !defined?(@has_urandom) || @has_urandom
59
+ flags = File::RDONLY
60
+ flags |= File::NONBLOCK if defined? File::NONBLOCK
61
+ flags |= File::NOCTTY if defined? File::NOCTTY
62
+ flags |= File::NOFOLLOW if defined? File::NOFOLLOW
63
+ begin
64
+ File.open("/dev/urandom", flags) {|f|
65
+ unless f.stat.chardev?
66
+ raise Errno::ENOENT
67
+ end
68
+ @has_urandom = true
69
+ ret = f.readpartial(n)
70
+ if ret.length != n
71
+ raise NotImplementedError, "Unexpected partial read from random device"
72
+ end
73
+ return ret
74
+ }
75
+ rescue Errno::ENOENT
76
+ @has_urandom = false
77
+ end
78
+ end
79
+
80
+ if !defined?(@has_win32)
81
+ begin
82
+ require 'Win32API'
83
+
84
+ crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
85
+ @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
86
+
87
+ hProvStr = " " * 4
88
+ prov_rsa_full = 1
89
+ crypt_verifycontext = 0xF0000000
90
+
91
+ if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
92
+ raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
93
+ end
94
+ @hProv, = hProvStr.unpack('L')
95
+
96
+ @has_win32 = true
97
+ rescue LoadError
98
+ @has_win32 = false
99
+ end
100
+ end
101
+ if @has_win32
102
+ bytes = " " * n
103
+ if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
104
+ raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
105
+ end
106
+ return bytes
107
+ end
108
+
109
+ raise NotImplementedError, "No random device"
110
+ end
111
+
112
+ # SecureRandom.hex generates a random hex string.
113
+ #
114
+ # The argument n specifies the length of the random length.
115
+ # The length of the result string is twice of n.
116
+ #
117
+ # If n is not specified, 16 is assumed.
118
+ # It may be larger in future.
119
+ #
120
+ # If secure random number generator is not available,
121
+ # NotImplementedError is raised.
122
+ def self.hex(n=nil)
123
+ random_bytes(n).unpack("H*")[0]
124
+ end
125
+
126
+ # SecureRandom.base64 generates a random base64 string.
127
+ #
128
+ # The argument n specifies the length of the random length.
129
+ # The length of the result string is about 4/3 of n.
130
+ #
131
+ # If n is not specified, 16 is assumed.
132
+ # It may be larger in future.
133
+ #
134
+ # If secure random number generator is not available,
135
+ # NotImplementedError is raised.
136
+ def self.base64(n=nil)
137
+ [random_bytes(n)].pack("m*").delete("\n")
138
+ end
139
+
140
+ # SecureRandom.random_number generates a random number.
141
+ #
142
+ # If an positive integer is given as n,
143
+ # SecureRandom.random_number returns an integer:
144
+ # 0 <= SecureRandom.random_number(n) < n.
145
+ #
146
+ # If 0 is given or an argument is not given,
147
+ # SecureRandom.random_number returns an float:
148
+ # 0.0 <= SecureRandom.random_number() < 1.0.
149
+ def self.random_number(n=0)
150
+ if 0 < n
151
+ hex = n.to_s(16)
152
+ hex = '0' + hex if (hex.length & 1) == 1
153
+ bin = [hex].pack("H*")
154
+ mask = bin[0].ord
155
+ mask |= mask >> 1
156
+ mask |= mask >> 2
157
+ mask |= mask >> 4
158
+ begin
159
+ rnd = SecureRandom.random_bytes(bin.length)
160
+ rnd[0] = (rnd[0].ord & mask).chr
161
+ end until rnd < bin
162
+ rnd.unpack("H*")[0].hex
163
+ else
164
+ # assumption: Float::MANT_DIG <= 64
165
+ i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
166
+ Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
167
+ end
168
+ end
169
+
170
+ # Following code is based on David Garamond's GUID library for Ruby.
171
+ def self.lastWin32ErrorMessage # :nodoc:
172
+ get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
173
+ format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
174
+ format_message_ignore_inserts = 0x00000200
175
+ format_message_from_system = 0x00001000
176
+
177
+ code = get_last_error.call
178
+ msg = "\0" * 1024
179
+ len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
180
+ msg[0, len].tr("\r", '').chomp
181
+ end
182
+ end
data/lib/uuid.rb ADDED
@@ -0,0 +1,128 @@
1
+ begin
2
+ require 'securerandom'
3
+ rescue LoadError
4
+ require File.join(File.dirname(__FILE__), 'compat', 'securerandom')
5
+ end
6
+
7
+ class UUID
8
+ def initialize(value = nil)
9
+ @value = Integer(value)
10
+ unless (@value >= 0) && (@value < (1 << 128))
11
+ raise RangeError, "#{value} (Integer value #{@value}) is out of range (need unsigned 128-bit value)"
12
+ end
13
+ end
14
+
15
+ def ==(other)
16
+ eql?(other)
17
+ end
18
+
19
+ def clock_seq
20
+ ((clock_seq_hi_variant & 0x3f) << 8) | clock_seq_low
21
+ end
22
+
23
+ def clock_seq_hi_variant
24
+ (to_i >> 56) & 0xff
25
+ end
26
+
27
+ def clock_seq_low
28
+ (to_i >> 48) & 0xff
29
+ end
30
+
31
+ def eql?(other)
32
+ other.is_a?(self.class) && (other.to_i == to_i)
33
+ end
34
+
35
+ def hash
36
+ to_i.hash
37
+ end
38
+
39
+ def hex
40
+ '%032x' % to_i
41
+ end
42
+
43
+ def inspect
44
+ to_s
45
+ end
46
+
47
+ def node
48
+ to_i & 0xffffffffffff
49
+ end
50
+
51
+ def time
52
+ ((time_hi_version & 0x0fff) << 48) | (time_mid << 32) | time_low
53
+ end
54
+
55
+ def time_hi_version
56
+ (to_i >> 64) & 0xffff
57
+ end
58
+
59
+ def time_low
60
+ (to_i >> 96)
61
+ end
62
+
63
+ def time_mid
64
+ (to_i >> 80) & 0xffff
65
+ end
66
+
67
+ def to_i
68
+ @value
69
+ end
70
+
71
+ def to_s
72
+ h = hex
73
+ '%s-%s-%s-%s-%s' % [h[0...8], h[8...12], h[12...16], h[16...20], h[20...32]]
74
+ end
75
+
76
+ def urn
77
+ "urn:uuid:#{self}"
78
+ end
79
+
80
+ RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
81
+ 'reserved for NCS compatibility',
82
+ 'specified in RFC 4122',
83
+ 'reserved for Microsoft compatibility',
84
+ 'reserved for future definition'
85
+ ]
86
+
87
+ def variant
88
+ case 0
89
+ when to_i & (0x8000 << 48)
90
+ RESERVED_NCS
91
+ when to_i & (0x4000 << 48)
92
+ RFC_4122
93
+ when to_i & (0x2000 << 48)
94
+ RESERVED_MICROSOFT
95
+ else
96
+ RESERVED_FUTURE
97
+ end
98
+ end
99
+
100
+ def version
101
+ (variant == RFC_4122) ? ((to_i >> 76) & 0xf) : nil
102
+ end
103
+
104
+ class << self
105
+ def parse(uuid)
106
+ str = uuid.to_s
107
+ unless str =~ /^(urn:uuid:)?[0-9a-fA-F]{8}((-)?[0-9a-fA-F]{4}){3}(-)?[0-9a-fA-F]{12}$/
108
+ raise ArgumentError, "#{str} is not a recognized UUID representation"
109
+ end
110
+ new bytes_to_i(str.gsub(/(^urn:uuid:|-)/, '').downcase.unpack('a2' * 16).collect { |x| x.to_i(16) }.pack('C*'))
111
+ end
112
+
113
+ def uuid4
114
+ bytes = SecureRandom.random_bytes(16)
115
+ bytes[6] = (bytes[6] & 0x0f) | 0x40
116
+ bytes[8] = (bytes[8] & 0x3f) | 0x80
117
+ new bytes_to_i(bytes)
118
+ end
119
+
120
+ private
121
+
122
+ def bytes_to_i(bytes)
123
+ bytes.unpack('C*').inject { |value, i| value * 256 | i }
124
+ end
125
+ end
126
+ end
127
+
128
+ UUID::Nil = UUID.new(0)
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'uuid'
data/spec/uuid_spec.rb ADDED
@@ -0,0 +1,87 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe UUID, '.uuid4' do
4
+ it 'generates a new UUID' do
5
+ UUID.uuid4.should be_a_kind_of(UUID)
6
+ UUID.uuid4.should_not == UUID.new
7
+ end
8
+
9
+ it 'generates version 4 UUIDs' do
10
+ UUID.uuid4.to_s.should =~ /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
11
+ UUID.uuid4.version.should == 4
12
+ end
13
+
14
+ it 'generates UUIDs of the RFC 4122 variant' do
15
+ UUID.uuid4.variant.should == UUID::RFC_4122
16
+ end
17
+ end
18
+
19
+ describe UUID, '.parse' do
20
+ it 'creates a UUID from a valid string representation when passed one' do
21
+ id = UUID.uuid4
22
+ [id.to_s, id.hex, id.urn].each do |s|
23
+ UUID.parse(s).should == id
24
+ end
25
+ UUID.parse(UUID::Nil.to_s).should == UUID::Nil
26
+ end
27
+
28
+ it 'raises an ArgumentError when passed a string it cannot parse into a UUID' do
29
+ lambda { UUID.parse '<garbage>' }.should raise_error(ArgumentError)
30
+ end
31
+ end
32
+
33
+ describe UUID, '#initialize' do
34
+ it 'creates a UUID from an Integer when passed one that is within range for a UUID' do
35
+ id = UUID.uuid4
36
+ UUID.new(id.to_i).should == id
37
+ UUID.new(0).should == UUID::Nil
38
+ end
39
+
40
+ it 'raises an ArgumentError when passed a parameter that cannot be coerced into an Integer' do
41
+ lambda { UUID.new '<garbage>' }.should raise_error(ArgumentError)
42
+ end
43
+
44
+ it 'raises a RangeError when passed an Integer that is out of range for a UUID' do
45
+ lambda { UUID.new -1 }.should raise_error(RangeError)
46
+ lambda { UUID.new(1 << 128) }.should raise_error(RangeError)
47
+ end
48
+ end
49
+
50
+ describe UUID, '#to_i' do
51
+ it 'returns an unsigned integer representation of the UUID' do
52
+ i = UUID.uuid4.to_i
53
+ i.should be_a_kind_of(Integer)
54
+ i.should >= 0
55
+ i.should < (1 << 128)
56
+ end
57
+ end
58
+
59
+ delimited_hex_format = /[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}/
60
+
61
+ describe UUID, '#to_s' do
62
+ it 'returns a UUID string in delimited hex format (i.e., xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)' do
63
+ UUID.new.to_s.should =~ /^#{delimited_hex_format}$/
64
+ end
65
+ end
66
+
67
+ describe UUID, '#hex' do
68
+ it 'returns a UUID string in undelimited hex format (i.e., xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)' do
69
+ UUID.new.hex.should =~ /^[0-9a-fA-F]{8}([0-9a-fA-F]{4}){3}[0-9a-fA-F]{12}$/
70
+ end
71
+ end
72
+
73
+ describe UUID, '#urn' do
74
+ it 'returns a UUID string in delimited hex format with a urn prefix (i.e., urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)' do
75
+ UUID.new.urn.should =~ /^urn:uuid:#{delimited_hex_format}$/
76
+ end
77
+ end
78
+
79
+ describe 'UUID::Nil' do
80
+ it 'has a string representation (#to_s) of 00000000-0000-0000-0000-000000000000' do
81
+ UUID::Nil.to_s.should == '00000000-0000-0000-0000-000000000000'
82
+ end
83
+
84
+ it 'has an integer value (#to_i) of 0' do
85
+ UUID::Nil.to_i.should == 0
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gabe-uuid
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.3"
5
+ platform: ruby
6
+ authors:
7
+ - Gabriel Boyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Simple UUID implementation, as per RFC 4122
17
+ email: gboyer@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - LICENSE
26
+ - README.markdown
27
+ - Rakefile
28
+ - spec/spec_helper.rb
29
+ - spec/uuid_spec.rb
30
+ - lib/uuid.rb
31
+ - lib/compat/securerandom.rb
32
+ has_rdoc: false
33
+ homepage: http://github.com/gabe/uuid/
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.6
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: Simple UUID implementation
58
+ test_files: []
59
+