Subversion Repositories oidplus

Rev

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

  1. unit WEID;
  2.  
  3. (*
  4.  * WEID<=>OID Converter for TurboPascal
  5.  * (c) Webfan.de, ViaThinkSoft
  6.  * Revision 2023-08-10
  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 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.  *)
  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.  
  95.   (* At the end, wrkstr should only contain digits! Verify it! *)
  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;
  134.   domainpart: string;
  135.   tmp: string;
  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 *)
  142.  
  143.   if Copy(namespace, 1, 5) = 'weid:' then
  144.   begin
  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
  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.  
  243.   weid := LowerCase(namespace) + UpperCase(weid); (* add namespace again *)
  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.
  337.