Subversion Repositories oidplus

Rev

Rev 748 | Rev 750 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit WEID_Delphi;
  2.  
  3. (*
  4.  * WEID<=>OID Converter for Delphi
  5.  * (c) Webfan.de, ViaThinkSoft
  6.  * Revision 2022-02-19
  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
  59.       result := i;
  60.       Exit;
  61.     end;
  62.   end;
  63.   result := 0;
  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);
  97.   result := '';
  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;
  123.     res := tobase_str[divide+1] + res; (* Divide is basically "numstring mod tobase" (i.e. the new character) *)
  124.   until newlen = 0;
  125.   result := res;
  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.  
  159.   (* At the end, wrkstr should only contain digits! Verify it! *)
  160.   for i := 1 to Length(wrkstr) do
  161.   begin
  162.     if not (wrkstr[i] in ['0'..'9']) then
  163.     begin
  164.       result := -1;
  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
  182.     result := 0
  183.   else
  184.     result := 10 - (sum mod 10);
  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 *)
  222.     result := '';
  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
  240.       result := ''; (* wrong checksum *)
  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.  
  263.   weid := namespace + weid; (* add namespace again *)
  264.  
  265.   result := oidstr;
  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
  321.     result := weidstr;
  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 *)
  344.     result := '';
  345.     Exit;
  346.   end;
  347.  
  348.   res := namespace;
  349.   if weidstr = '' then
  350.     res := res + checksum
  351.   else
  352.     res := res + weidstr + '-' + checksum;
  353.   result := res;
  354. end;
  355.  
  356. end.
  357.