class UPnP::UUID

This UUID class is here to make Assaf Arkin’s uuid gem not write to $stdout. Used under MIT license (see source code).

To generate a UUID:

UUID.setup
uuid = UUID.new
uuid.generate

Constants

CLOCK_GAPS

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

CLOCK_MULTIPLIER

Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)

FORMATS

Formats supported by the UUID generator.

:default

Produces 36 characters, including hyphens separating the UUID value parts

:compact

Produces a 32 digits (hexadecimal) value with no hyphens

:urn

Adds the prefix urn:uuid: to the :default format

NIC_FILE

File holding the NIC MAC address

VERSION_CLOCK

Version number stamped into the UUID to identify it as time-based.

Public Class Methods

generate(nic_file = NIC_FILE) click to toggle source

Sets up the UUID class generates a UUID in the default format.

# File lib/UPnP/UUID.rb, line 82
def self.generate(nic_file = NIC_FILE)
  return @uuid.generate if @uuid
  setup nic_file
  @uuid = new
  @uuid.generate
end
new(nic_file = NIC_FILE) click to toggle source

Creates a new UUID generator using the NIC stored in NIC_FILE.

# File lib/UPnP/UUID.rb, line 119
def initialize(nic_file = NIC_FILE)
  if File.exist? nic_file then
    address = File.read nic_file

    raise Error, "invalid MAC address #{address}" unless
      address =~ %r([\da-f]{2}[:\-]){5}[\da-f]{2}/
    @address = address.scan(%r[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
  else
    @address = rand(0x800000000000) | 0xF00000000000
  end

  @drift = 0
  @last_clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i
  @mutex = Mutex.new
  @sequence = rand 0x10000
end
setup(nic_file = NIC_FILE) click to toggle source

Discovers the NIC MAC address and saves it to nic_file. Works for UNIX (ifconfig) and Windows (ipconfig).

# File lib/UPnP/UUID.rb, line 93
def self.setup(nic_file = NIC_FILE)
  nic_file = File.expand_path nic_file

  return if File.exist? nic_file

  FileUtils.mkdir_p File.dirname(nic_file)

  # Run ifconfig for UNIX, or ipconfig for Windows.
  config = ''
  Dir.chdir Dir.tmpdir do
    config << %xifconfig 2>/dev/null`
    config << %xipconfig /all 2>NUL`
  end

  addresses = config.scan(%r[^:\-](?:[\da-z][\da-z][:\-]){5}[\da-z][\da-z][^:\-]/)
  addresses = addresses.map { |addr| addr[1..-2] }

  raise Error, 'MAC address not found via ifconfig or ipconfig' if
    addresses.empty?

  open nic_file, 'w' do |io| io.write addresses.first end
end

Public Instance Methods

generate(format = :default) click to toggle source

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

# File lib/UPnP/UUID.rb, line 140
def generate(format = :default)
  template = FORMATS[format]

  raise ArgumentError, "unknown UUID format #{format.inspect}" if
    template.nil?

  # 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
        @last_clock += 1
      else
        Thread.pass
        nil
      end
    else
      @sequence = rand 0x10000
      @last_clock = clock
    end
  end while not clock

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