uuid 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (8) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README +76 -76
  3. data/Rakefile +135 -136
  4. data/bin/uuid-setup +3 -4
  5. data/changelog.txt +6 -0
  6. data/lib/uuid.rb +407 -384
  7. data/test/test-uuid.rb +26 -26
  8. metadata +30 -24
@@ -1,3 +1,9 @@
1
+ Release 1.0.1 (Jul 26, 2006)
2
+
3
+ * Added: Regular expressions to test if a string is a UUID.
4
+ * Changed: When used in ActiveRecord, adds as callback instead of overriding
5
+ save.
6
+
1
7
  Release 1.0.0 (Nov 20, 2005)
2
8
 
3
9
  * Changed: Separated form reliable-msg into its own package.
@@ -1,386 +1,409 @@
1
- #
2
- # = uuid.rb - UUID generator
3
- #
4
- # Author:: Assaf Arkin assaf@labnotes.org
5
- # Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/UuidGenerator
6
- # Copyright:: Copyright (c) 2005 Assaf Arkin
7
- # License:: MIT and/or Creative Commons Attribution-ShareAlike
8
- #
9
- #--
10
- #++
11
-
12
-
1
+ #
2
+ # = uuid.rb - UUID generator
3
+ #
4
+ # Author:: Assaf Arkin assaf@labnotes.org
5
+ # Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/UuidGenerator
6
+ # Copyright:: Copyright (c) 2005,2006 Assaf Arkin
7
+ # License:: MIT and/or Creative Commons Attribution-ShareAlike
8
+
9
+
13
10
  require 'thread'
14
- require 'yaml'
15
- require 'singleton'
16
- require 'logger'
17
-
18
-
19
-
20
- # == Generating UUIDs
21
- #
22
- # Call UUID.new to generate and return a new UUID. The method returns a string in one of three
23
- # formats. The default format is 36 characters long, and contains the 32 hexadecimal octets and
24
- # hyphens separating the various value parts. The <tt>:compact</tt> format omits the hyphens,
25
- # while the <tt>:urn</tt> format adds the <tt>:urn:uuid</tt> prefix.
26
- #
27
- # For example:
28
- # 10.times do
29
- # p UUID.new
30
- # end
31
- #
32
- # ---
33
- # == UUIDs in Brief
34
- #
35
- # UUID (universally unique identifier) are guaranteed to be unique across time and space.
36
- #
37
- # A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and
38
- # a 48-bit node identifier.
39
- #
40
- # The time value is taken from the system clock, and is monotonically incrementing. However,
41
- # since it is possible to set the system clock backward, a sequence number is added. The
42
- # sequence number is incremented each time the UUID generator is started. The combination
43
- # guarantees that identifiers created on the same machine are unique with a high degree of
44
- # probability.
45
- #
46
- # Note that due to the structure of the UUID and the use of sequence number, there is no
47
- # guarantee that UUID values themselves are monotonically incrementing. The UUID value
48
- # cannot itself be used to sort based on order of creation.
49
- #
50
- # To guarantee that UUIDs are unique across all machines in the network, use the IEEE 802
51
- # MAC address of the machine's network interface card as the node identifier. Network interface
52
- # cards have unique MAC address that are 47-bit long (the last bit acts as a broadcast flag).
53
- # Use +ipconfig+ (Windows), or +ifconfig+ (Unix) to find the MAC address (aka physical address)
54
- # of a network card. It takes the form of six pairs of hexadecimal digits, separated by hypen or
55
- # colon, e.g. '<tt>08-0E-46-21-4B-35</tt>'
56
- #
57
- # For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
58
- #
59
- # == Configuring the UUID generator
60
- #
61
- # The UUID generator requires a state file which maintains the MAC address and next sequence
62
- # number to use. By default, the UUID generator will use the file <tt>uuid.state</tt> contained
63
- # in the current directory, or in the installation directory.
64
- #
65
- # Use UUID.config to specify a different location for the UUID state file. If the UUID state file
66
- # does not exist, you can create one manually, or use UUID.config with the options <tt>:sequence</tt>
67
- # and <tt>:mac_addr</tt>.
68
- #
69
- # A UUID state file looks like:
70
- # ---
71
- # last_clock: "0x28227f76122d80"
72
- # mac_addr: 08-0E-46-21-4B-35
73
- # sequence: "0x1639"
74
- #
75
- #
76
- #--
77
- # === Time-based UUID
78
- #
79
- # The UUID specification prescribes the following format for representing UUIDs. Four octets encode
80
- # the low field of the time stamp, two octects encode the middle field of the timestamp, and two
81
- # octets encode the high field of the timestamp with the version number. Two octets encode the
82
- # clock sequence number and six octets encode the unique node identifier.
83
- #
84
- # The timestamp is a 60 bit value holding UTC time as a count of 100 nanosecond intervals since
85
- # October 15, 1582. UUIDs generated in this manner are guaranteed not to roll over until 3400 AD.
86
- #
87
- # The clock sequence is used to help avoid duplicates that could arise when the clock is set backward
88
- # in time or if the node ID changes. Although the system clock is guaranteed to be monotonic, the
89
- # system clock is not guaranteed to be monotonic across system failures. The UUID cannot be sure
90
- # that no UUIDs were generated with timestamps larger than the current timestamp.
91
- #
92
- # If the clock sequence can be determined at initialization, it is incremented by one. The clock sequence
93
- # MUST be originally (i.e. once in the lifetime of a system) initialized to a random number to minimize the
94
- # correlation across systems. The initial value must not be correlated to the node identifier.
95
- #
96
- # The node identifier must be unique for each UUID generator. This is accomplished using the IEEE 802
97
- # network card address. For systems with multiple IEEE 802 addresses, any available address can be used.
98
- # For systems with no IEEE address, a 47 bit random value is used and the multicast bit is set so it will
99
- # never conflict with addresses obtained from network cards.
100
- #
101
- # === UUID state file
102
- #
103
- # The UUID state is contained in the UUID state file. The file name can be specified when configuring
104
- # the UUID generator with UUID.config. The default is to use the file +uuid.state+ in the current directory,
105
- # or the installation directory.
106
- #
107
- # The UUID state file is read once when the UUID generator is first used (or configured). The sequence
108
- # number contained in the UUID is read and used, and the state file is updated to the next sequence
109
- # number. The MAC address is also read from the state file. The current clock time (in 100ns resolution)
110
- # is stored in the state file whenever the sequence number is updated, but is never read.
111
- #
112
- # If the UUID generator detects that the system clock has been moved backwards, it will obtain a new
113
- # sequence in the same manner. So it is possible that the UUID state file will be updated while the
114
- # application is running.
115
- #++
116
- module UUID
117
-
118
- VERSION = '1.0.0'
119
-
120
- PACKAGE = "uuid"
121
-
122
- # Default state file.
123
- STATE_FILE = "uuid.state"
124
-
125
- # Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
126
- CLOCK_MULTIPLIER = 10000000 #:nodoc:
127
-
128
- # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
129
- CLOCK_GAPS = 100000 #:nodoc:
130
-
131
- # Version number stamped into the UUID to identify it as time-based.
132
- VERSION_CLOCK = 0x0100 #:nodoc:
133
-
134
- # Formats supported by the UUID generator.
135
- FORMATS = {:compact=>"%08x%04x%04x%04x%012x", :default=>"%08x-%04x-%04x-%04x-%012x", :urn=>"urn:uuid:%08x-%04x-%04x-%04x-%012x"} #:nodoc:
136
-
137
- # Length (in characters) of UUIDs generated for each of the formats.
138
- FORMATS_LENGTHS = {:compact=>32, :default=>36, :urn=>45} #:nodoc:
139
-
140
- ERROR_INVALID_SEQUENCE = "Invalid sequence number: found '%s', expected 4 hexdecimal digits" #:nodoc:
141
-
142
- ERROR_NOT_A_SEQUENCE = "Not a sequence number: expected integer between 0 and 0xFFFF" #:nodoc:
143
-
144
- ERROR_INVALID_MAC_ADDR = "Invalid MAC address: found '%s', expected a number in the format XX-XX-XX-XX-XX-XX" #:nodoc:
145
-
146
- INFO_INITIALIZED = "Initialized UUID generator with sequence number 0x%04x and MAC address %s" #:nodoc:
147
-
148
- ERROR_INITIALIZED_RANDOM_1 = "Initialized UUID generator with random sequence number/MAC address." #:nodoc:
149
-
150
- ERROR_INITIALIZED_RANDOM_2 = "UUIDs are not guaranteed to be unique. Please create a uuid.state file as soon as possible." #:nodoc:
151
-
152
- IFCONFIG_PATTERN = /[^:\-](?:[0-9A-Za-z][0-9A-Za-z][:\-]){5}[0-9A-Za-z][0-9A-Za-z][^:\-]/ #:nodoc:
153
-
154
- @@mutex = Mutex.new
155
- @@last_clock = @@logger = @@state_file = nil
156
-
157
- # Generates and returns a new UUID string.
158
- #
159
- # The argument +format+ specifies which formatting template to use:
160
- # * <tt>:default</tt> -- Produces 36 characters, including hyphens separating the UUID value parts
161
- # * <tt>:compact</tt> -- Produces a 32 digits (hexadecimal) value with no hyphens
162
- # * <tt>:urn</tt> -- Aadds the prefix <tt>urn:uuid:</tt> to the <tt>:default</tt> format
163
- #
164
- # For example:
165
- # print UUID.new :default
166
- # or just
167
- # print UUID.new
168
- #
169
- # :call-seq:
170
- # UUID.new([format]) -> string
171
- #
172
- def new format = nil
173
- # Determine which format we're using for the UUID string.
174
- template = FORMATS[format || :default] or
175
- raise RuntimeError, "I don't know the format '#{format}'"
176
-
177
- # The clock must be monotonically increasing. The clock resolution is at best 100 ns
178
- # (UUID spec), but practically may be lower (on my setup, around 1ms). If this method
179
- # is called too fast, we don't have a monotonically increasing clock, so the solution is
180
- # to just wait.
181
- # It is possible for the clock to be adjusted backwards, in which case we would end up
182
- # blocking for a long time. When backward clock is detected, we prevent duplicates by
183
- # asking for a new sequence number and continue with the new clock.
184
- clock = @@mutex.synchronize do
185
- # Initialize UUID generator if not already initialized. Uninitizlied UUID generator has no
186
- # last known clock.
187
- next_sequence unless @@last_clock
188
- clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
189
- if clock > @@last_clock
190
- @@drift = 0
191
- @@last_clock = clock
192
- elsif clock = @@last_clock
193
- drift = @@drift += 1
194
- if drift < 10000
195
- @@last_clock += 1
196
- else
197
- Thread.pass
198
- nil
199
- end
200
- else
201
- next_sequence
202
- @@last_clock = clock
203
- end
204
- end while not clock
205
- sprintf template, clock & 0xFFFFFFFF, (clock >> 32)& 0xFFFF, ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
206
- @@sequence & 0xFFFF, @@mac_hex & 0xFFFFFFFFFFFF
207
- end
208
-
209
- alias uuid new
210
- module_function :uuid, :new
211
-
212
- # Configures the UUID generator. Use this method to specify the UUID state file, logger, etc.
213
- #
214
- # The method accepts the following options:
215
- # * <tt>:state_file</tt> -- Specifies the location of the state file. If missing, the default
216
- # is <tt>uuid.state</tt>
217
- # * <tt>:logger<tt> -- The UUID generator will use this logger to report the state information (optional).
218
- # * <tt>:sequence</tt> -- Specifies the sequence number (0 to 0xFFFF) to use. Required to
219
- # create a new state file, ignored if the state file already exists.
220
- # * <tt>:mac_addr</tt> -- Specifies the MAC address (xx-xx-xx-xx-xx) to use. Required to
221
- # create a new state file, ignored if the state file already exists.
222
- #
223
- # For example, to create a new state file:
224
- # UUID.config :state_file=>'my-uuid.state', :sequence=>rand(0x10000), :mac_addr=>'0C-0E-35-41-60-65'
225
- # To use an existing state file and log to +STDOUT+:
226
- # UUID.config :state_file=>'my-uuid.state', :logger=>Logger.new(STDOUT)
227
- #
228
- # :call-seq:
229
- # UUID.config(config)
230
- #
231
- def self.config options
232
- options ||= {}
233
- @@mutex.synchronize do
234
- @@logger = options[:logger]
235
- next_sequence options
236
- end
237
- end
238
-
239
- # Create a uuid.state file by finding the IEEE 802 NIC MAC address for this machine.
240
- # Works for UNIX (ifconfig) and Windows (ipconfig). Creates the uuid.state file in the
241
- # installation directory (typically the GEM's lib).
242
- def self.setup
243
- file = File.expand_path(File.dirname(__FILE__))
244
- file = File.basename(file) == 'lib' ? file = File.join(file, '..', STATE_FILE) : file = File.join(file, STATE_FILE)
245
- file = File.expand_path(file)
246
- if File.exist? file
247
- puts "#{PACKAGE}: Found an existing UUID state file: #{file}"
248
- else
249
- puts "#{PACKAGE}: No UUID state file found, attempting to create one for you:"
250
- # Run ifconfig for UNIX, or ipconfig for Windows.
251
- config = ""
252
- begin
253
- Kernel.open "|ifconfig" do |input|
254
- input.each_line { |line| config << line }
255
- end
256
- rescue
257
- end
258
- begin
259
- Kernel.open "|ipconfig /all" do |input|
260
- input.each_line { |line| config << line }
261
- end
262
- rescue
263
- end
264
-
265
- addresses = config.scan(IFCONFIG_PATTERN).collect { |addr| addr[1..-2] }
266
- if addresses.empty?
267
- puts "Could not find any IEEE 802 NIC MAC addresses for this machine."
268
- puts "You need to create the uuid.state file manually."
269
- else
270
- puts "Found the following IEEE 802 NIC MAC addresses on your computer:"
271
- addresses.each { |addr| puts " #{addr}" }
272
- puts "Selecting the first address #{addresses[0]} for use in your UUID state file."
273
- File.open file, "w" do |output|
274
- output.puts "mac_addr: #{addresses[0]}"
275
- output.puts format("sequence: \"0x%04x\"", rand(0x10000))
276
- end
277
- puts "Created a new UUID state file: #{file}"
278
- end
279
- end
280
- file
281
- end
282
-
283
- private
284
- def self.state plus_one = false
285
- return nil unless @@sequence && @@mac_addr
286
- {"sequence"=>sprintf("0x%04x", (plus_one ? @@sequence + 1 : @@sequence) & 0xFFFF), "last_clock"=>sprintf("0x%x", @@last_clock || (Time.new.to_f * CLOCK_MULTIPLIER).to_i), "mac_addr" => @@mac_addr}
287
- end
288
-
289
- def self.next_sequence config = nil
290
- # If called to advance the sequence number (config is nil), we have a state file that we're able to use.
291
- # If called from configuration, use the specified or default state file.
292
- state_file = (config && config[:state_file]) || @@state_file
293
-
294
- unless state_file
295
- state_file = if File.exist?(STATE_FILE)
296
- STATE_FILE
297
- else
298
- file = File.expand_path(File.dirname(__FILE__))
299
- file = File.basename(file) == 'lib' ? file = File.join(file, '..', STATE_FILE) : file = File.join(file, STATE_FILE)
300
- file = File.expand_path(file)
301
- File.exist?(file) ? file : setup
302
- end
303
- end
304
- begin
305
- File.open state_file, "r+" do |file|
306
- # Lock the file for exclusive access, just to make sure it's not being read while we're
307
- # updating its contents.
308
- file.flock(File::LOCK_EX)
309
- state = YAML::load file
310
- # Get the sequence number. Must be a valid 16-bit hexadecimal value.
311
- sequence = state['sequence']
312
- if sequence
313
- raise RuntimeError, format(ERROR_INVALID_SEQUENCE, sequence) unless sequence.is_a?(String) and sequence =~ /[0-9a-fA-F]{4}/
314
- sequence = sequence.hex & 0xFFFF
315
- else
316
- sequence = rand(0x10000)
317
- end
318
- # Get the MAC address. Must be 6 pairs of hexadecimal octets. Convert MAC address into
319
- # a 48-bit value with the higher bit being zero.
320
- mac_addr = state['mac_addr']
321
- raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless mac_addr.is_a?(String) and mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
322
- mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
323
-
324
- # If everything is OK, proceed to the next step. Grab the sequence number and store
325
- # the new state. Start at beginning of file, and truncate file when done.
326
- @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
327
- file.pos = 0
328
- YAML::dump state(true), file
329
- file.truncate file.pos
330
- end
331
- # Initialized.
332
- if @@logger
333
- @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
334
- else
335
- warn "#{PACKAGE}: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
336
- end
337
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
338
- rescue Errno::ENOENT=>error
339
- if !config
340
- # Generate random values.
341
- @@mac_hex, @@sequence, @@state_file = rand(0x800000000000) | 0xF00000000000, rand(0x10000), nil
342
- # Initialized.
343
- if @@logger
344
- @@logger.error ERROR_INITIALIZED_RANDOM_1
345
- @@logger.error ERROR_INITIALIZED_RANDOM_2
346
- else
347
- warn "#{PACKAGE}: " + ERROR_INITIALIZED_RANDOM_1
348
- warn "#{PACKAGE}: " + ERROR_INITIALIZED_RANDOM_2
349
- end
350
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
351
- else
352
- # No state file. If we were called for configuration with valid sequence number and MAC address,
353
- # attempt to create state file. See code above for how we interpret these values.
354
- sequence = config[:sequence]
355
- raise RuntimeError, format(ERROR_NOT_A_SEQUENCE, sequence) unless sequence.is_a?(Integer)
356
- sequence &= 0xFFFF
357
- mac_addr = config[:mac_addr]
358
- raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless mac_addr.is_a?(String) and mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
359
- mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
360
- File.open state_file, "w" do |file|
361
- file.flock(File::LOCK_EX)
362
- @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
363
- file.pos = 0
364
- YAML::dump state(true), file
365
- file.truncate file.pos
366
- end
367
- # Initialized.
368
- if @@logger
369
- @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
370
- else
371
- warn "#{PACKAGE}: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
372
- end
373
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
374
- end
375
- rescue Exception=>error
376
- @@last_clock = nil
377
- raise error
378
- end
379
- end
380
-
381
- end
382
-
383
-
384
- if __FILE__ == $0
385
- UUID.setup
11
+ require 'yaml'
12
+ require 'singleton'
13
+ require 'logger'
14
+
15
+
16
+
17
+ # == Generating UUIDs
18
+ #
19
+ # Call UUID.new to generate and return a new UUID. The method returns a string in one of three
20
+ # formats. The default format is 36 characters long, and contains the 32 hexadecimal octets and
21
+ # hyphens separating the various value parts. The <tt>:compact</tt> format omits the hyphens,
22
+ # while the <tt>:urn</tt> format adds the <tt>:urn:uuid</tt> prefix.
23
+ #
24
+ # For example:
25
+ # 10.times do
26
+ # p UUID.new
27
+ # end
28
+ #
29
+ # ---
30
+ # == UUIDs in Brief
31
+ #
32
+ # UUID (universally unique identifier) are guaranteed to be unique across time and space.
33
+ #
34
+ # A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and
35
+ # a 48-bit node identifier.
36
+ #
37
+ # The time value is taken from the system clock, and is monotonically incrementing. However,
38
+ # since it is possible to set the system clock backward, a sequence number is added. The
39
+ # sequence number is incremented each time the UUID generator is started. The combination
40
+ # guarantees that identifiers created on the same machine are unique with a high degree of
41
+ # probability.
42
+ #
43
+ # Note that due to the structure of the UUID and the use of sequence number, there is no
44
+ # guarantee that UUID values themselves are monotonically incrementing. The UUID value
45
+ # cannot itself be used to sort based on order of creation.
46
+ #
47
+ # To guarantee that UUIDs are unique across all machines in the network, use the IEEE 802
48
+ # MAC address of the machine's network interface card as the node identifier. Network interface
49
+ # cards have unique MAC address that are 47-bit long (the last bit acts as a broadcast flag).
50
+ # Use +ipconfig+ (Windows), or +ifconfig+ (Unix) to find the MAC address (aka physical address)
51
+ # of a network card. It takes the form of six pairs of hexadecimal digits, separated by hypen or
52
+ # colon, e.g. '<tt>08-0E-46-21-4B-35</tt>'
53
+ #
54
+ # For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
55
+ #
56
+ # == Configuring the UUID generator
57
+ #
58
+ # The UUID generator requires a state file which maintains the MAC address and next sequence
59
+ # number to use. By default, the UUID generator will use the file <tt>uuid.state</tt> contained
60
+ # in the current directory, or in the installation directory.
61
+ #
62
+ # Use UUID.config to specify a different location for the UUID state file. If the UUID state file
63
+ # does not exist, you can create one manually, or use UUID.config with the options <tt>:sequence</tt>
64
+ # and <tt>:mac_addr</tt>.
65
+ #
66
+ # A UUID state file looks like:
67
+ # ---
68
+ # last_clock: "0x28227f76122d80"
69
+ # mac_addr: 08-0E-46-21-4B-35
70
+ # sequence: "0x1639"
71
+ #
72
+ #
73
+ #--
74
+ # === Time-based UUID
75
+ #
76
+ # The UUID specification prescribes the following format for representing UUIDs. Four octets encode
77
+ # the low field of the time stamp, two octects encode the middle field of the timestamp, and two
78
+ # octets encode the high field of the timestamp with the version number. Two octets encode the
79
+ # clock sequence number and six octets encode the unique node identifier.
80
+ #
81
+ # The timestamp is a 60 bit value holding UTC time as a count of 100 nanosecond intervals since
82
+ # October 15, 1582. UUIDs generated in this manner are guaranteed not to roll over until 3400 AD.
83
+ #
84
+ # The clock sequence is used to help avoid duplicates that could arise when the clock is set backward
85
+ # in time or if the node ID changes. Although the system clock is guaranteed to be monotonic, the
86
+ # system clock is not guaranteed to be monotonic across system failures. The UUID cannot be sure
87
+ # that no UUIDs were generated with timestamps larger than the current timestamp.
88
+ #
89
+ # If the clock sequence can be determined at initialization, it is incremented by one. The clock sequence
90
+ # MUST be originally (i.e. once in the lifetime of a system) initialized to a random number to minimize the
91
+ # correlation across systems. The initial value must not be correlated to the node identifier.
92
+ #
93
+ # The node identifier must be unique for each UUID generator. This is accomplished using the IEEE 802
94
+ # network card address. For systems with multiple IEEE 802 addresses, any available address can be used.
95
+ # For systems with no IEEE address, a 47 bit random value is used and the multicast bit is set so it will
96
+ # never conflict with addresses obtained from network cards.
97
+ #
98
+ # === UUID state file
99
+ #
100
+ # The UUID state is contained in the UUID state file. The file name can be specified when configuring
101
+ # the UUID generator with UUID.config. The default is to use the file +uuid.state+ in the current directory,
102
+ # or the installation directory.
103
+ #
104
+ # The UUID state file is read once when the UUID generator is first used (or configured). The sequence
105
+ # number contained in the UUID is read and used, and the state file is updated to the next sequence
106
+ # number. The MAC address is also read from the state file. The current clock time (in 100ns resolution)
107
+ # is stored in the state file whenever the sequence number is updated, but is never read.
108
+ #
109
+ # If the UUID generator detects that the system clock has been moved backwards, it will obtain a new
110
+ # sequence in the same manner. So it is possible that the UUID state file will be updated while the
111
+ # application is running.
112
+ #++
113
+ module UUID
114
+
115
+ VERSION = '1.0.1'
116
+
117
+ PACKAGE = "uuid"
118
+
119
+ # Default state file.
120
+ STATE_FILE = "uuid.state"
121
+
122
+ # Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
123
+ CLOCK_MULTIPLIER = 10000000 #:nodoc:
124
+
125
+ # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
126
+ CLOCK_GAPS = 100000 #:nodoc:
127
+
128
+ # Version number stamped into the UUID to identify it as time-based.
129
+ VERSION_CLOCK = 0x0100 #:nodoc:
130
+
131
+ # Formats supported by the UUID generator.
132
+ FORMATS = {:compact=>"%08x%04x%04x%04x%012x", :default=>"%08x-%04x-%04x-%04x-%012x", :urn=>"urn:uuid:%08x-%04x-%04x-%04x-%012x"} #:nodoc:
133
+
134
+ # Length (in characters) of UUIDs generated for each of the formats.
135
+ FORMATS_LENGTHS = {:compact=>32, :default=>36, :urn=>45} #:nodoc:
136
+
137
+ ERROR_INVALID_SEQUENCE = "Invalid sequence number: found '%s', expected 4 hexdecimal digits" #:nodoc:
138
+
139
+ ERROR_NOT_A_SEQUENCE = "Not a sequence number: expected integer between 0 and 0xFFFF" #:nodoc:
140
+
141
+ ERROR_INVALID_MAC_ADDR = "Invalid MAC address: found '%s', expected a number in the format XX-XX-XX-XX-XX-XX" #:nodoc:
142
+
143
+ INFO_INITIALIZED = "Initialized UUID generator with sequence number 0x%04x and MAC address %s" #:nodoc:
144
+
145
+ ERROR_INITIALIZED_RANDOM_1 = "Initialized UUID generator with random sequence number/MAC address." #:nodoc:
146
+
147
+ ERROR_INITIALIZED_RANDOM_2 = "UUIDs are not guaranteed to be unique. Please create a uuid.state file as soon as possible." #:nodoc:
148
+
149
+ IFCONFIG_PATTERN = /[^:\-](?:[0-9A-Za-z][0-9A-Za-z][:\-]){5}[0-9A-Za-z][0-9A-Za-z][^:\-]/ #:nodoc:
150
+
151
+
152
+ # Regular expression to identify a 36 character UUID. Can be used for a partial match.
153
+ REGEXP = /[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}/
154
+
155
+ # Regular expression to identify a 36 character UUID. Can only be used for a full match.
156
+ REGEXP_FULL = /^[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}$/
157
+
158
+
159
+ @@mutex = Mutex.new
160
+ @@last_clock = nil
161
+ @@logger = nil
162
+ @@state_file = nil
163
+
164
+
165
+ # Generates and returns a new UUID string.
166
+ #
167
+ # The argument +format+ specifies which formatting template to use:
168
+ # * <tt>:default</tt> -- Produces 36 characters, including hyphens separating the UUID value parts
169
+ # * <tt>:compact</tt> -- Produces a 32 digits (hexadecimal) value with no hyphens
170
+ # * <tt>:urn</tt> -- Aadds the prefix <tt>urn:uuid:</tt> to the <tt>:default</tt> format
171
+ #
172
+ # For example:
173
+ # print UUID.new :default
174
+ # or just
175
+ # print UUID.new
176
+ #
177
+ # :call-seq:
178
+ # UUID.new([format]) -> string
179
+ #
180
+ def new format = nil
181
+ # Determine which format we're using for the UUID string.
182
+ template = FORMATS[format || :default] or
183
+ raise RuntimeError, "I don't know the format '#{format}'"
184
+
185
+ # The clock must be monotonically increasing. The clock resolution is at best 100 ns
186
+ # (UUID spec), but practically may be lower (on my setup, around 1ms). If this method
187
+ # is called too fast, we don't have a monotonically increasing clock, so the solution is
188
+ # to just wait.
189
+ # It is possible for the clock to be adjusted backwards, in which case we would end up
190
+ # blocking for a long time. When backward clock is detected, we prevent duplicates by
191
+ # asking for a new sequence number and continue with the new clock.
192
+ clock = @@mutex.synchronize do
193
+ # Initialize UUID generator if not already initialized. Uninitizlied UUID generator has no
194
+ # last known clock.
195
+ next_sequence unless @@last_clock
196
+ clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
197
+ if clock > @@last_clock
198
+ @@drift = 0
199
+ @@last_clock = clock
200
+ elsif clock = @@last_clock
201
+ drift = @@drift += 1
202
+ if drift < 10000
203
+ @@last_clock += 1
204
+ else
205
+ Thread.pass
206
+ nil
207
+ end
208
+ else
209
+ next_sequence
210
+ @@last_clock = clock
211
+ end
212
+ end while not clock
213
+ sprintf template, clock & 0xFFFFFFFF, (clock >> 32)& 0xFFFF, ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
214
+ @@sequence & 0xFFFF, @@mac_hex & 0xFFFFFFFFFFFF
215
+ end
216
+
217
+
218
+ alias uuid new
219
+ module_function :uuid, :new
220
+
221
+
222
+ # Configures the UUID generator. Use this method to specify the UUID state file, logger, etc.
223
+ #
224
+ # The method accepts the following options:
225
+ # * <tt>:state_file</tt> -- Specifies the location of the state file. If missing, the default
226
+ # is <tt>uuid.state</tt>
227
+ # * <tt>:logger<tt> -- The UUID generator will use this logger to report the state information (optional).
228
+ # * <tt>:sequence</tt> -- Specifies the sequence number (0 to 0xFFFF) to use. Required to
229
+ # create a new state file, ignored if the state file already exists.
230
+ # * <tt>:mac_addr</tt> -- Specifies the MAC address (xx-xx-xx-xx-xx) to use. Required to
231
+ # create a new state file, ignored if the state file already exists.
232
+ #
233
+ # For example, to create a new state file:
234
+ # UUID.config :state_file=>'my-uuid.state', :sequence=>rand(0x10000), :mac_addr=>'0C-0E-35-41-60-65'
235
+ # To use an existing state file and log to +STDOUT+:
236
+ # UUID.config :state_file=>'my-uuid.state', :logger=>Logger.new(STDOUT)
237
+ #
238
+ # :call-seq:
239
+ # UUID.config(config)
240
+ #
241
+ def self.config options
242
+ options ||= {}
243
+ @@mutex.synchronize do
244
+ @@logger = options[:logger]
245
+ next_sequence options
246
+ end
247
+ end
248
+
249
+
250
+ # Create a uuid.state file by finding the IEEE 802 NIC MAC address for this machine.
251
+ # Works for UNIX (ifconfig) and Windows (ipconfig). Creates the uuid.state file in the
252
+ # installation directory (typically the GEM's lib).
253
+ def self.setup
254
+ file = File.expand_path(File.dirname(__FILE__))
255
+ file = File.basename(file) == 'lib' ? file = File.join(file, '..', STATE_FILE) : file = File.join(file, STATE_FILE)
256
+ file = File.expand_path(file)
257
+ if File.exist? file
258
+ puts "#{PACKAGE}: Found an existing UUID state file: #{file}"
259
+ else
260
+ puts "#{PACKAGE}: No UUID state file found, attempting to create one for you:"
261
+ # Run ifconfig for UNIX, or ipconfig for Windows.
262
+ config = ""
263
+ Kernel.open "|ifconfig" do |input|
264
+ input.each_line { |line| config << line }
265
+ end rescue nil
266
+ Kernel.open "|ipconfig /all" do |input|
267
+ input.each_line { |line| config << line }
268
+ end rescue nil
269
+
270
+ addresses = config.scan(IFCONFIG_PATTERN).collect { |addr| addr[1..-2] }
271
+ if addresses.empty?
272
+ puts "Could not find any IEEE 802 NIC MAC addresses for this machine."
273
+ puts "You need to create the uuid.state file manually."
274
+ else
275
+ puts "Found the following IEEE 802 NIC MAC addresses on your computer:"
276
+ addresses.each { |addr| puts " #{addr}" }
277
+ puts "Selecting the first address #{addresses[0]} for use in your UUID state file."
278
+ File.open file, "w" do |output|
279
+ output.puts "mac_addr: #{addresses[0]}"
280
+ output.puts format("sequence: \"0x%04x\"", rand(0x10000))
281
+ end
282
+ puts "Created a new UUID state file: #{file}"
283
+ end
284
+ end
285
+ file
286
+ end
287
+
288
+
289
+ private
290
+ def self.state plus_one = false
291
+ return nil unless @@sequence && @@mac_addr
292
+ { "sequence"=>sprintf("0x%04x", (plus_one ? @@sequence + 1 : @@sequence) & 0xFFFF),
293
+ "last_clock"=>sprintf("0x%x", @@last_clock || (Time.new.to_f * CLOCK_MULTIPLIER).to_i),
294
+ "mac_addr" => @@mac_addr }
295
+ end
296
+
297
+
298
+ def self.next_sequence config = nil
299
+ # If called to advance the sequence number (config is nil), we have a state file that we're able to use.
300
+ # If called from configuration, use the specified or default state file.
301
+ state_file = (config && config[:state_file]) || @@state_file
302
+
303
+ unless state_file
304
+ if File.exist?(STATE_FILE)
305
+ state_file = STATE_FILE
306
+ else
307
+ file = File.expand_path(File.dirname(__FILE__))
308
+ file = File.basename(file) == 'lib' ? file = File.join(file, '..', STATE_FILE) : file = File.join(file, STATE_FILE)
309
+ file = File.expand_path(file)
310
+ state_file = File.exist?(file) ? file : setup
311
+ end
312
+ end
313
+ begin
314
+ File.open state_file, "r+" do |file|
315
+ # Lock the file for exclusive access, just to make sure it's not being read while we're
316
+ # updating its contents.
317
+ file.flock(File::LOCK_EX)
318
+ state = YAML::load file
319
+ # Get the sequence number. Must be a valid 16-bit hexadecimal value.
320
+ sequence = state['sequence']
321
+ if sequence
322
+ raise RuntimeError, format(ERROR_INVALID_SEQUENCE, sequence) unless
323
+ sequence.is_a?(String) and sequence =~ /[0-9a-fA-F]{4}/
324
+ sequence = sequence.hex & 0xFFFF
325
+ else
326
+ sequence = rand(0x10000)
327
+ end
328
+ # Get the MAC address. Must be 6 pairs of hexadecimal octets. Convert MAC address into
329
+ # a 48-bit value with the higher bit being zero.
330
+ mac_addr = state['mac_addr']
331
+ raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless
332
+ mac_addr.is_a?(String) and mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
333
+ mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
334
+
335
+ # If everything is OK, proceed to the next step. Grab the sequence number and store
336
+ # the new state. Start at beginning of file, and truncate file when done.
337
+ @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
338
+ file.pos = 0
339
+ YAML::dump state(true), file
340
+ file.truncate file.pos
341
+ end
342
+ # Initialized.
343
+ if @@logger
344
+ @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
345
+ else
346
+ warn "#{PACKAGE}: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
347
+ end
348
+ @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
349
+ rescue Errno::ENOENT=>error
350
+ if !config
351
+ # Generate random values.
352
+ @@mac_hex, @@sequence, @@state_file = rand(0x800000000000) | 0xF00000000000, rand(0x10000), nil
353
+ # Initialized.
354
+ if @@logger
355
+ @@logger.error ERROR_INITIALIZED_RANDOM_1
356
+ @@logger.error ERROR_INITIALIZED_RANDOM_2
357
+ else
358
+ warn "#{PACKAGE}: " + ERROR_INITIALIZED_RANDOM_1
359
+ warn "#{PACKAGE}: " + ERROR_INITIALIZED_RANDOM_2
360
+ end
361
+ @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
362
+ else
363
+ # No state file. If we were called for configuration with valid sequence number and MAC address,
364
+ # attempt to create state file. See code above for how we interpret these values.
365
+ sequence = config[:sequence]
366
+ raise RuntimeError, format(ERROR_NOT_A_SEQUENCE, sequence) unless sequence.is_a?(Integer)
367
+ sequence &= 0xFFFF
368
+ mac_addr = config[:mac_addr]
369
+ raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless
370
+ mac_addr.is_a?(String) and mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
371
+ mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
372
+ File.open state_file, "w" do |file|
373
+ file.flock(File::LOCK_EX)
374
+ @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
375
+ file.pos = 0
376
+ YAML::dump state(true), file
377
+ file.truncate file.pos
378
+ end
379
+ # Initialized.
380
+ if @@logger
381
+ @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
382
+ else
383
+ warn "#{PACKAGE}: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
384
+ end
385
+ @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
386
+ end
387
+ rescue Exception=>error
388
+ @@last_clock = nil
389
+ raise error
390
+ end
391
+ end
392
+
393
+ end
394
+
395
+
396
+ if defined?(ActiveRecord)
397
+ class ActiveRecord::Base
398
+
399
+ def self.uuid_primary_key
400
+ before_create { |record| record.id = UUID.new unless record.id }
401
+ end
402
+
403
+ end
404
+ end
405
+
406
+
407
+ if __FILE__ == $0
408
+ UUID.setup
386
409
  end