Subversion Repositories oidplus

Rev

Rev 749 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
749 daniel-mar 1
unit WEID_Delphi;
748 daniel-mar 2
 
3
(*
4
 * WEID<=>OID Converter for Delphi
5
 * (c) Webfan.de, ViaThinkSoft
750 daniel-mar 6
 * Revision 2022-02-22
748 daniel-mar 7
 *)
8
 
9
(*
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 WeLohn Check Digit).
15
 
16
  Changes in the December 2021 definition by Daniel Marschall:
17
     - There are several classes of WEIDs which have different OID bases:
18
           "Class C" WEID:  weid:EXAMPLE-3      (base .1.3.6.1.4.1.37553.8.)
19
                            oid:1.3.6.1.4.1.37553.8.32488192274
20
           "Class B" WEID:  weid:pen:SX0-7PR-6  (base .1.3.6.1.4.1.)
21
                            oid:1.3.6.1.4.1.37476.9999
22
           "Class A" WEID:  weid:root:2-RR-2    (base .)
23
                            oid:2.999
24
     - The namespace (weid:, weid:pen:, weid:root:) is now case insensitive.
25
     - Padding with '0' characters is valid (e.g. weid:000EXAMPLE-3)
26
       The paddings do not count into the WeLuhn check-digit.
27
*)
28
 
29
interface
30
 
31
(*
32
Translates a weid to an oid
33
"weid:EXAMPLE-3" becomes "1.3.6.1.4.1.37553.8.32488192274"
34
If it failed (e.g. wrong namespace, wrong checksum, etc.) then false is returned.
35
If the weid ends with '?', then it will be replaced with the checksum,
36
e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3
37
*)
38
function WeidToOid(var weid: string): string;
39
 
40
(*
41
Converts an OID to WEID
42
"1.3.6.1.4.1.37553.8.32488192274" becomes "weid:EXAMPLE-3"
43
*)
44
function OidToWeid(oid: string): string;
45
 
46
implementation
47
 
48
uses
49
  SysUtils;
50
 
51
function LastCharPos(const S: string; const Chr: char): integer;
52
var
53
  i: Integer;
54
begin
55
  for i := length(S) downto 1 do
56
  begin
57
    if S[i] = Chr then
58
    begin
749 daniel-mar 59
      result := i;
748 daniel-mar 60
      Exit;
61
    end;
62
  end;
749 daniel-mar 63
  result := 0;
748 daniel-mar 64
  Exit;
65
end;
66
 
67
function base_convert_bigint(numstring: string; frombase, tobase: integer): string;
68
var
69
  i: Integer;
70
  frombase_str: string;
71
  tobase_str: string;
72
  len: Integer;
73
  number: string;
74
  divide: Integer;
75
  newlen: Integer;
76
  res: string;
77
begin
78
  frombase_str := '';
79
  for i := 0 to frombase-1 do
80
  begin
81
    if i < 10 then
82
      frombase_str := frombase_str + IntToStr(i)
83
    else
84
      frombase_str := frombase_str + Chr(Ord('A') + (i-10));
85
  end;
86
 
87
  tobase_str := '';
88
  for i := 0 to tobase-1 do
89
  begin
90
    if i < 10 then
91
      tobase_str := tobase_str + IntToStr(i)
92
    else
93
      tobase_str := tobase_str + Chr(Ord('A') + (i-10));
94
  end;
95
 
96
  len := Length(numstring);
749 daniel-mar 97
  result := '';
748 daniel-mar 98
  number := numstring; (* this is a fake "Int8" array (implemented with chars) *)
99
  for i := 0 to len-1 do
100
  begin
101
    number[i+1] := Chr(Pos(UpCase(numstring[i+1]), frombase_str)-1);
102
  end;
103
  res := '';
104
  repeat (* Loop until whole number is converted *)
105
    divide := 0;
106
    newlen := 0;
107
    for i := 0 to len-1 do (* Perform division manually (which is why this works with big numbers) *)
108
    begin
109
      divide := divide * frombase + Ord(number[i+1]);
110
      if (divide >= tobase) then
111
      begin
112
        number[newlen+1] := Chr(divide div tobase);
113
        Inc(newlen);
114
        divide := divide mod tobase;
115
      end
116
      else if newlen > 0 then
117
      begin
118
        number[newlen+1] := #0;
119
        Inc(newlen);
120
      end;
121
    end;
122
    len := newlen;
749 daniel-mar 123
    res := tobase_str[divide+1] + res; (* Divide is basically "numstring mod tobase" (i.e. the new character) *)
748 daniel-mar 124
  until newlen = 0;
749 daniel-mar 125
  result := res;
748 daniel-mar 126
end;
127
 
128
function weLuhnGetCheckDigit(s: string): integer;
129
var
130
  p: integer;
131
  wrkstr: string;
132
  c: Char;
133
  i: Integer;
134
  sum: integer;
135
  nbdigits: Integer;
136
  parity: Integer;
137
  n: Integer;
138
  digit: Integer;
139
begin
140
  (* Padding zeros don't count to the check digit (December 2021) *)
141
  s := '-' + s + '-';
142
  while Pos('-0', s) > 0 do
143
  begin
144
    s := StringReplace(s, '-0-', #1, [rfReplaceAll]);
145
    s := StringReplace(s, '-0', '-', [rfReplaceAll]);
146
  end;
147
  s := StringReplace(s, #1, '-0-', [rfReplaceAll]);
148
  s := Copy(s, 2, Length(s)-2);
149
 
150
  (* remove separators of the WEID string *)
151
  wrkstr := StringReplace(s, '-', '', [rfReplaceAll]);
152
 
153
  (* Replace 'a' with '10', 'b' with '11', etc. *)
154
  for c := 'A' to 'Z' do
155
  begin
156
    wrkstr := StringReplace(wrkstr, c, IntToStr(Ord(c)-Ord('A')+10), [rfReplaceAll]);
157
  end;
158
 
749 daniel-mar 159
  (* At the end, wrkstr should only contain digits! Verify it! *)
748 daniel-mar 160
  for i := 1 to Length(wrkstr) do
161
  begin
162
    if not (wrkstr[i] in ['0'..'9']) then
163
    begin
749 daniel-mar 164
      result := -1;
748 daniel-mar 165
      exit;
166
    end;
167
  end;
168
 
169
  (* Now do the standard Luhn algorithm *)
170
  nbdigits := Length(wrkstr);
171
  parity := nbdigits and 1; (* mod 2 *)
172
  sum := 0;
173
  for n := nbdigits-1 downto 0 do
174
  begin
175
    digit := StrToInt(wrkstr[n+1]);
176
    if (n and 1) <> parity then digit := digit * 2;
177
    if digit > 9 then digit := digit - 9;
178
    sum := sum + digit;
179
  end;
180
 
181
  if sum mod 10 = 0 then
749 daniel-mar 182
    result := 0
748 daniel-mar 183
  else
749 daniel-mar 184
    result := 10 - (sum mod 10);
748 daniel-mar 185
end;
186
 
187
function WeidToOid(var weid: string): string;
188
var
189
  base: string;
190
  namespace: string;
191
  p: integer;
192
  rest: string;
193
  actual_checksum: string;
194
  expected_checksum: integer;
195
  complete: string;
196
  oidstr: string;
197
  arc: string;
198
begin
199
  p := LastCharPos(weid,':');
200
  namespace := Copy(weid, 1, p);
201
  rest := Copy(weid, p+1, Length(weid)-p);
202
 
203
  namespace := LowerCase(namespace); (* namespace is case insensitive *)
204
  if namespace = 'weid:' then
205
  begin
206
    (* Class C *)
207
    base := '1-3-6-1-4-1-SZ5-8';
208
  end
209
  else if namespace = 'weid:pen:' then
210
  begin
211
    (* Class B *)
212
    base := '1-3-6-1-4-1';
213
  end
214
  else if namespace = 'weid:root:' then
215
  begin
216
    (* Class A *)
217
    base := '';
218
  end
219
  else
220
  begin
221
    (* Wrong namespace *)
749 daniel-mar 222
    result := '';
748 daniel-mar 223
    Exit;
224
  end;
225
 
226
  weid := rest;
227
 
228
  if base <> '' then
229
    complete := base + '-' + weid
230
  else
231
    complete := weid;
232
  p := LastCharPos(complete, '-');
233
  actual_checksum := Copy(complete, p+1, 1);
234
  complete := Copy(complete, 1, p-1);
235
  expected_checksum := weLuhnGetCheckDigit(complete);
236
  if (actual_checksum <> '?') then
237
  begin
238
    if actual_checksum <> IntToStr(expected_checksum) then
239
    begin
749 daniel-mar 240
      result := ''; (* wrong checksum *)
748 daniel-mar 241
      Exit;
242
    end;
243
  end
244
  else
245
  begin
246
    (* If checksum is '?', it will be replaced by the actual checksum, *)
247
    (* e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3                      *)
248
    weid := StringReplace(weid, '?', IntToStr(expected_checksum), [rfReplaceAll]);
249
  end;
250
 
251
  oidstr := '';
252
  while true do
253
  begin
254
    p := Pos('-', complete);
255
    if p = 0 then p := Length(complete)+1;
256
    arc := Copy(complete, 1, p-1);
257
    Delete(complete, 1, p);
258
    oidstr := oidstr + base_convert_bigint(arc, 36, 10) + '.';
259
    if complete = '' then break;
260
  end;
261
  oidstr := Copy(oidstr, 1, Length(oidstr)-1);
262
 
750 daniel-mar 263
  weid := LowerCase(namespace) + UpperCase(weid); (* add namespace again *)
748 daniel-mar 264
 
749 daniel-mar 265
  result := oidstr;
748 daniel-mar 266
end;
267
 
268
function OidToWeid(oid: string): string;
269
var
270
  is_class_a: boolean;
271
  is_class_b: boolean;
272
  is_class_c: boolean;
273
  weidstr: string;
274
  checksum: string;
275
  namespace: string;
276
  p: Integer;
277
  cd: Integer;
278
  res: string;
279
begin
280
  if Copy(oid,1,1) = '.' then
281
    Delete(oid,1,1); (* remove leading dot *)
282
 
283
  if oid <> '' then
284
  begin
285
    weidstr := '';
286
    while true do
287
    begin
288
      p := Pos('.', oid);
289
      if p = 1 then
290
      begin
291
        Delete(oid, 1, 1);
292
      end
293
      else if p > 0 then
294
      begin
295
        weidstr := weidstr + base_convert_bigint(Copy(oid, 1, p-1),10,36) + '-';
296
        Delete(oid, 1, p);
297
      end
298
      else
299
      begin
300
        weidstr := weidstr + base_convert_bigint(oid,10,36) + '-';
301
        break;
302
      end;
303
    end;
304
    weidstr := Copy(weidstr, 1, Length(weidstr)-1);
305
  end
306
  else
307
  begin
308
    weidstr := '';
309
  end;
310
 
311
  is_class_c := (Pos('1-3-6-1-4-1-SZ5-8-', weidstr) = 1) or
312
                (weidstr = '1-3-6-1-4-1-SZ5-8');
313
  is_class_b := ((Pos('1-3-6-1-4-1-', weidstr) = 1) or
314
                (weidstr = '1-3-6-1-4-1'))
315
                and not is_class_c;
316
  is_class_a := not is_class_b and not is_class_c;
317
 
318
  cd := weLuhnGetCheckDigit(weidstr);
319
  if cd < 0 then
320
  begin
749 daniel-mar 321
    result := weidstr;
748 daniel-mar 322
    exit;
323
  end;
324
  checksum := IntToStr(cd);
325
 
326
  if is_class_c then
327
  begin
328
    Delete(weidstr, 1, Length('1-3-6-1-4-1-SZ5-8-'));
329
    namespace := 'weid:';
330
  end
331
  else if is_class_b then
332
  begin
333
    Delete(weidstr, 1, Length('1-3-6-1-4-1-'));
334
    namespace := 'weid:pen:';
335
  end
336
  else if is_class_a then
337
  begin
338
    (* weidstr stays *)
339
    namespace := 'weid:root:';
340
  end
341
  else
342
  begin
343
    (* should not happen *)
749 daniel-mar 344
    result := '';
748 daniel-mar 345
    Exit;
346
  end;
347
 
348
  res := namespace;
349
  if weidstr = '' then
350
    res := res + checksum
351
  else
352
    res := res + weidstr + '-' + checksum;
749 daniel-mar 353
  result := res;
748 daniel-mar 354
end;
355
 
356
end.