# # = uuid.rb - UUID generator #

# Author

Assaf Arkin assaf@labnotes.org

# Eric Hodel drbrain@segment7.net

# Copyright

Copyright © 2005-2010 Assaf Arkin, Eric Hodel

# License

MIT and/or Creative Commons Attribution-ShareAlike

require 'fileutils' require 'thread' require 'tmpdir' require 'socket' require 'macaddr' require 'digest/sha1'

## # = Generating UUIDs # # Call generate to generate a new UUID. The method returns a string in one of # three formats. The default format is 36 characters long, and contains the 32 # hexadecimal octets and hyphens separating the various value parts. The # :compact format omits the hyphens, while the :urn format # adds the :urn:uuid prefix. # # For example: # # uuid = UUID.new # # 10.times do # p uuid.generate # end # # = UUIDs in Brief # # UUID (universally unique identifier) are guaranteed to be unique across time # and space. # # A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit # sequence number and a 48-bit node identifier. # # The time value is taken from the system clock, and is monotonically # incrementing. However, since it is possible to set the system clock # backward, a sequence number is added. The sequence number is incremented # each time the UUID generator is started. The combination guarantees that # identifiers created on the same machine are unique with a high degree of # probability. # # Note that due to the structure of the UUID and the use of sequence number, # there is no guarantee that UUID values themselves are monotonically # incrementing. The UUID value cannot itself be used to sort based on order # of creation. # # To guarantee that UUIDs are unique across all machines in the network, # the IEEE 802 MAC address of the machine's network interface card is used as # the node identifier. # # For more information see RFC 4122.

class UUID

# Version number.
module Version
  version = Gem::Specification.load(File.expand_path("../uuid.gemspec", File.dirname(__FILE__))).version.to_s.split(".").map { |i| i.to_i }
  MAJOR = version[0]
  MINOR = version[1]
  PATCH = version[2]
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
end

VERSION = Version::STRING

##
# Clock multiplier. Converts Time (resolution: seconds) to UUID clock
# (resolution: 10ns)
CLOCK_MULTIPLIER = 10000000

##
# Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time
# ticks.
CLOCK_GAPS = 100000

##
# Version number stamped into the UUID to identify it as time-based.
VERSION_CLOCK = 0x0100

##
# Formats supported by the UUID generator.
#
# <tt>:default</tt>:: Produces 36 characters, including hyphens separating
#                     the UUID value parts
# <tt>:compact</tt>:: Produces a 32 digits (hexadecimal) value with no
#                     hyphens
# <tt>:urn</tt>:: Adds the prefix <tt>urn:uuid:</tt> to the default format
FORMATS = {
  :compact => '%08x%04x%04x%04x%012x',
  :default => '%08x-%04x-%04x-%04x-%012x',
  :urn     => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
}

##
# MAC address (48 bits), sequence number and last clock
STATE_FILE_FORMAT = 'SLLQ'

@state_file = nil
@mode = nil
@uuid = nil

##
# The access mode of the state file.  Set it with state_file.

def self.mode
  @mode
end

def self.mode=(mode)
  @mode = mode
end

##
# Generates a new UUID string using +format+.  See FORMATS for a list of
# supported formats.

def self.generate(format = :default)
  @uuid ||= new
  @uuid.generate format
end

##
# Returns the UUID generator used by generate.  Useful if you need to mess
# with it, e.g. force next sequence when forking (e.g. Unicorn, Resque):
#
# after_fork do
#   UUID.generator.next_sequence
# end
def self.generator
  @uuid ||= new
end

##
# Call this to use a UUID Server.  Expects address to bind to (SOCKET_NAME is
# a good default)
def self.server=(address)
  @uuid = Client.new(address) unless Client === @uuid
end

##
# Creates an empty state file in /var/tmp/ruby-uuid or the windows common
# application data directory using mode 0644.  Call with a different mode
# before creating a UUID generator if you want to open access beyond your
# user by default.
#
# If the default state dir is not writable, UUID falls back to ~/.ruby-uuid.
#
# State files are not portable across machines.
def self.state_file(mode = 0644)
  return @state_file unless @state_file.nil?

  @mode = mode

  begin
    require 'Win32API'

    csidl_common_appdata = 0x0023
    path = 0.chr * 260
    get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L')
    get_folder_path.call 0, csidl_common_appdata, 0, 1, path

    state_dir = File.join(path.strip)
  rescue LoadError
    state_dir = File.join('', 'var', 'tmp')
  end

  @state_file = File.join(state_dir, 'ruby-uuid')

  if !File.writable?(state_dir) || (File.exists?(@state_file) && !File.writable?(@state_file)) then
    @state_file = File.expand_path('.ruby-uuid', '~')
  end

  @state_file
end

##
# Specify the path of the state file.  Use this if you need a different
# location for your state file.
#
# Set to false if your system cannot use a state file (e.g. many shared
# hosts).
def self.state_file=(path)
  @state_file = path
  @mode ||= 0644
end

##
# Returns true if +uuid+ is in compact, default or urn formats.  Does not
# validate the layout (RFC 4122 section 4) of the UUID.
def self.validate(uuid)
  return true if uuid =~ /\A[\da-f]{32}\z/
  return true if
    uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/
end

##
# Generate a pseudo MAC address because we have no pure-ruby way
# to know  the MAC  address of the  NIC this system  uses.  Note
# that cheating  with pseudo arresses here  is completely legal:
# see Section 4.5 of RFC4122 for details.
#
# This implementation is shamelessly stolen from
#  https://github.com/spectra/ruby-uuid/blob/master/uuid.rb
# Thanks spectra.
#
def pseudo_mac_address
  sha1 = ::Digest::SHA1.new
  256.times do
    r = [rand(0x100000000)].pack "N"
    sha1.update r
  end
  str = sha1.digest
  r = rand 14 # 20-6
  node = str[r, 6] || str
  if RUBY_VERSION >= "1.9.0"
    nnode = node.bytes.to_a
    nnode[0] |= 0x01
    node = ''
    nnode.each { |s| node << s.chr }
  else
    node[0] |= 0x01 # multicast bit
  end
  node.bytes.collect{|b|b.to_s(16)}.join.hex & 0x7FFFFFFFFFFF
end

##
# Uses system calls to get a mac address
#
def iee_mac_address
  begin
    Mac.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF
  rescue
    0
  end
end

##
# return iee_mac_address if available, pseudo_mac_address otherwise
#
def mac_address
  return iee_mac_address unless iee_mac_address == 0
  return pseudo_mac_address
end

##
# Create a new UUID generator.  You really only need to do this once.
def initialize
  @drift = 0
  @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
  @mutex = Mutex.new

  state_file = self.class.state_file
  if state_file && File.size?(state_file) then
    next_sequence
  else
    @mac = mac_address
    fail "Cannot determine MAC address from any available interface, tried with #{mac_address}" if @mac == 0
    @sequence = rand 0x10000

    # Ensure the mode is respected, even with a restrictive umask
    File.open(state_file, 'w') { |f| f.chmod(self.class.mode) } if state_file && !File.exists?(state_file)

    if state_file
      open_lock 'wb' do |io|
        write_state io
      end
    end
  end
end

##
# Generates a new UUID string using +format+.  See FORMATS for a list of
# supported formats.
def generate(format = :default)
  template = FORMATS[format]

  raise ArgumentError, "invalid UUID format #{format.inspect}" unless template

  # The clock must be monotonically increasing. The clock resolution is at
  # best 100 ns (UUID spec), but practically may be lower (on my setup,
  # around 1ms). If this method is called too fast, we don't have a
  # monotonically increasing clock, so the solution is to just wait.
  #
  # It is possible for the clock to be adjusted backwards, in which case we
  # would end up blocking for a long time. When backward clock is detected,
  # we prevent duplicates by asking for a new sequence number and continue
  # with the new clock.

  clock = @mutex.synchronize do
    clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0

    if clock > @last_clock then
      @drift = 0
      @last_clock = clock
    elsif clock == @last_clock then
      drift = @drift += 1

      if drift < 10000 then
        @last_clock += 1
      else
        Thread.pass
        nil
      end
    else
      next_sequence
      @last_clock = clock
    end
  end until clock

  template % [
      clock        & 0xFFFFFFFF,
     (clock >> 32) & 0xFFFF,
    ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
    @sequence      & 0xFFFF,
    @mac           & 0xFFFFFFFFFFFF
  ]
end

##
# Updates the state file with a new sequence number.
def next_sequence
  if self.class.state_file
    open_lock 'rb+' do |io|
      @mac, @sequence, @last_clock = read_state(io)

      io.rewind
      io.truncate 0

      @sequence += 1

      write_state io
    end
  else
    @sequence += 1
  end
rescue Errno::ENOENT
  open_lock 'w' do |io|
    write_state io
  end
ensure
  @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
  @drift = 0
end

def inspect
  mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':')
  "MAC: #{mac}  Sequence: #{@sequence}"
end

protected

##
# Open the state file with an exclusive lock and access mode +mode+.
def open_lock(mode)
  File.open self.class.state_file, mode, self.class.mode do |io|
    begin
      io.flock File::LOCK_EX
      yield io
    ensure
      io.flock File::LOCK_UN
    end
  end
end

##
# Read the state from +io+
def read_state(io)
  mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT)
  mac = (mac1 << 32) + mac2

  return mac, seq, last_clock
end

##
# Write that state to +io+
def write_state(io)
  mac2 =  @mac        & 0xffffffff
  mac1 = (@mac >> 32) & 0xffff

  io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
end

# You don't have to use this, it's just a good default.
SOCKET_NAME ="/var/lib/uuid.sock"

# With UUID server you don't have to worry about multiple processes
# synchronizing over the state file, calling next_sequence when forking a
# process and other things you're probably not worried about (because
# statistically they're very unlikely to break your code).
#
# But if you are worried about and thought to yourself, "what would a simple
# UUID server look like?", here's the answer.  The protocol is dead simple:
# client sends a byte, server responds with a UUID.  Can use TCP or domain
# sockets.
class Server

  # Create new server.  Nothing interesting happens until you call listen.
  def initialize()
    @generator = UUID.new
  end

  # Start the server listening on the specific address.  Blocks and never
  # returns.  Address can be:
  # - A Socket object
  # - UNIX domain socket name (e.g. /var/run/uuid.sock, must start with /)
  # - IP address, colon, port (e.g. localhost:1337)
  def listen(address)
    sock = bind(address)
    while client = sock.accept
      Thread.start(client) do |socket|
        while socket.read 1
          socket.write @generator.generate
        end
      end
    end
  end

  # Returns UNIXServer or TCPServer from address.  Returns argument if not a
  # string, so can pass through (see #listen).
  def bind(address)
    return address unless String === address
    if address[0] == /
      if File.exist?(address)
        raise ArgumentError, "#{address} is not a socket" unless File.socket?(address)
        File.unlink(address)
      end
      sock = UNIXServer.new(address)
      File.chmod 0666, address
    elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
      sock = TCPServer.new($1, $2.to_i)
    else
      raise ArgumentError, "Don't know how to bind #{address}"
    end
    sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
    sock
  end

end

# Every server needs a client.  Client provides you with the single ultimate
# method: #generate.  Typically you'll use this instead of the local UUID
# generator:
#   UUID.server = UUID::SOCKET_NAME
class Client

  def initialize(address)
    @socket = connect(address)
    at_exit { close }
  end

  # Talks to server and returns new UUID in specified format.
  def generate(format = :default)
    @socket.write "\00""
    uuid = @socket.read(36)
    return uuid if format == :default
    template = FORMATS[format]
    raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
    template % uuid.split("-").map { |p| p.to_i(16) }
  end

  # Returns UNIXSocket or TCPSocket from address.  Returns argument if not a
  # string, so can pass through.
  def connect(address)
    return address unless String === address
    if address[0] == /
      sock = UNIXSocket.new(address)
    elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
      sock = TCPSocket.new($1, $2.to_i)
    else
      raise ArgumentError, "Don't know how to connect to #{address}"
    end
    sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
    sock
  end

  def next_sequence #:nodoc: Stubbed to do nothing.
  end

  def inspect
    @socket ? "Server on #{Socket.unpack_sockaddr_in(@socket.getsockname).reverse!.join(':')}" : "Connection closed"
  end

  # Close the socket.
  def close
    @socket.shutdown if @socket
    @socket = nil
  end

end

end