Subversion Repositories oidplus

Rev

Rev 1036 | View as "text/javascript" | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. (function (globalObject) {
  2.   'use strict';
  3.  
  4. /**
  5. * WEID<=>OID Converter
  6. * (c) Webfan.de, ViaThinkSoft
  7. * Revision 2023-08-09
  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. var WeidOidConverter = {
  43.  
  44.         weLuhnCheckDigit: function(str) {
  45.                 // Padding zeros don't count to the check digit (December 2021)
  46.                 var ary = str.split('-');
  47.                 ary.forEach(function (o,i,a) {
  48.                         if (a[i].match(/^0+$/)) {
  49.                                 a[i] = '0';
  50.                         } else {
  51.                                 a[i] = a[i].replace(/^0+/, '');
  52.                         }
  53.                 });
  54.                 str = ary.join('-');
  55.  
  56.                 // remove separators from the WEID string
  57.                 var wrkstr = str.replaceAll('-', '');
  58.  
  59.                 // Replace 'a' with '10', 'b' with '1', etc.
  60.                 for (var i=0; i<26; i++) {
  61.                         wrkstr = wrkstr.toLowerCase().replaceAll(String.fromCharCode('a'.charCodeAt(0)+i).toLowerCase(), (10+i));
  62.                 }
  63.  
  64.                 // At the end, wrkstr should only contain digits! Verify it!
  65.                 if (!wrkstr.match(/^\d*$/)) {
  66.                         console.error("weLuhnCheckDigit: Invalid input");
  67.                         return false;
  68.                 }
  69.  
  70.                 // Now do the standard Luhn algorithm
  71.                 var nbdigits = wrkstr.length;
  72.                 var parity = nbdigits & 1; // mod 2
  73.                 var sum = 0;
  74.                 for (var n=nbdigits-1; n>=0; n--) {
  75.                         var digit = parseInt(wrkstr.substr(n,1));
  76.                         if ((n & 1) != parity) digit *= 2;
  77.                         if (digit > 9) digit -= 9;
  78.                         sum += digit;
  79.                 }
  80.                 return (sum%10) == 0 ? 0 : 10-(sum%10);
  81.         },
  82.  
  83.         oidSanitize: function(oid) {
  84.                 var oid = oid.trim();
  85.  
  86.                 if (oid.substr(0,1) == '.') oid = oid.substr(1); // remove leading dot
  87.  
  88.                 if (oid != '') {
  89.                         var elements = oid.split('.');
  90.  
  91.                         var fail = false;
  92.                         elements.forEach(function (o,i,a) {
  93.                                 if (a[i].trim() == '') fail = true;
  94.  
  95.                                 if (!a[i].match(/^\d+$/)) fail = true;
  96.  
  97.                                 if (a[i].match(/^0+$/)) {
  98.                                         a[i] = '0';
  99.                                 } else {
  100.                                         a[i] = a[i].replace(/^0+/, '');
  101.                                 }
  102.                         });
  103.                         if (fail) return false;
  104.  
  105.                         oid = elements.join(".");
  106.  
  107.                         if ((elements.length > 0) && (elements[0] != '0') && (elements[0] != '1') && (elements[0] != '2')) return false;
  108.                         if ((elements.length > 1) && ((elements[0] == '0') || (elements[0] == '1')) && ((elements[1].length > 2) || (elements[1] > 39))) return false;
  109.                 }
  110.  
  111.                 return oid;
  112.         },
  113.  
  114.         // Translates a WEID to an OID
  115.         // "weid:EXAMPLE-3" becomes "1.3.6.1.4.1.37553.8.32488192274"
  116.         // If it failed (e.g. wrong namespace, wrong check digit, etc.) then false is returned.
  117.         // If the weid ends with '?', the check digit will be added
  118.         // Return value is an array with the elements "oid" and "weid".
  119.         // Example:
  120.         //     weid2oid("weid:EXAMPLE-?").weid == "weid:EXAMPLE-3"
  121.         //     weid2oid("weid:EXAMPLE-?").oid  == "1.3.6.1.4.1.37553.8.32488192274"
  122.         weid2oid: function(weid) {
  123.                 var weid = weid.trim();
  124.  
  125.                 var p = weid.lastIndexOf(':');
  126.                 var namespace = weid.substr(0, p+1);
  127.                 var rest = weid.substr(p+1);
  128.  
  129.                 var base = null;
  130.                 namespace = namespace.toLowerCase(); // namespace is case insensitive
  131.  
  132.                 if (namespace.startsWith("weid:")) {
  133.                         var domainpart = weid.split(":")[1].split(".");
  134.                         if (domainpart.length > 1) {
  135.                                 // Spec Change 10: Class D / Domain-WEID ( https://github.com/frdl/weid/issues/3 )
  136.                                 if (weid.split(":").length != 3) return false;
  137.                                 var domainrest = weid.split(":")[2].split("-");
  138.                                 var alt_weid = "weid:9-DNS-" + domainpart.reverse().join("-").toUpperCase() + "-" + domainrest.join("-");
  139.                                 return WeidOidConverter.weid2oid(alt_weid);
  140.                         }
  141.                 }
  142.  
  143.                 if (namespace.startsWith('weid:x-')) {
  144.                         // Spec Change 11: Proprietary Namespaces ( https://github.com/frdl/weid/issues/4 )
  145.                         return { "weid": weid, "oid" : "[Proprietary WEID Namespace]" };
  146.                 } else if (namespace == 'weid:') {
  147.                         // Class C
  148.                         base = '1-3-6-1-4-1-SZ5-8';
  149.                 } else if (namespace == 'weid:pen:') {
  150.                         // Class B
  151.                         base = '1-3-6-1-4-1';
  152.                 } else if (namespace == 'weid:root:') {
  153.                         // Class A
  154.                         base = '';
  155.                 } else {
  156.                         // Wrong namespace
  157.                         console.error("weid2oid: Wrong input");
  158.                         return false;
  159.                 }
  160.  
  161.                 weid = rest;
  162.  
  163.                 var elements = ((base != '') ? base.split('-') : []).concat(weid.split('-'));
  164.  
  165.                 var fail = false;
  166.                 elements.forEach(function (o,i,a) {
  167.                         if (a[i].trim() == '') fail = true;
  168.                 });
  169.                 if (fail) return false;
  170.  
  171.                 var actual_checksum = elements.pop();
  172.                 var expected_checksum = WeidOidConverter.weLuhnCheckDigit(elements.join('-'));
  173.                 if (actual_checksum != '?') {
  174.                         if (actual_checksum != expected_checksum) {
  175.                                 console.error("weid2oid: Wrong check digit");
  176.                                 return false; // wrong checksum
  177.                         }
  178.                 } else {
  179.                         // If check digit is '?', it will be replaced by the actual check digit,
  180.                         // e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3
  181.                         weid = weid.replace('?', expected_checksum);
  182.                 }
  183.                 elements.forEach(function (o,i,a) {
  184.                         a[i] = WeidOidConverter.base_convert_bigint(a[i], 36, 10);
  185.                 });
  186.                 var oid = elements.join('.');
  187.  
  188.                 weid = namespace.toLowerCase() + weid.toUpperCase(); // add namespace again
  189.  
  190.                 oid = WeidOidConverter.oidSanitize(oid);
  191.                 if (oid === false) return false; // invalid OID
  192.  
  193.                 return { "weid": weid, "oid" : oid };
  194.         },
  195.  
  196.         // Converts an OID to WEID
  197.         // "1.3.6.1.4.1.37553.8.32488192274" becomes "weid:EXAMPLE-3"
  198.         oid2weid: function(oid) {
  199.                 var oid = WeidOidConverter.oidSanitize(oid);
  200.                 if (oid === false) return false;
  201.  
  202.                 var weidstr = null;
  203.                 if (oid != '') {
  204.                         var elements = oid.split('.');
  205.                         elements.forEach(function (o,i,a) {
  206.                                 a[i] = WeidOidConverter.base_convert_bigint(a[i], 10, 36);
  207.                         });
  208.                         weidstr = elements.join("-");
  209.                 } else {
  210.                         weidstr = '';
  211.                 }
  212.  
  213.                 var is_class_c = (weidstr.startsWith('1-3-6-1-4-1-SZ5-8-') || (weidstr == '1-3-6-1-4-1-SZ5-8'));
  214.                 var is_class_b = (weidstr.startsWith('1-3-6-1-4-1-') || (weidstr == '1-3-6-1-4-1'));
  215.                 var is_class_a = !is_class_b && !is_class_c;
  216.  
  217.                 var checksum = WeidOidConverter.weLuhnCheckDigit(weidstr);
  218.  
  219.                 var namespace = null;
  220.                 if (is_class_c) {
  221.                         weidstr = weidstr.substr('1-3-6-1-4-1-SZ5-8-'.length);
  222.                         namespace = 'weid:';
  223.                 } else if (is_class_b) {
  224.                         weidstr = weidstr.substr('1-3-6-1-4-1-'.length);
  225.                         namespace = 'weid:pen:';
  226.                 } else if (is_class_a) {
  227.                         // weidstr stays
  228.                         namespace = 'weid:root:';
  229.                 } else {
  230.                         // should not happen
  231.                         console.error("oid2weid: Cannot detect namespace");
  232.                         return false;
  233.                 }
  234.  
  235.                 var weid = namespace + (weidstr == '' ? checksum : weidstr + '-' + checksum);
  236.  
  237.                 return { "weid": weid, "oid": oid };
  238.         },
  239.  
  240.         base_convert_bigint: function(numstring, frombase, tobase) {
  241.  
  242.                 // This variant would require the "mikemcl/bignumber.js" library:
  243.                 //var x = BigNumber(numstr, frombase);
  244.                 //return isNaN(x) ? false : x.toString(tobase).toUpperCase();
  245.  
  246.                 var frombase_str = '';
  247.                 for (var i=0; i<frombase; i++) {
  248.                         frombase_str += parseInt(i, 10).toString(36).toUpperCase();
  249.                 }
  250.  
  251.                 var tobase_str = '';
  252.                 for (var i=0; i<tobase; i++) {
  253.                         tobase_str += parseInt(i, 10).toString(36).toUpperCase();
  254.                 }
  255.  
  256.                 for (var i=0; i<numstring.length; i++) {
  257.                         if (frombase_str.toLowerCase().indexOf(numstring.substr(i,1).toLowerCase()) < 0) {
  258.                                 console.error("base_convert_bigint: Invalid input");
  259.                                 return false;
  260.                         }
  261.                 }
  262.  
  263.                 var length = numstring.length;
  264.                 var result = '';
  265.                 var number = [];
  266.                 for (var i=0; i<length; i++) {
  267.                         number[i] = frombase_str.toLowerCase().indexOf(numstring[i].toLowerCase());
  268.                 }
  269.                 var newlen = null;
  270.                 do { // Loop until whole number is converted
  271.                         var divide = 0;
  272.                         var newlen = 0;
  273.                         for (var i=0; i<length; i++) { // Perform division manually (which is why this works with big numbers)
  274.                                 divide = divide * frombase + parseInt(number[i]);
  275.                                 if (divide >= tobase) {
  276.                                         number[newlen++] = (divide / tobase);
  277.                                         divide = divide % tobase;
  278.                                 } else if (newlen > 0) {
  279.                                         number[newlen++] = 0;
  280.                                 }
  281.                         }
  282.                         length = newlen;
  283.                         result = tobase_str.substr(divide,1) + result; // Divide is basically numstring % tobase (i.e. the new character)
  284.                 }
  285.                 while (newlen != 0);
  286.  
  287.                 return result;
  288.         }
  289. };
  290.  
  291. WeidOidConverter['default'] = WeidOidConverter.WeidOidConverter = WeidOidConverter;
  292.  
  293. if (typeof define == 'function' && define.amd) {
  294.         define('WeidOidConverter', function () {
  295.                 return WeidOidConverter;
  296.         });
  297. } else if (typeof module != 'undefined' && module.exports) {
  298.         module.exports = WeidOidConverter;
  299. } else {
  300.         if (!globalObject) {
  301.                 globalObject = typeof self != 'undefined' && self ? self : window;
  302.         }
  303.         globalObject.WeidOidConverter = WeidOidConverter;
  304. }
  305. })(this);
  306.