Subversion Repositories oidplus

Rev

Rev 750 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
748 daniel-mar 1
unit WEID;
2
 
3
(*
4
 * WEID<=>OID Converter for TurboPascal
5
 * (c) Webfan.de, ViaThinkSoft
1377 daniel-mar 6
 * Revision 2023-08-10
748 daniel-mar 7
 *)
8
 
9
(*
1377 daniel-mar 10
 * What is a WEID?
11
 *     A WEID (WEhowski IDentifier) is an alternative representation of an
12
 *     OID (Object IDentifier) defined by Till Wehowski.
13
 *     In OIDs, arcs are in decimal base 10. In WEIDs, the arcs are in base 36.
14
 *     Also, each WEID has a check digit at the end (called WeLuhn Check Digit).
15
 *
16
 * The full specification can be found here: https://weid.info/spec.html
17
 *
18
 * This converter supports WEID as of Spec Change #11
19
 *
20
 * A few short notes:
21
 *     - There are several classes of WEIDs which have different OID bases:
22
 *           "Class A" WEID:  weid:root:2-RR-?
23
 *                            oid:2.999
24
 *                            WEID class base OID: (OID Root)
25
 *           "Class B" WEID:  weid:pen:SX0-7PR-?
26
 *                            oid:1.3.6.1.4.1.37476.9999
27
 *                            WEID class base OID: 1.3.6.1.4.1
28
 *           "Class C" WEID:  weid:EXAMPLE-?
29
 *                            oid:1.3.6.1.4.1.37553.8.32488192274
30
 *                            WEID class base OID: 1.3.6.1.4.1.37553.8
31
 *           "Class D" WEID:  weid:example.com:TEST-? is equal to weid:9-DNS-COM-EXAMPLE-TEST-?
32
 *                            Since the check digit is based on the OID, the check digit is equal for both notations.
33
 *                            oid:1.3.6.1.4.1.37553.8.9.17704.32488192274.16438.1372205
34
 *                            WEID class base OID: 1.3.6.1.4.1.37553.8.9.17704
35
 *     - The last arc in a WEID is the check digit. A question mark is the wildcard for an unknown check digit.
36
 *       In this case, the converter will return the correct expected check digit for the input.
37
 *     - The namespace (weid:, weid:pen:, weid:root:) is case insensitive.
38
 *     - Padding with '0' characters is valid (e.g. weid:000EXAMPLE-3)
39
 *       The paddings do not count into the WeLuhn check digit.
40
 *)
748 daniel-mar 41
 
42
interface
43
 
44
(*
45
Translates a weid to an oid
46
"weid:EXAMPLE-3" becomes "1.3.6.1.4.1.37553.8.32488192274"
47
If it failed (e.g. wrong namespace, wrong checksum, etc.) then false is returned.
48
If the weid ends with '?', then it will be replaced with the checksum,
49
e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3
50
*)
51
function WeidToOid(var weid: string): string;
52
 
53
(*
54
Converts an OID to WEID
55
"1.3.6.1.4.1.37553.8.32488192274" becomes "weid:EXAMPLE-3"
56
*)
57
function OidToWeid(oid: string): string;
58
 
59
implementation
60
 
61
uses
62
  VtsFuncs;
63
 
64
function weLuhnGetCheckDigit(s: string): integer;
65
var
66
  p: integer;
67
  wrkstr: string;
68
  c: Char;
69
  i: Integer;
70
  sum: integer;
71
  nbdigits: Integer;
72
  parity: Integer;
73
  n: Integer;
74
  digit: Integer;
75
begin
76
  (* Padding zeros don't count to the check digit (December 2021) *)
77
  s := '-' + s + '-';
78
  while Pos('-0', s) > 0 do
79
  begin
80
    s := StringReplace(s, '-0-', #1);
81
    s := StringReplace(s, '-0', '-');
82
  end;
83
  s := StringReplace(s, #1, '-0-');
84
  s := Copy(s, 2, Length(s)-2);
85
 
86
  (* remove separators of the WEID string *)
87
  wrkstr := StringReplace(s, '-', '');
88
 
89
  (* Replace 'a' with '10', 'b' with '11', etc. *)
90
  for c := 'A' to 'Z' do
91
  begin
92
    wrkstr := StringReplace(wrkstr, c, IntToStr(Ord(c)-Ord('A')+10));
93
  end;
94
 
749 daniel-mar 95
  (* At the end, wrkstr should only contain digits! Verify it! *)
748 daniel-mar 96
  for i := 1 to Length(wrkstr) do
97
  begin
98
    if not (wrkstr[i] in ['0'..'9']) then
99
    begin
100
      weLuhnGetCheckDigit := -1;
101
      exit;
102
    end;
103
  end;
104
 
105
  (* Now do the standard Luhn algorithm *)
106
  nbdigits := Length(wrkstr);
107
  parity := nbdigits and 1; (* mod 2 *)
108
  sum := 0;
109
  for n := nbdigits-1 downto 0 do
110
  begin
111
    digit := StrToInt(wrkstr[n+1]);
112
    if (n and 1) <> parity then digit := digit * 2;
113
    if digit > 9 then digit := digit - 9;
114
    sum := sum + digit;
115
  end;
116
 
117
  if sum mod 10 = 0 then
118
    weLuhnGetCheckDigit := 0
119
  else
120
    weLuhnGetCheckDigit := 10 - (sum mod 10);
121
end;
122
 
123
function WeidToOid(var weid: string): string;
124
var
125
  base: string;
126
  namespace: string;
127
  p: integer;
128
  rest: string;
129
  actual_checksum: string;
130
  expected_checksum: integer;
131
  complete: string;
132
  oidstr: string;
133
  arc: string;
1377 daniel-mar 134
  domainpart: string;
135
  tmp: string;
748 daniel-mar 136
begin
137
  p := LastCharPos(weid,':');
138
  namespace := Copy(weid, 1, p);
139
  rest := Copy(weid, p+1, Length(weid)-p);
140
 
141
  namespace := LowerCase(namespace); (* namespace is case insensitive *)
1377 daniel-mar 142
 
143
  if Copy(namespace, 1, 5) = 'weid:' then
748 daniel-mar 144
  begin
1377 daniel-mar 145
    tmp := Copy(namespace, 1, Length(namespace)-1);
146
    namespace[5] := '*'; (* to force searching the second ":" *)
147
    p := Pos(':', tmp);
148
    Delete(tmp, 1, p);
149
    if pos('.', tmp) > 0 then
150
    begin
151
      (* Spec Change 10: Class D / Domain-WEID *)
152
      if pos(':', tmp) > 0 then
153
      begin
154
        WeidToOid := '';
155
        exit;
156
      end;
157
      domainpart := '';
158
      while tmp <> '' do
159
      begin
160
        p := Pos('.', tmp);
161
        if p = 0 then
162
        begin
163
          domainpart := tmp + '-' + domainpart;
164
          break;
165
        end
166
        else
167
        begin
168
          domainpart := Copy(tmp, 1, p-1) + '-' + domainpart;
169
          Delete(tmp, 1, p);
170
        end;
171
      end;
172
      weid := 'weid:9-DNS-' + UpperCase(Domainpart) + Rest;
173
      WeidToOid := WeidToOid(weid);
174
      exit;
175
    end;
176
  end;
177
 
178
  if Copy(namespace, 1, 7) = 'weid:x-' then
179
  begin
180
    (* Spec Change 11: Proprietary Namespaces *)
181
    WeidToOid := '[Proprietary WEID Namespace]';
182
    Exit;
183
  end
184
  else if namespace = 'weid:' then  
185
  begin
748 daniel-mar 186
    (* Class C *)
187
    base := '1-3-6-1-4-1-SZ5-8';
188
  end
189
  else if namespace = 'weid:pen:' then
190
  begin
191
    (* Class B *)
192
    base := '1-3-6-1-4-1';
193
  end
194
  else if namespace = 'weid:root:' then
195
  begin
196
    (* Class A *)
197
    base := '';
198
  end
199
  else
200
  begin
201
    (* Wrong namespace *)
202
    WeidToOid := '';
203
    Exit;
204
  end;
205
 
206
  weid := rest;
207
 
208
  if base <> '' then
209
    complete := base + '-' + weid
210
  else
211
    complete := weid;
212
  p := LastCharPos(complete, '-');
213
  actual_checksum := Copy(complete, p+1, 1);
214
  complete := Copy(complete, 1, p-1);
215
  expected_checksum := weLuhnGetCheckDigit(complete);
216
  if (actual_checksum <> '?') then
217
  begin
218
    if actual_checksum <> IntToStr(expected_checksum) then
219
    begin
220
      WeidToOid := ''; (* wrong checksum *)
221
      Exit;
222
    end;
223
  end
224
  else
225
  begin
226
    (* If checksum is '?', it will be replaced by the actual checksum, *)
227
    (* e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3                      *)
228
    weid := StringReplace(weid, '?', IntToStr(expected_checksum));
229
  end;
230
 
231
  oidstr := '';
232
  while true do
233
  begin
234
    p := Pos('-', complete);
235
    if p = 0 then p := Length(complete)+1;
236
    arc := Copy(complete, 1, p-1);
237
    Delete(complete, 1, p);
238
    oidstr := oidstr + base_convert_bigint(arc, 36, 10) + '.';
239
    if complete = '' then break;
240
  end;
241
  oidstr := Copy(oidstr, 1, Length(oidstr)-1);
242
 
750 daniel-mar 243
  weid := LowerCase(namespace) + UpperCase(weid); (* add namespace again *)
748 daniel-mar 244
 
245
  WeidToOid := oidstr;
246
end;
247
 
248
function OidToWeid(oid: string): string;
249
var
250
  is_class_a: boolean;
251
  is_class_b: boolean;
252
  is_class_c: boolean;
253
  weidstr: string;
254
  checksum: string;
255
  namespace: string;
256
  p: Integer;
257
  cd: Integer;
258
  res: string;
259
begin
260
  if Copy(oid,1,1) = '.' then
261
    Delete(oid,1,1); (* remove leading dot *)
262
 
263
  if oid <> '' then
264
  begin
265
    weidstr := '';
266
    while true do
267
    begin
268
      p := Pos('.', oid);
269
      if p = 1 then
270
      begin
271
        Delete(oid, 1, 1);
272
      end
273
      else if p > 0 then
274
      begin
275
        weidstr := weidstr + base_convert_bigint(Copy(oid, 1, p-1),10,36) + '-';
276
        Delete(oid, 1, p);
277
      end
278
      else
279
      begin
280
        weidstr := weidstr + base_convert_bigint(oid,10,36) + '-';
281
        break;
282
      end;
283
    end;
284
    weidstr := Copy(weidstr, 1, Length(weidstr)-1);
285
  end
286
  else
287
  begin
288
    weidstr := '';
289
  end;
290
 
291
  is_class_c := (Pos('1-3-6-1-4-1-SZ5-8-', weidstr) = 1) or
292
                (weidstr = '1-3-6-1-4-1-SZ5-8');
293
  is_class_b := ((Pos('1-3-6-1-4-1-', weidstr) = 1) or
294
                (weidstr = '1-3-6-1-4-1'))
295
                and not is_class_c;
296
  is_class_a := not is_class_b and not is_class_c;
297
 
298
  cd := weLuhnGetCheckDigit(weidstr);
299
  if cd < 0 then
300
  begin
301
    OidToWeid := weidstr;
302
    exit;
303
  end;
304
  checksum := IntToStr(cd);
305
 
306
  if is_class_c then
307
  begin
308
    Delete(weidstr, 1, Length('1-3-6-1-4-1-SZ5-8-'));
309
    namespace := 'weid:';
310
  end
311
  else if is_class_b then
312
  begin
313
    Delete(weidstr, 1, Length('1-3-6-1-4-1-'));
314
    namespace := 'weid:pen:';
315
  end
316
  else if is_class_a then
317
  begin
318
    (* weidstr stays *)
319
    namespace := 'weid:root:';
320
  end
321
  else
322
  begin
323
    (* should not happen *)
324
    OidToWeid := '';
325
    Exit;
326
  end;
327
 
328
  res := namespace;
329
  if weidstr = '' then
330
    res := res + checksum
331
  else
332
    res := res + weidstr + '-' + checksum;
333
  OidToWeid := res;
334
end;
335
 
336
end.