Module: Identifold

Defined in:
lib/identifold.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

DATA =
"0123456789ABCDEFGHJKMNPQRSTVWXYZ"
CHECK =
DATA + "*~$=U"
TYPE_ID =
"0123456789abcdefghjkmnpqrstvwxyz"

Class Method Summary collapse

Class Method Details

.check_symbol(payload, sequential) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/identifold.rb', line 66

def check_symbol(payload, sequential)
  alphabet = sequential ? "0123456789" : DATA
  base = sequential ? 10 : 32
  remainder = 0
  payload.each_char do |symbol|
    position = alphabet.index(symbol)
    raise Error, "invalid_ref_symbol" unless position

    remainder = (remainder * base + position) % 37
  end
  CHECK[remainder]
end

.create_reference_candidate(registry, namespace, random_bytes) ⇒ Object

Raises:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/identifold.rb', line 79

def create_reference_candidate(registry, namespace, random_bytes)
  reference = find_namespace(registry, namespace)["reference"]
  raise Error, "unknown_namespace" unless reference && reference["strategy"] == "random"

  length = profile_length(reference["profile"])
  raise Error, "invalid_random_source" if random_bytes.length < length

  payload = random_bytes.first(length).map do |value|
    raise Error, "invalid_random_source" unless value.between?(0, 255)

    DATA[value % 32]
  end.join
  "#{reference["prefix"]}-#{group(payload)}-#{check_symbol(payload, false)}"
end

.format_sequential_reference(registry, namespace, sequence, scope = "") ⇒ Object

Raises:



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/identifold.rb', line 94

def format_sequential_reference(registry, namespace, sequence, scope = "")
  reference = find_namespace(registry, namespace)["reference"]
  raise Error, "unknown_namespace" unless reference && reference["strategy"] == "sequence"

  width = reference["width"]
  raise Error, "sequence_overflow" unless sequence.match?(/\A\d+\z/) && sequence.length <= width

  padded = sequence.rjust(width, "0")
  payload = scope + padded
  "#{reference["prefix"]}-#{scope.empty? ? "" : "#{scope}-"}#{padded}-#{check_symbol(payload, true)}"
end

.normalize(value, registry) ⇒ Object

Raises:



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/identifold.rb', line 106

def normalize(value, registry)
  if value.include?("_")
    parsed = parse_public_id(value)
    find_namespace(registry, parsed[:namespace])
    return parsed[:value]
  end
  return parse_machine_id(value) if value.length == 36

  compact = value.upcase.delete("-")
  definition = registry.find do |candidate|
    reference = candidate["reference"]
    reference && compact.start_with?(reference["prefix"])
  end
  unless definition
    raise Error, value.match?(/\A[A-Za-z]{2,8}/) ? "unknown_namespace" : "invalid_kind"
  end

  reference = definition["reference"]
  body = compact[reference["prefix"].length..]
  raise Error, "invalid_ref" if body.include?("?") || body.include?("_")

  if reference["strategy"] == "sequence"
    scope_length = reference["scope"] == "calendar-year" ? 4 : 0
    raise Error, "invalid_ref_length" unless body.length == scope_length + reference["width"] + 1

    payload = body[...-1]
    raise Error, "invalid_checksum" unless check_symbol(payload, true) == body[-1]

    return format_sequential_reference(
      registry,
      definition["publicPrefix"],
      payload[scope_length..],
      payload[0, scope_length]
    )
  end

  length = profile_length(reference["profile"])
  raise Error, "invalid_ref_length" unless body.length == length + 1

  payload = body[0, length].tr("OIL", "011")
  raise Error, "invalid_checksum" unless check_symbol(payload, false) == body[-1]

  "#{reference["prefix"]}-#{group(payload)}-#{body[-1]}"
end

.parse_machine_id(input) ⇒ Object

Raises:



17
18
19
20
21
22
23
24
# File 'lib/identifold.rb', line 17

def parse_machine_id(input)
  value = input.to_s.downcase
  raise Error, "invalid_mid" unless value.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
  raise Error, "invalid_uuid_version" unless value[14] == "7"
  raise Error, "invalid_mid" unless "89ab".include?(value[19])

  value
end

.parse_public_id(value) ⇒ Object

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/identifold.rb', line 38

def parse_public_id(value)
  raise Error, "invalid_pid" unless value == value.downcase

  separator = value.rindex("_")
  raise Error, "invalid_public_prefix" unless separator

  namespace = value[...separator]
  suffix = value[(separator + 1)..]
  raise Error, "invalid_public_prefix" unless namespace.match?(/\A[a-z](?:[a-z_]{0,61}[a-z])?\z/)
  raise Error, "invalid_pid" unless suffix.length == 26 && suffix[0] <= "7"

  number = 0
  suffix.each_char do |symbol|
    position = TYPE_ID.index(symbol)
    raise Error, "invalid_pid" unless position

    number = (number << 5) | position
  end
  hex = number.to_s(16).rjust(32, "0")
  machine_id = [hex[0, 8], hex[8, 4], hex[12, 4], hex[16, 4], hex[20, 12]].join("-")
  begin
    machine_id = parse_machine_id(machine_id)
  rescue Error
    raise Error, "invalid_pid"
  end
  { value: value, namespace: namespace, machineId: machine_id }
end

.public_id_from_machine_id(machine_id, namespace) ⇒ Object

Raises:



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/identifold.rb', line 26

def public_id_from_machine_id(machine_id, namespace)
  raise Error, "invalid_public_prefix" unless namespace.match?(/\A[a-z](?:[a-z_]{0,61}[a-z])?\z/)

  number = parse_machine_id(machine_id).delete("-").to_i(16)
  suffix = Array.new(26, "0")
  25.downto(0) do |index|
    suffix[index] = TYPE_ID[number & 31]
    number >>= 5
  end
  "#{namespace}_#{suffix.join}"
end