├── .gitignore
├── .rvmrc
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── uuid.gemspec
├── Rakefile
├── MIT-LICENSE
├── bin
└── uuid
├── CHANGELOG
├── README.rdoc
├── test
└── test-uuid.rb
└── lib
└── uuid.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle
2 | *.swp
3 | /html
4 | *.gem
5 |
--------------------------------------------------------------------------------
/.rvmrc:
--------------------------------------------------------------------------------
1 | export RUBYOPT="rubygems"
2 | export RUBYLIB="."
3 | rvm 1.9.2
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.8.7
3 | - 1.9.2
4 | gemfile:
5 | - Gemfile
6 | env:
7 | notifications:
8 | recipients:
9 | - assaf@labnotes.org
10 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | gemspec
3 |
4 | group :development do
5 | gem "rake"
6 | gem "yard"
7 | end
8 |
9 | group :test do
10 | gem "mocha"
11 | end
12 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | uuid (2.3.7)
5 | macaddr (~> 1.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | macaddr (1.6.7)
11 | systemu (~> 2.6.2)
12 | mocha (0.9.12)
13 | rake (0.8.7)
14 | systemu (2.6.4)
15 | yard (0.6.7)
16 |
17 | PLATFORMS
18 | ruby
19 |
20 | DEPENDENCIES
21 | mocha
22 | rake
23 | uuid!
24 | yard
25 |
--------------------------------------------------------------------------------
/uuid.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = 'uuid'
3 | s.version = '2.3.9'
4 | s.summary = "UUID generator"
5 | s.description = <<-EOF
6 | UUID generator for producing universally unique identifiers based on RFC 4122
7 | (http://www.ietf.org/rfc/rfc4122.txt).
8 | EOF
9 |
10 | s.authors << 'Assaf Arkin' << 'Eric Hodel'
11 | s.email = 'assaf@labnotes.org'
12 | s.homepage = 'http://github.com/assaf/uuid'
13 | s.license = 'MIT'
14 |
15 | s.files = Dir['{bin,test,lib,docs}/**/*'] + ['README.rdoc', 'MIT-LICENSE', 'Rakefile', 'CHANGELOG', 'uuid.gemspec']
16 | s.executables = "uuid"
17 |
18 | s.rdoc_options << '--main' << 'README.rdoc' << '--title' << 'UUID generator' << '--line-numbers'
19 | '--webcvs' << 'http://github.com/assaf/uuid'
20 | s.extra_rdoc_files = ['README.rdoc', 'MIT-LICENSE']
21 |
22 | s.add_dependency 'macaddr', ['~>1.0']
23 | end
24 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake/testtask'
2 | #require 'rake/rdoctask'
3 |
4 |
5 | spec = Gem::Specification.load(File.expand_path("uuid.gemspec", File.dirname(__FILE__)))
6 |
7 | desc "Default Task"
8 | task :default => :test
9 |
10 |
11 | desc "Run all test cases"
12 | Rake::TestTask.new do |test|
13 | test.verbose = true
14 | test.test_files = ['test/*.rb']
15 | test.warning = true
16 | end
17 |
18 | # Create the documentation.
19 | #Rake::RDocTask.new do |rdoc|
20 | # rdoc.rdoc_files.include "README.rdoc", "lib/**/*.rb"
21 | # rdoc.options = spec.rdoc_options
22 | #end
23 |
24 |
25 |
26 | desc "Push new release to rubyforge and git tag"
27 | task :push do
28 | sh "git push"
29 | puts "Tagging version #{spec.version} .."
30 | sh "git tag v#{spec.version}"
31 | sh "git push --tag"
32 | puts "Building and pushing gem .."
33 | sh "gem build #{spec.name}.gemspec"
34 | sh "gem push #{spec.name}-#{spec.version}.gem"
35 | end
36 |
37 | desc "Install #{spec.name} locally"
38 | task :install do
39 | sh "gem build #{spec.name}.gemspec"
40 | sh "gem install #{spec.name}-#{spec.version}.gem"
41 | end
42 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2005-2012 Assaf Arkin, Eric Hodel
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.
21 |
--------------------------------------------------------------------------------
/bin/uuid:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "uuid"
3 | require "optparse"
4 |
5 | address = nil
6 | count = 1
7 | format = :default
8 | server = false
9 |
10 | opts = OptionParser.new("", 24, ' ') do |opts|
11 | opts.banner = "Usage: #{File.basename($0)} [options]"
12 |
13 | opts.separator "\nOptions:"
14 | opts.on("-s", "--socket {HOST:PORT|PATH}",
15 | "communicate on HOST:PORT or PATH (default: #{UUID::SOCKET_NAME})") do |value|
16 | address = value
17 | end
18 |
19 | opts.on("-S", "--server", "run as a server") do |value|
20 | server = value ? true : false
21 | end
22 |
23 | opts.on("-F", "--format {FORMAT}", "UUID format (client only)") do |value|
24 | format = value.to_sym
25 | end
26 |
27 | opts.on("-C", "--count {COUNT}", "returns give number of UUIDs") do |value|
28 | count = value.to_i
29 | end
30 |
31 | opts.on("-h", "--help", "Show this message") do
32 | puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
33 | exit
34 | end
35 |
36 | opts.on("-v", "--version", "Show version") do
37 | puts "UUID v#{UUID::VERSION}"
38 | exit
39 | end
40 |
41 | opts.parse! ARGV
42 | end
43 |
44 |
45 | if server
46 | $stdout << "Starting UUID server on #{address}\n"
47 | UUID::Server.new.listen(address || UUID::SOCKET_NAME)
48 | else
49 | UUID.server = address if address
50 | $stdout << UUID.generate(format)
51 | (count - 1).times do
52 | $stdout.putc "\n"
53 | $stdout << UUID.generate(format)
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | 2.3.7 (2013-02-18)
2 |
3 | Use Dir.tmpdir instead of hardcoded /var/tmp path (Justin Langhorst).
4 |
5 |
6 | 2.3.6 (2012-11-18)
7 |
8 | Manually setting the state_file path sets mode too (Tom Lea).
9 |
10 | When manually setting the state file, set the mode too. Failure to do so
11 | causes an implicit conversion of nil to integer on first usage.
12 |
13 | Also provided a mode method to set the mode explicitly.
14 |
15 |
16 | Reverted executable loss (Michael Witrant ).
17 |
18 |
19 | 2.3.4 (2012-01-23)
20 |
21 | Source files are now UTF-8 (Vicente Reig)
22 |
23 | 2.3.4 (2011-09-01)
24 |
25 | Fixed State file mode isn't respected with standard umask (Brandon Turner).
26 |
27 | 2.3.3 (2011-08-01)
28 |
29 | Check if ruby-uuid is writable: it might have been claimed by another user on
30 | the system already (Andy Lo-A-Foe)
31 |
32 | 2.3.2 (2011-04-11)
33 | * Allow usage on environments that don't have MAC address accessible (mikezter)
34 | * Fix for JRuby expand_path interpretation on windows (pinnymz)
35 |
36 | 2.3.1 (2010-05-07)
37 | * Fix: Open state file in binary mode (http://github.com/assaf/uuid/issues#issue/8)
38 |
39 | 2.3.0 (2010-04-07)
40 | * Added: UUID.generator returns the current UUID generator. Particularly useful for calling
41 | next_sequence on the generator when forking a process.
42 | * Added: UUID::Server and UUID::Client so you can have one process serving you UUIDs.
43 | * Added: UUID command line tool. Yay!
44 |
45 | 2.2.0 (2010-02-18)
46 | * Added: set UUID.state_file = false if you cannot use a state file (e.g. shared hosting environment)
47 |
48 | 2.1.1 (2010-01-27)
49 | * Fixed bug which caused UUID.new to fail if the state file was somehow extant but empty
50 |
51 | 2.1.0 (2009-12-16)
52 | * Added uuid.validate -- easier to implement than explain why it's wrong.
53 |
54 | 2.0.2 (2009-06-10)
55 | * Maintenance release. Added uuid.gemspec file in packaging, tested against
56 | Ruby 1.9.1.
57 |
58 | 2.0.1 (2008-08-28)
59 | * Fixed: MAC address parses correctly when using colon as separator, not
60 | when using hyphen (ruby-mingw32). If your MAC address is all zero
61 | (check with UUID.new.inspect), remove the ruby-uuid file, and it
62 | will reset to the actual MAC address. (Rasha)
63 | * Fixed: UUID.new.inspect not showing full MAC address.
64 |
65 | 2.0.0 (2008-08-28)
66 | * Changed: API. UUID.generate still works as it always did, but the rest of
67 | the API is brand spanking new, so if you rely on anything besides
68 | UUID.generate, or just curious, check out the rdocs.
69 | * Changed: uuid.state replaced by ruby-uuid file. By default stored in
70 | /var/tmp, or if that path is not accessible, as .ruby-uuid in the
71 | home directory.
72 | * Changed: ruby-uuid is now stored as binary file (faster read/write), if you
73 | need to have a peek, open irb and type UUID.new.inspect.
74 | * Changed: Source code and documentation for this release hosted on the
75 | wonderful Github: http://github.com/assaf/uuid
76 |
77 | 1.0.4 (2007-08-28)
78 | * Changed: By default creates the uuid.state file in the working directory,
79 | not in the installation directory, which requires sudo privileges
80 | (e.g. gem).
81 |
82 | 1.0.3 (2006-11-08)
83 | * Fixed: Work around YAML bug in serializing that occurs when MAC address
84 | consists only of decimal digits. Credit: ebuprofen"
85 |
86 | 1.0.2 (2006-08-19)
87 | * Changed: Constants are not conditionally defined (removes warnings when
88 | using in Rails.
89 |
90 | 1.0.1 (2006-07-27)
91 | * Added: Regular expressions to test if a string is a UUID.
92 | * Changed: When used in ActiveRecord, adds as callback instead of overriding
93 | save.
94 |
95 | 1.0.0 (2005-11-20)
96 | * Changed: Separated form reliable-msg into its own package.
97 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = UUID Generator
2 |
3 | Generates universally unique identifiers (UUIDs) for use in distributed
4 | applications. Based on {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
5 |
6 |
7 | == Generating UUIDs
8 |
9 | Call #generate to generate a new UUID. The method returns a string in one of
10 | three formats. The default format is 36 characters long, and contains the 32
11 | hexadecimal octets and hyphens separating the various value parts. The
12 | :compact format omits the hyphens, while the :urn format
13 | adds the :urn:uuid prefix.
14 |
15 | For example:
16 |
17 | uuid = UUID.new
18 | 10.times do
19 | p uuid.generate
20 | end
21 |
22 |
23 | == UUIDs in Brief
24 |
25 | UUID (universally unique identifier) are guaranteed to be unique across time
26 | and space.
27 |
28 | A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
29 | sequence number and a 48-bit node identifier.
30 |
31 | The time value is taken from the system clock, and is monotonically
32 | incrementing. However, since it is possible to set the system clock
33 | backward, a sequence number is added. The sequence number is incremented
34 | each time the UUID generator is started. The combination guarantees that
35 | identifiers created on the same machine are unique with a high degree of
36 | probability.
37 |
38 | Note that due to the structure of the UUID and the use of sequence number,
39 | there is no guarantee that UUID values themselves are monotonically
40 | incrementing. The UUID value cannot itself be used to sort based on order
41 | of creation.
42 |
43 | To guarantee that UUIDs are unique across all machines in the network,
44 | the IEEE 802 MAC address of the machine's network interface card is used as
45 | the node identifier.
46 |
47 | For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
48 |
49 |
50 | == UUID State File
51 |
52 | The UUID generator uses a state file to hold the MAC address and sequence
53 | number.
54 |
55 | The MAC address is used to generate identifiers that are unique to your
56 | machine, preventing conflicts in distributed applications. The MAC
57 | address is six bytes (48 bit) long. It is automatically extracted from
58 | one of the network cards on your machine.
59 |
60 | The sequence number is incremented each time the UUID generator is
61 | first used by the application, to prevent multiple processes from
62 | generating the same set of identifiers, and deal with changes to the
63 | system clock.
64 |
65 | The UUID state file is created in #Dir.tmpdir/ruby-uuid or the Windows
66 | common application data directory using mode 0644. If that directory is not
67 | writable, the file is created as .ruby-uuid in the home directory.
68 | If you need to create the file with a different mode, use UUID#state_file
69 | before running the UUID generator.
70 |
71 | Note: If you are running on a shared host where the state file is not shared
72 | between processes, or persisted across restarts (e.g. Heroku, Google App
73 | Engine) you can simple turn it off:
74 |
75 | UUID.state_file = false
76 |
77 | State files are not portable across machines.
78 |
79 | If you do not use the state file, UUID generation will attempt to use your
80 | server's MAC address using the macaddr gem, which runs system commands to
81 | identify the MAC address and then cache it. Since this can take a few seconds
82 | on some operating systems, when using UUID.state_file = false, you should add
83 | the following line after disabling the state file:
84 |
85 | UUID.generator.next_sequence
86 |
87 | Note: when using a forking server (Unicorn, Resque, Pipemaster, etc) you don't
88 | want your forked processes using the same sequence number. Make sure to
89 | increment the sequence number each time a worker forks.
90 |
91 | For example, in config/unicorn.rb:
92 |
93 | after_fork do |server, worker|
94 | UUID.generator.next_sequence
95 | end
96 |
97 |
98 | == Command Line
99 |
100 | You can run uuid from the command line, generating new UUID to stdout:
101 |
102 | $ uuid
103 |
104 | Multiple UUIDs in a sequence, separated with a newline:
105 |
106 | $ uuid --count 10
107 |
108 | You can also run client and server from the command line
109 |
110 | $ uuid --server &
111 | $ uuid --socket /var/lib/uuid.sock
112 |
113 |
114 | == Latest and Greatest
115 |
116 | Source code and documentation hosted on Github: http://github.com/assaf/uuid
117 |
118 | To get UUID from source:
119 |
120 | git clone git://github.com/assaf/uuid.git
121 |
122 |
123 | == License
124 |
125 | This package is licensed under the MIT license and/or the Creative
126 | Commons Attribution-ShareAlike.
127 |
128 | :include: MIT-LICENSE
129 |
130 |
--------------------------------------------------------------------------------
/test/test-uuid.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # Author:: Assaf Arkin assaf@labnotes.org
3 | # Eric Hodel drbrain@segment7.net
4 | # Copyright:: Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
5 | # License:: MIT and/or Creative Commons Attribution-ShareAlike
6 |
7 | require 'test/unit'
8 | require 'rubygems'
9 | require 'uuid'
10 | require 'mocha'
11 |
12 | class TestUUID < Test::Unit::TestCase
13 |
14 | def test_state_file_creation
15 | path = UUID.state_file
16 | File.delete path if File.exist?(path)
17 | UUID.new.generate
18 | File.exist?(path)
19 | end
20 |
21 | def test_state_file_creation_mode
22 | UUID.class_eval{ @state_file = nil; @mode = nil }
23 | UUID.state_file 0666
24 | path = UUID.state_file
25 | File.delete path if File.exist?(path)
26 |
27 | old_umask = File.umask(0022)
28 | UUID.new.generate
29 | File.umask(old_umask)
30 |
31 | assert_equal '0666', sprintf('%04o', File.stat(path).mode & 0777)
32 | end
33 |
34 | def test_state_file_specify
35 | path = File.join("path", "to", "ruby-uuid")
36 | UUID.state_file = path
37 | assert_equal path, UUID.state_file
38 | end
39 |
40 | def test_mode_is_set_on_state_file_specify
41 | UUID.class_eval{ @state_file = nil; @mode = nil }
42 | path = File.join(Dir.tmpdir, "ruby-uuid-test")
43 | File.delete path if File.exist?(path)
44 |
45 | UUID.state_file = path
46 |
47 | old_umask = File.umask(0022)
48 | UUID.new.generate
49 | File.umask(old_umask)
50 |
51 | UUID.class_eval{ @state_file = nil; @mode = nil }
52 | assert_equal '0644', sprintf('%04o', File.stat(path).mode & 0777)
53 | end
54 |
55 | def test_with_no_state_file
56 | UUID.state_file = false
57 | assert !UUID.state_file
58 | uuid = UUID.new
59 | assert_match(/\A[\da-f]{32}\z/i, uuid.generate(:compact))
60 | seq = uuid.next_sequence
61 | assert_equal seq + 1, uuid.next_sequence
62 | assert !UUID.state_file
63 | end
64 |
65 | def validate_uuid_generator(uuid)
66 | assert_match(/\A[\da-f]{32}\z/i, uuid.generate(:compact))
67 |
68 | assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
69 | uuid.generate(:default))
70 |
71 | assert_match(/^urn:uuid:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
72 | uuid.generate(:urn))
73 |
74 | e = assert_raise ArgumentError do
75 | uuid.generate :unknown
76 | end
77 | assert_equal 'invalid UUID format :unknown', e.message
78 |
79 | end
80 |
81 | def test_instance_generate
82 | uuid = UUID.new
83 | validate_uuid_generator(uuid)
84 | end
85 |
86 | def test_class_generate
87 | assert_match(/\A[\da-f]{32}\z/i, UUID.generate(:compact))
88 |
89 | assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
90 | UUID.generate(:default))
91 |
92 | assert_match(/^urn:uuid:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
93 | UUID.generate(:urn))
94 |
95 | e = assert_raise ArgumentError do
96 | UUID.generate :unknown
97 | end
98 | assert_equal 'invalid UUID format :unknown', e.message
99 | end
100 |
101 | def test_class_validate
102 | assert !UUID.validate('')
103 |
104 | assert UUID.validate('01234567abcd8901efab234567890123'), 'compact'
105 | assert UUID.validate('01234567-abcd-8901-efab-234567890123'), 'default'
106 | assert UUID.validate('urn:uuid:01234567-abcd-8901-efab-234567890123'),
107 | 'urn'
108 |
109 | assert UUID.validate('01234567ABCD8901EFAB234567890123'), 'compact'
110 | assert UUID.validate('01234567-ABCD-8901-EFAB-234567890123'), 'default'
111 | assert UUID.validate('urn:uuid:01234567-ABCD-8901-EFAB-234567890123'),
112 | 'urn'
113 | end
114 |
115 | def test_monotonic
116 | seen = {}
117 | uuid_gen = UUID.new
118 |
119 | 20_000.times do
120 | uuid = uuid_gen.generate
121 | assert !seen.has_key?(uuid), "UUID repeated"
122 | seen[uuid] = true
123 | end
124 | end
125 |
126 | def test_same_mac
127 | class << foo = UUID.new
128 | attr_reader :mac
129 | end
130 | class << bar = UUID.new
131 | attr_reader :mac
132 | end
133 | assert_equal foo.mac, bar.mac
134 | end
135 |
136 | def test_increasing_sequence
137 | class << foo = UUID.new
138 | attr_reader :sequence
139 | end
140 | class << bar = UUID.new
141 | attr_reader :sequence
142 | end
143 | assert_equal foo.sequence + 1, bar.sequence
144 | end
145 |
146 | def test_pseudo_random_mac_address
147 | uuid_gen = UUID.new
148 | Mac.stubs(:addr).returns "00:00:00:00:00:00"
149 | assert uuid_gen.iee_mac_address == 0
150 | [:compact, :default, :urn].each do |format|
151 | assert UUID.validate(uuid_gen.generate(format)), format.to_s
152 | end
153 | validate_uuid_generator(uuid_gen)
154 | end
155 |
156 | end
157 |
158 |
--------------------------------------------------------------------------------
/lib/uuid.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | #
3 | # = uuid.rb - UUID generator
4 | #
5 | # Author:: Assaf Arkin assaf@labnotes.org
6 | # Eric Hodel drbrain@segment7.net
7 | # Copyright:: Copyright (c) 2005-2010 Assaf Arkin, Eric Hodel
8 | # License:: MIT and/or Creative Commons Attribution-ShareAlike
9 |
10 | require 'fileutils'
11 | require 'thread'
12 | require 'tmpdir'
13 | require 'socket'
14 | require 'macaddr'
15 | require 'digest/sha1'
16 | require 'tmpdir'
17 |
18 |
19 | ##
20 | # = Generating UUIDs
21 | #
22 | # Call #generate to generate a new UUID. The method returns a string in one of
23 | # three formats. The default format is 36 characters long, and contains the 32
24 | # hexadecimal octets and hyphens separating the various value parts. The
25 | # :compact format omits the hyphens, while the :urn format
26 | # adds the :urn:uuid prefix.
27 | #
28 | # For example:
29 | #
30 | # uuid = UUID.new
31 | #
32 | # 10.times do
33 | # p uuid.generate
34 | # end
35 | #
36 | # = UUIDs in Brief
37 | #
38 | # UUID (universally unique identifier) are guaranteed to be unique across time
39 | # and space.
40 | #
41 | # A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
42 | # sequence number and a 48-bit node identifier.
43 | #
44 | # The time value is taken from the system clock, and is monotonically
45 | # incrementing. However, since it is possible to set the system clock
46 | # backward, a sequence number is added. The sequence number is incremented
47 | # each time the UUID generator is started. The combination guarantees that
48 | # identifiers created on the same machine are unique with a high degree of
49 | # probability.
50 | #
51 | # Note that due to the structure of the UUID and the use of sequence number,
52 | # there is no guarantee that UUID values themselves are monotonically
53 | # incrementing. The UUID value cannot itself be used to sort based on order
54 | # of creation.
55 | #
56 | # To guarantee that UUIDs are unique across all machines in the network,
57 | # the IEEE 802 MAC address of the machine's network interface card is used as
58 | # the node identifier.
59 | #
60 | # For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
61 |
62 | class UUID
63 |
64 | # Version number.
65 | module Version
66 | version = Gem::Specification.load(File.expand_path("../uuid.gemspec", File.dirname(__FILE__))).version.to_s.split(".").map { |i| i.to_i }
67 | MAJOR = version[0]
68 | MINOR = version[1]
69 | PATCH = version[2]
70 | STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
71 | end
72 |
73 | VERSION = Version::STRING
74 |
75 | ##
76 | # Clock multiplier. Converts Time (resolution: seconds) to UUID clock
77 | # (resolution: 10ns)
78 | CLOCK_MULTIPLIER = 10000000
79 |
80 | ##
81 | # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time
82 | # ticks.
83 | CLOCK_GAPS = 100000
84 |
85 | ##
86 | # Version number stamped into the UUID to identify it as time-based.
87 | VERSION_CLOCK = 0x0100
88 |
89 | ##
90 | # Formats supported by the UUID generator.
91 | #
92 | # :default:: Produces 36 characters, including hyphens separating
93 | # the UUID value parts
94 | # :compact:: Produces a 32 digits (hexadecimal) value with no
95 | # hyphens
96 | # :urn:: Adds the prefix urn:uuid: to the default format
97 | FORMATS = {
98 | :compact => '%08x%04x%04x%04x%012x',
99 | :default => '%08x-%04x-%04x-%04x-%012x',
100 | :urn => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
101 | }
102 |
103 | ##
104 | # MAC address (48 bits), sequence number and last clock
105 | STATE_FILE_FORMAT = 'SLLQ'
106 |
107 | @state_file = nil
108 | @mode = nil
109 | @uuid = nil
110 |
111 | ##
112 | # The access mode of the state file. Set it with state_file.
113 |
114 | def self.mode
115 | @mode
116 | end
117 |
118 | def self.mode=(mode)
119 | @mode = mode
120 | end
121 |
122 | ##
123 | # Generates a new UUID string using +format+. See FORMATS for a list of
124 | # supported formats.
125 |
126 | def self.generate(format = :default)
127 | @uuid ||= new
128 | @uuid.generate format
129 | end
130 |
131 | ##
132 | # Returns the UUID generator used by generate. Useful if you need to mess
133 | # with it, e.g. force next sequence when forking (e.g. Unicorn, Resque):
134 | #
135 | # after_fork do
136 | # UUID.generator.next_sequence
137 | # end
138 | def self.generator
139 | @uuid ||= new
140 | end
141 |
142 | ##
143 | # Call this to use a UUID Server. Expects address to bind to (SOCKET_NAME is
144 | # a good default)
145 | def self.server=(address)
146 | @uuid = Client.new(address) unless Client === @uuid
147 | end
148 |
149 | ##
150 | # Creates an empty state file in #Dir.tmpdir/ruby-uuid or the windows common
151 | # application data directory using mode 0644. Call with a different mode
152 | # before creating a UUID generator if you want to open access beyond your
153 | # user by default.
154 | #
155 | # If the default state dir is not writable, UUID falls back to ~/.ruby-uuid.
156 | #
157 | # State files are not portable across machines.
158 | def self.state_file(mode = 0644)
159 | return @state_file unless @state_file.nil?
160 |
161 | @mode = mode
162 |
163 | begin
164 | require 'Win32API'
165 |
166 | csidl_common_appdata = 0x0023
167 | path = 0.chr * 260
168 | get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L')
169 | get_folder_path.call 0, csidl_common_appdata, 0, 1, path
170 |
171 | state_dir = File.join(path.strip)
172 | rescue LoadError
173 | state_dir = Dir.tmpdir
174 | end
175 |
176 | @state_file = File.join(state_dir, 'ruby-uuid')
177 |
178 | if !File.writable?(state_dir) || (File.exist?(@state_file) && !File.writable?(@state_file)) then
179 | @state_file = File.expand_path('.ruby-uuid', '~')
180 | end
181 |
182 | @state_file
183 | end
184 |
185 | ##
186 | # Specify the path of the state file. Use this if you need a different
187 | # location for your state file.
188 | #
189 | # Set to false if your system cannot use a state file (e.g. many shared
190 | # hosts).
191 | def self.state_file=(path)
192 | @state_file = path
193 | @mode ||= 0644
194 | end
195 |
196 | ##
197 | # Returns true if +uuid+ is in compact, default or urn formats. Does not
198 | # validate the layout (RFC 4122 section 4) of the UUID.
199 | def self.validate(uuid)
200 | return true if uuid =~ /\A[\da-f]{32}\z/i
201 | return true if
202 | uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/i
203 | end
204 |
205 | ##
206 | # Generate a pseudo MAC address because we have no pure-ruby way
207 | # to know the MAC address of the NIC this system uses. Note
208 | # that cheating with pseudo addresses here is completely legal:
209 | # see Section 4.5 of RFC4122 for details.
210 | #
211 | # This implementation is shamelessly stolen from
212 | # https://github.com/spectra/ruby-uuid/blob/master/uuid.rb
213 | # Thanks spectra.
214 | #
215 | def pseudo_mac_address
216 | sha1 = ::Digest::SHA1.new
217 | 256.times do
218 | r = [rand(0x100000000)].pack "N"
219 | sha1.update r
220 | end
221 | str = sha1.digest
222 | r = rand 14 # 20-6
223 | node = str[r, 6] || str
224 | if RUBY_VERSION >= "1.9.0"
225 | nnode = node.bytes.to_a
226 | nnode[0] |= 0x01
227 | node = ''
228 | nnode.each { |s| node << s.chr }
229 | else
230 | node[0] |= 0x01 # multicast bit
231 | end
232 | node.bytes.collect{|b|b.to_s(16)}.join.hex & 0x7FFFFFFFFFFF
233 | end
234 |
235 | ##
236 | # Uses system calls to get a mac address
237 | #
238 | def iee_mac_address
239 | begin
240 | Mac.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF
241 | rescue
242 | 0
243 | end
244 | end
245 |
246 | ##
247 | # return iee_mac_address if available, pseudo_mac_address otherwise
248 | #
249 | def mac_address
250 | return iee_mac_address unless iee_mac_address == 0
251 | return pseudo_mac_address
252 | end
253 |
254 | ##
255 | # Create a new UUID generator. You really only need to do this once.
256 | def initialize
257 | @drift = 0
258 | @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
259 | @mutex = Mutex.new
260 |
261 | state_file = self.class.state_file
262 | if state_file && File.size?(state_file) then
263 | next_sequence
264 | else
265 | @mac = mac_address
266 | fail "Cannot determine MAC address from any available interface, tried with #{mac_address}" if @mac == 0
267 | @sequence = rand 0x10000
268 |
269 | # Ensure the mode is respected, even with a restrictive umask
270 | File.open(state_file, 'w') { |f| f.chmod(self.class.mode) } if state_file && !File.exist?(state_file)
271 |
272 | if state_file
273 | open_lock 'wb' do |io|
274 | write_state io
275 | end
276 | end
277 | end
278 | end
279 |
280 | ##
281 | # Generates a new UUID string using +format+. See FORMATS for a list of
282 | # supported formats.
283 | def generate(format = :default)
284 | template = FORMATS[format]
285 |
286 | raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
287 |
288 | # The clock must be monotonically increasing. The clock resolution is at
289 | # best 100 ns (UUID spec), but practically may be lower (on my setup,
290 | # around 1ms). If this method is called too fast, we don't have a
291 | # monotonically increasing clock, so the solution is to just wait.
292 | #
293 | # It is possible for the clock to be adjusted backwards, in which case we
294 | # would end up blocking for a long time. When backward clock is detected,
295 | # we prevent duplicates by asking for a new sequence number and continue
296 | # with the new clock.
297 |
298 | clock = @mutex.synchronize do
299 | clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
300 |
301 | if clock > @last_clock then
302 | @drift = 0
303 | @last_clock = clock
304 | elsif clock == @last_clock then
305 | drift = @drift += 1
306 |
307 | if drift < 10000 then
308 | @last_clock += 1
309 | else
310 | Thread.pass
311 | nil
312 | end
313 | else
314 | next_sequence
315 | @last_clock = clock
316 | end
317 | end until clock
318 |
319 | template % [
320 | clock & 0xFFFFFFFF,
321 | (clock >> 32) & 0xFFFF,
322 | ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
323 | @sequence & 0xFFFF,
324 | @mac & 0xFFFFFFFFFFFF
325 | ]
326 | end
327 |
328 | ##
329 | # Updates the state file with a new sequence number.
330 | def next_sequence
331 | if self.class.state_file
332 | open_lock 'rb+' do |io|
333 | @mac, @sequence, @last_clock = read_state(io)
334 |
335 | io.rewind
336 | io.truncate 0
337 |
338 | @sequence += 1
339 |
340 | write_state io
341 | end
342 | else
343 | @sequence += 1
344 | end
345 | rescue Errno::ENOENT
346 | open_lock 'w' do |io|
347 | write_state io
348 | end
349 | ensure
350 | @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
351 | @drift = 0
352 | end
353 |
354 | def inspect
355 | mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':')
356 | "MAC: #{mac} Sequence: #{@sequence}"
357 | end
358 |
359 | protected
360 |
361 | ##
362 | # Open the state file with an exclusive lock and access mode +mode+.
363 | def open_lock(mode)
364 | File.open self.class.state_file, mode, self.class.mode do |io|
365 | begin
366 | io.flock File::LOCK_EX
367 | yield io
368 | ensure
369 | io.flock File::LOCK_UN
370 | end
371 | end
372 | end
373 |
374 | ##
375 | # Read the state from +io+
376 | def read_state(io)
377 | mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT)
378 | mac = (mac1 << 32) + mac2
379 |
380 | return mac, seq, last_clock
381 | end
382 |
383 |
384 | ##
385 | # Write that state to +io+
386 | def write_state(io)
387 | mac2 = @mac & 0xffffffff
388 | mac1 = (@mac >> 32) & 0xffff
389 |
390 | io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
391 | end
392 |
393 |
394 | # You don't have to use this, it's just a good default.
395 | SOCKET_NAME ="/var/lib/uuid.sock"
396 |
397 | # With UUID server you don't have to worry about multiple processes
398 | # synchronizing over the state file, calling next_sequence when forking a
399 | # process and other things you're probably not worried about (because
400 | # statistically they're very unlikely to break your code).
401 | #
402 | # But if you are worried about and thought to yourself, "what would a simple
403 | # UUID server look like?", here's the answer. The protocol is dead simple:
404 | # client sends a byte, server responds with a UUID. Can use TCP or domain
405 | # sockets.
406 | class Server
407 |
408 | # Create new server. Nothing interesting happens until you call listen.
409 | def initialize()
410 | @generator = UUID.new
411 | end
412 |
413 | # Start the server listening on the specific address. Blocks and never
414 | # returns. Address can be:
415 | # - A Socket object
416 | # - UNIX domain socket name (e.g. /var/run/uuid.sock, must start with /)
417 | # - IP address, colon, port (e.g. localhost:1337)
418 | def listen(address)
419 | sock = bind(address)
420 | while client = sock.accept
421 | Thread.start(client) do |socket|
422 | while socket.read 1
423 | socket.write @generator.generate
424 | end
425 | end
426 | end
427 | end
428 |
429 | # Returns UNIXServer or TCPServer from address. Returns argument if not a
430 | # string, so can pass through (see #listen).
431 | def bind(address)
432 | return address unless String === address
433 | if address[0] == ?/
434 | if File.exist?(address)
435 | raise ArgumentError, "#{address} is not a socket" unless File.socket?(address)
436 | File.unlink(address)
437 | end
438 | sock = UNIXServer.new(address)
439 | File.chmod 0666, address
440 | elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
441 | sock = TCPServer.new($1, $2.to_i)
442 | else
443 | raise ArgumentError, "Don't know how to bind #{address}"
444 | end
445 | sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
446 | sock
447 | end
448 |
449 | end
450 |
451 |
452 | # Every server needs a client. Client provides you with the single ultimate
453 | # method: #generate. Typically you'll use this instead of the local UUID
454 | # generator:
455 | # UUID.server = UUID::SOCKET_NAME
456 | class Client
457 |
458 | def initialize(address)
459 | @socket = connect(address)
460 | at_exit { close }
461 | end
462 |
463 | # Talks to server and returns new UUID in specified format.
464 | def generate(format = :default)
465 | @socket.write "\0"
466 | uuid = @socket.read(36)
467 | return uuid if format == :default
468 | template = FORMATS[format]
469 | raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
470 | template % uuid.split("-").map { |p| p.to_i(16) }
471 | end
472 |
473 | # Returns UNIXSocket or TCPSocket from address. Returns argument if not a
474 | # string, so can pass through.
475 | def connect(address)
476 | return address unless String === address
477 | if address[0] == ?/
478 | sock = UNIXSocket.new(address)
479 | elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
480 | sock = TCPSocket.new($1, $2.to_i)
481 | else
482 | raise ArgumentError, "Don't know how to connect to #{address}"
483 | end
484 | sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
485 | sock
486 | end
487 |
488 | def next_sequence #:nodoc: Stubbed to do nothing.
489 | end
490 |
491 | def inspect
492 | @socket ? "Server on #{Socket.unpack_sockaddr_in(@socket.getsockname).reverse!.join(':')}" : "Connection closed"
493 | end
494 |
495 | # Close the socket.
496 | def close
497 | @socket.shutdown if @socket
498 | @socket = nil
499 | end
500 |
501 | end
502 |
503 | end
504 |
--------------------------------------------------------------------------------