Class: UPnP::UUID

Inherits:
Object
  • Object
show all
Defined in:
lib/UPnP/UUID.rb

Overview

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

Constant Summary collapse

NIC_FILE =

File holding the NIC MAC address

'~/.UPnP/uuid_mac_address'
CLOCK_MULTIPLIER =

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

10000000
CLOCK_GAPS =

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

100000
VERSION_CLOCK =

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

0x0100
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

{
  :compact => '%08x%04x%04x%04x%012x',
  :default => '%08x-%04x-%04x-%04x-%012x',
  :urn     => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(nic_file = NIC_FILE) ⇒ UUID

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



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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 =~ /([\da-f]{2}[:\-]){5}[\da-f]{2}/i
    @address = address.scan(/[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

Class Method Details

.generate(nic_file = NIC_FILE) ⇒ Object

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



82
83
84
85
86
87
# 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

.setup(nic_file = NIC_FILE) ⇒ Object

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

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 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 << `ifconfig 2>/dev/null`
    config << `ipconfig /all 2>NUL`
  end

  addresses = config.scan(/[^:\-](?:[\da-z][\da-z][:\-]){5}[\da-z][\da-z][^:\-]/i)
  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

Instance Method Details

#generate(format = :default) ⇒ Object

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

Raises:

  • (ArgumentError)


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# 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