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