Module: UUID::NCName

Defined in:
lib/uuid/ncname/version.rb,
lib/uuid/ncname.rb

Defined Under Namespace

Classes: AmbiguousToken

Constant Summary collapse

VERSION =
"0.4.1"

Class Method Summary collapse

Class Method Details

.from_ncname(ncname, radix: nil, format: :str, version: nil, align: nil, validate: false) ⇒ String?

Converts an NCName-encoded UUID back to its canonical representation. Will return nil if the input doesn't match the radix (if supplied) or is otherwise malformed.

Parameters:

  • ncname (#to_s)

    an NCName-encoded UUID, either a 22-character (Base64) variant, or a 26-character (Base32) variant.

  • radix (nil, 32, 58, 64) (defaults to: nil)

    Optional radix; will use a heuristic if omitted.

  • format (:str, :urn, :hex, :b64, :bin) (defaults to: :str)

    An optional formatting parameter; defaults to :str, the canonical string representation.

  • version (0, 1) (defaults to: nil)

    See ::to_ncname. Defaults to 1.

  • align (nil, true, false) (defaults to: nil)

    See ::to_ncname for details. Setting this parameter to nil, the default, will cause the decoder to detect the alignment state from the identifier.

  • validate (false, true) (defaults to: false)

    Check that the ninth (the variant) octet is correctly masked after decoding.

Returns:

  • (String, nil)

    The corresponding UUID or nil if the input is malformed.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/uuid/ncname.rb', line 291

def self.from_ncname ncname,
    radix: nil, format: :str, version: nil, align: nil, validate: false
  raise 'Format must be symbol-able' unless format.respond_to? :to_sym
  raise "Invalid format #{format}" unless FORMAT[format]
  raise 'Align must be true, false, or nil' unless
    [true, false, nil].include? align

  # XXX remove this when appropriate
  # version = warn_version version
  version = assert_version version

  return unless ncname and ncname.respond_to? :to_s

  ncname = ncname.to_s.strip.gsub(/\s+/, '')
  match  = MATCH.match(ncname) or return
  return if align and !/[A-Pa-p]$/.match? ncname # MATCH is lax

  # determine the radix from the input
  if radix
    raise ArgumentError, "Radix must be 32, 58, or 64, not #{radix}" unless
      [32, 58, 64].any? radix
    return unless { 32 => 26, 58 => 23, 64 => 22 }[radix] == ncname.length
  else
    radix = { 26 => 32, 23 => 58, 22 => 64}[ncname.length] or
      raise ArgumentError,
      "Not sure what to do with an identifier of length #{ncname.length}."
  end

  # note MATCH separates the variant
  uuidver, *content = match.captures
  content = content.join

  align   = !!(/[A-Pa-p]$/.match? content) if align.nil?
  uuidver = decode_version uuidver
  content = DECODE[radix].call content, align

  bin = TRANSFORM[version][1].call uuidver, content

  # double-check the variant (high-order bits have to be 10)
  return if validate and bin[8].ord >> 6 != 2

  FORMAT[format].call bin
end

.from_ncname_32(ncname, format: :str, version: nil, align: nil) ⇒ String?

Shorthand for conversion from the Base32 version

Parameters:

  • ncname (#to_s)

    The Base32 variant of the NCName-encoded UUID

  • format (:str, :hex, :b64, :bin) (defaults to: :str)

    The format

  • version (0, 1) (defaults to: nil)

    See ::to_ncname.

  • align (true, false) (defaults to: nil)

    See ::to_ncname.

Returns:

  • (String, nil)

    The corresponding UUID or nil if the input is malformed.



426
427
428
429
# File 'lib/uuid/ncname.rb', line 426

def self.from_ncname_32 ncname, format: :str, version: nil, align: nil
  from_ncname ncname,
    radix: 32, format: format, version: version, align: align
end

.from_ncname_58(ncname, format: :str, version: nil, align: nil) ⇒ String?

Shorthand for conversion from the Base58 version

Parameters:

  • ncname (#to_s)

    The Base58 variant of the NCName-encoded UUID

  • format (:str, :hex, :b64, :bin) (defaults to: :str)

    The format

  • version (0, 1) (defaults to: nil)

    See ::to_ncname.

  • align (true, false) (defaults to: nil)

    See ::to_ncname.

Returns:

  • (String, nil)

    The corresponding UUID or nil if the input is malformed.



394
395
396
397
# File 'lib/uuid/ncname.rb', line 394

def self.from_ncname_58 ncname, format: :str, version: nil, align: nil
  from_ncname ncname,
    radix: 58, format: format, version: version, align: align
end

.from_ncname_64(ncname, format: :str, version: nil, align: nil) ⇒ String?

Shorthand for conversion from the Base64 version

Parameters:

  • ncname (#to_s)

    The Base64 variant of the NCName-encoded UUID

  • format (:str, :hex, :b64, :bin) (defaults to: :str)

    The format

  • version (0, 1) (defaults to: nil)

    See ::to_ncname.

  • align (true, false) (defaults to: nil)

    See ::to_ncname.

Returns:

  • (String, nil)

    The corresponding UUID or nil if the input is malformed.



362
363
364
365
# File 'lib/uuid/ncname.rb', line 362

def self.from_ncname_64 ncname, format: :str, version: nil, align: nil
  from_ncname ncname,
    radix: 64, format: format, version: version, align: align
end

.to_ncname(uuid, radix: 64, version: nil, align: true) ⇒ String

Converts a UUID (or object that when converted to a string looks like a UUID) to an NCName. By default it produces the Base64 variant.

Parameters:

  • uuid (#to_s)

    whatever it is, it had better look like a UUID. This includes UUID objects, URNs, 32-character hex strings, 16-byte binary strings, etc.

  • radix (32, 64) (defaults to: 64)

    either the number 32 or the number 64.

  • version (0, 1) (defaults to: nil)

    An optional formatting version, where 0 is the naïve original version and 1 moves the variant nybble out to the end of the identifier. The default version is 1.

  • align (true, false) (defaults to: true)

    Optional directive to treat the terminating character as aligned to the numerical base of the representation. Since the version nybble is removed from the string and the first 120 bits divide evenly into both Base32 and Base64, the overhang is only ever 4 bits. This means that when the terminating character is aligned, it will always be in the range of the letters A through P in (the RFC 3548/4648 representations of) both Base32 and Base64. When version is 1 and the terminating character is aligned, RFC4122-compliant UUIDs will always terminate with I, J, K, or L. Defaults to true.

Returns:

  • (String)

    The NCName-formatted UUID.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/uuid/ncname.rb', line 230

def self.to_ncname uuid, radix: 64, version: nil, align: true
  raise 'Radix must be either 32, 58, or 64' unless
    [32, 58, 64].include? radix
  raise 'UUID must be something stringable' if uuid.nil? or
    not uuid.respond_to? :to_s
  align = !!align # coerce to a boolean

  # XXX remove this when appropriate
  # version = warn_version(version)
  version = assert_version version

  uuid = uuid.to_s
  bin  = nil

  if uuid.length == 16
    bin = uuid
  else
    uuid = uuid.gsub(/\s+/, '')
    if (m = /^(?:urn:uuid:)?([0-9A-Fa-f-]{32,})$/.match(uuid))
      bin = [m[1].tr('-', '')].pack 'H*'
    elsif (m = /^([0-9A-Za-z+\/_-]+=*)$/.match(uuid))
      match = m[1].tr('-_', '+/')
      bin = ::Base64.decode64(match)
    else
      raise "Not sure what to do with #{uuid}"
    end
  end

  raise 'Binary representation of UUID is shorter than 16 bytes' if
    bin.length < 16

  uuidver, content = TRANSFORM[version].first.call bin[0, 16]

  encode_version(uuidver, radix) + ENCODE[radix].call(content, align)
end

.to_ncname_32(uuid, version: nil, align: true) ⇒ String

Shorthand for conversion to the Base32 version

Parameters:

  • uuid (#to_s)

    The UUID

  • version (0, 1) (defaults to: nil)

    See ::to_ncname.

  • align (true, false) (defaults to: true)

    See ::to_ncname.

Returns:

  • (String)

    The Base32-encoded NCName



409
410
411
# File 'lib/uuid/ncname.rb', line 409

def self.to_ncname_32 uuid, version: nil, align: true
  to_ncname uuid, radix: 32, version: version, align: align
end

.to_ncname_58(uuid, version: nil, align: true) ⇒ String

Shorthand for conversion to the Base58 version

Parameters:

  • uuid (#to_s)

    The UUID

  • version (0, 1) (defaults to: nil)

    See ::to_ncname.

  • align (true, false) (defaults to: true)

    See ::to_ncname.

Returns:

  • (String)

    The Base58-encoded NCName



377
378
379
# File 'lib/uuid/ncname.rb', line 377

def self.to_ncname_58 uuid, version: nil, align: true
  to_ncname uuid, radix: 58, version: version, align: align
end

.to_ncname_64(uuid, version: nil, align: true) ⇒ String

Shorthand for conversion to the Base64 version

Parameters:

  • uuid (#to_s)

    The UUID

  • version (0, 1) (defaults to: nil)

    See ::to_ncname.

  • align (true, false) (defaults to: true)

    See ::to_ncname.

Returns:

  • (String)

    The Base64-encoded NCName



345
346
347
# File 'lib/uuid/ncname.rb', line 345

def self.to_ncname_64 uuid, version: nil, align: true
  to_ncname uuid, version: version, align: align
end

.valid?(token, strict: false) ⇒ false, ...

Note:

Version 1 tokens always end with I, J, K, or L (with base32 being case-insensitive), so tokens that end in something else will always be version 0.

Test if the given token is a UUID NCName, with a hint to its version. This method can positively identify a token as a UUID NCName, but there is a small subset of UUIDs which will produce tokens which are valid in both versions. The method returns false if the token is invalid, otherwise it returns 0 or 1 for the guessed version.

Parameters:

  • token (#to_s)

    The token to test

  • strict (false, true) (defaults to: false)

Returns:

  • (false, 0, 1)


448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/uuid/ncname.rb', line 448

def self.valid? token, strict: false
  token = token.to_s
  if MATCH.match? token
    # false is definitely version zero but true is only maybe version 1
    version = /^(?:.{21}[I-L]|.{25}[I-Li-l])$/.match(token) ? 1 : 0

    # try decoding with validation on
    uu = from_ncname token, version: version, validate: true

    # note that version 1 will always return something because the
    # method of detecting it is a version 1 also happens to be the
    # method of determining whether or not it is valid.
    return false unless uu

    if version == 1 and strict
      # but we can also check if the input is a valid version 0
      u0 = from_ncname token, version: 0, validate: true
      raise AmbiguousToken.new(token, v0: u0, v1: uu) if u0
    end

    version
  else
    false
  end
end