uuid 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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