Subversion Repositories oidplus

Rev

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

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusOid extends OIDplusObject {
  27.         private $oid;
  28.  
  29.         public function __construct($oid) {
  30.                 $bak_oid = $oid;
  31.  
  32.                 $oid = sanitizeOID($oid, 'auto');
  33.                 if ($oid === false) {
  34.                         throw new OIDplusException(_L('Invalid OID %1',$bak_oid));
  35.                 }
  36.  
  37.                 if (($oid != '') && (!oid_valid_dotnotation($oid, false, true, 0))) {
  38.                         // avoid OIDs like 3.0
  39.                         throw new OIDplusException(_L('Invalid OID %1',$bak_oid));
  40.                 }
  41.  
  42.                 $this->oid = $oid;
  43.         }
  44.  
  45.         public static function parse($node_id) {
  46.                 @list($namespace, $oid) = explode(':', $node_id, 2);
  47.                 if ($namespace !== self::ns()) return false;
  48.                 return new self($oid);
  49.         }
  50.  
  51.         public static function objectTypeTitle() {
  52.                 return _L('Object Identifier (OID)');
  53.         }
  54.  
  55.         public static function objectTypeTitleShort() {
  56.                 return _L('OID');
  57.         }
  58.  
  59.         public static function ns() {
  60.                 return 'oid';
  61.         }
  62.  
  63.         public static function root() {
  64.                 return self::ns().':';
  65.         }
  66.  
  67.         public function isRoot() {
  68.                 return $this->oid == '';
  69.         }
  70.  
  71.         public function nodeId($with_ns=true) {
  72.                 return $with_ns ? self::root().$this->oid : $this->oid;
  73.         }
  74.  
  75.         public function addString($str) {
  76.                 if (!$this->isRoot()) {
  77.                         if (strpos($str,'.') !== false) throw new OIDplusException(_L('Please only submit one arc (not an absolute OID or multiple arcs).'));
  78.                 }
  79.  
  80.                 return $this->appendArcs($str)->nodeId();
  81.         }
  82.  
  83.         public function crudShowId(OIDplusObject $parent) {
  84.                 if ($parent instanceof OIDplusOid) {
  85.                         return $this->deltaDotNotation($parent);
  86.                 }
  87.         }
  88.  
  89.         public function jsTreeNodeName(OIDplusObject $parent = null) {
  90.                 if ($parent == null) return $this->objectTypeTitle();
  91.                 if ($parent instanceof OIDplusOid) {
  92.                         return $this->viewGetArcAsn1s($parent);
  93.                 } else {
  94.                         return '';
  95.                 }
  96.         }
  97.  
  98.         public function defaultTitle() {
  99.                 return _L('OID %1',$this->oid);
  100.         }
  101.  
  102.         public function isLeafNode() {
  103.                 return false;
  104.         }
  105.  
  106.         private function getTechInfo() {
  107.                 $tech_info = array();
  108.  
  109.                 $tmp = _L('Dot notation');
  110.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="https://oid-rep.orange-labs.fr/faq.htm#14" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  111.                 $tech_info[$tmp] = $this->getDotNotation();
  112.  
  113.                 $tmp = _L('ASN.1 notation');
  114.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="https://oid-rep.orange-labs.fr/faq.htm#17" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  115.                 $tech_info[$tmp] = $this->getAsn1Notation();
  116.  
  117.                 $tmp = _L('OID-IRI notation');
  118.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="https://oid-rep.orange-labs.fr/faq.htm#iri" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  119.                 $tech_info[$tmp] = $this->getIriNotation();
  120.  
  121.                 $tmp = _L('WEID notation');
  122.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="https://weid.info/" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  123.                 $tech_info[$tmp] = $this->getWeidNotation();
  124.  
  125.                 $tmp = _L('DER encoding');
  126.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="https://misc.daniel-marschall.de/asn.1/oid-converter/online.php" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  127.                 $tech_info[$tmp] = str_replace(' ', ':', \OidDerConverter::hexarrayToStr(\OidDerConverter::oidToDER($this->nodeId(false))));
  128.  
  129.                 return $tech_info;
  130.         }
  131.  
  132.         protected function isClassCWeid() {
  133.                 $dist = oid_distance($this->oid, '1.3.6.1.4.1.37553.8');
  134.                 if ($dist === false) return false;
  135.                 return $dist >= 0;
  136.         }
  137.  
  138.         public function getContentPage(&$title, &$content, &$icon) {
  139.                 if ($this->isClassCWeid()) {
  140.                         // TODO: Also change treeview menu mini-icon?
  141.                         $icon = file_exists(__DIR__.'/img/weid_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/weid_icon.png' : '';
  142.                 } else {
  143.                         $icon = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  144.                 }
  145.  
  146.                 if ($this->isRoot()) {
  147.                         $title = OIDplusOid::objectTypeTitle();
  148.  
  149.                         $res = OIDplus::db()->query("select id from ###objects where parent = ?", array(self::root()));
  150.                         if ($res->any()) {
  151.                                 $content = _L('Please select an OID in the tree view at the left to show its contents.');
  152.                         } else {
  153.                                 $content = _L('Currently, no OID is registered in the system.');
  154.                         }
  155.  
  156.                         if (!$this->isLeafNode()) {
  157.                                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  158.                                         $content .= '<h2>'._L('Manage your root OIDs').'</h2>';
  159.                                 } else {
  160.                                         $content .= '<h2>'._L('Root OIDs').'</h2>';
  161.                                 }
  162.                                 $content .= '%%CRUD%%';
  163.                         }
  164.                 } else {
  165.                         $title = $this->getTitle();
  166.  
  167.                         $tech_info = $this->getTechInfo();
  168.                         $tech_info_html = '';
  169.                         if (count($tech_info) > 0) {
  170.                                 $tech_info_html .= '<h2>'._L('Technical information').'</h2>';
  171.                                 $tech_info_html .= '<table border="0">';
  172.                                 foreach ($tech_info as $key => $value) {
  173.                                         $tech_info_html .= '<tr><td>'.$key.': </td><td><code>'.$value.'</code></td></tr>';
  174.                                 }
  175.                                 $tech_info_html .= '</table>';
  176.                         }
  177.  
  178.                         $content = $tech_info_html;
  179.  
  180.                         $content .= '<h2>'._L('Description').'</h2>%%DESC%%'.
  181.                                     '<h2>'._L('Registration Authority').'</h2>%%RA_INFO%%';
  182.  
  183.                         if (!$this->isLeafNode()) {
  184.                                 if ($this->userHasWriteRights()) {
  185.                                         $content .= '<h2>'._L('Create or change subordinate objects').'</h2>';
  186.                                 } else {
  187.                                         $content .= '<h2>'._L('Subordinate objects').'</h2>';
  188.                                 }
  189.                                 $content .= '%%CRUD%%';
  190.                         }
  191.                 }
  192.         }
  193.  
  194.         # ---
  195.  
  196.         // Gets the last arc of an WEID
  197.         public function weidArc() {
  198.                 // Dirty hack: We prepend '0.' in front of the OID to enforce the
  199.                 //             creation of a Class A weid (weid:root:) . Otherwise we could not
  200.                 //             get the hidden arc value "8" from "weid:4" (which is actually "weid:pen:SZ5-8-?"
  201.                 $weid = \Frdl\Weid\WeidOidConverter::oid2weid('0.'.$this->getDotNotation());
  202.                 if ($weid === false) return false;
  203.                 $ary = explode(':', $weid);
  204.                 $weid = array_pop($ary); // remove namespace and sub-namespace if existing
  205.                 $x = explode('-', $weid);
  206.                 if (count($x) < 2) return ''; // WEID root arc. Has no name
  207.                 return $x[count($x)-2];
  208.         }
  209.  
  210.         public function getWeidNotation($withAbbr=true) {
  211.                 $weid = \Frdl\Weid\WeidOidConverter::oid2weid($this->getDotNotation());
  212.                 if ($withAbbr) {
  213.                         $ary = explode(':', $weid);
  214.                         $weid = array_pop($ary); // remove namespace and sub-namespace if existing
  215.                         $ns = implode(':', $ary).':';
  216.  
  217.                         $weid_arcs = explode('-', $weid);
  218.                         foreach ($weid_arcs as $i => &$weid) {
  219.                                 if ($i == count($weid_arcs)-1) {
  220.                                         $weid = '<abbr title="'._L('weLuhn check digit').'">'.$weid.'</abbr>';
  221.                                 } else {
  222.                                         $oid_arcs = explode('.',$this->oid);
  223.                                         $weid_num = $oid_arcs[(count($oid_arcs)-1)-(count($weid_arcs)-1)+($i+1)];
  224.                                         if ($weid_num != $weid) {
  225.                                                 $weid = '<abbr title="'._L('Numeric value').': '.$weid_num.'">'.$weid.'</abbr>';
  226.                                         }
  227.                                 }
  228.                         }
  229.                         $base_arc = '???';
  230.                         if ($ns === 'weid:')      $base_arc = '1.3.6.1.4.1.37553.8';
  231.                         if ($ns === 'weid:pen:')  $base_arc = '1.3.6.1.4.1';
  232.                         if ($ns === 'weid:root:') $base_arc = _L('OID tree root');
  233.  
  234.                         $weid = '<abbr title="'._L('Base OID').': '.$base_arc.'">' . rtrim($ns,':') . '</abbr>:' . implode('-',$weid_arcs);
  235.                 }
  236.                 return $weid;
  237.         }
  238.  
  239.         public function appendArcs(String $arcs) {
  240.                 $out = new self($this->oid);
  241.  
  242.                 if ($out->isRoot()) {
  243.                         $out->oid .= $arcs;
  244.                 } else {
  245.                         $out->oid .= '.' . $arcs;
  246.                 }
  247.  
  248.                 $bak_oid = $out->oid;
  249.                 $out->oid = sanitizeOID($out->oid);
  250.                 if ($out->oid === false) throw new OIDplusException(_L('%1 is not a valid OID!',$bak_oid));
  251.  
  252.                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_ID_LENGTH')-strlen(self::root());
  253.                 if (strlen($out->oid) > $maxlen) {
  254.                         throw new OIDplusException(_L('The resulting OID "%1" is too long (max allowed length: %2).',$out->oid,$maxlen));
  255.                 }
  256.  
  257.                 $depth = 0;
  258.                 foreach (explode('.',$out->oid) as $arc) {
  259.                         if (strlen($arc) > OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_ARC_SIZE')) {
  260.                                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_ARC_SIZE');
  261.                                 throw new OIDplusException(_L('Arc "%1" is too long and therefore cannot be appended to the OID "%2" (max allowed arc size is "%3")',$arc,$this->oid,$maxlen));
  262.                         }
  263.                         $depth++;
  264.                 }
  265.                 if ($depth > OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_DEPTH')) {
  266.                         $maxdepth = OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_DEPTH');
  267.                         throw new OIDplusException(_L('OID %1 has too many arcs (current depth %2, max depth %3)',$out->oid,$depth,$maxdepth));
  268.                 }
  269.  
  270.                 return $out;
  271.         }
  272.  
  273.         public function deltaDotNotation(OIDplusOid $parent) {
  274.                 if (!$parent->isRoot()) {
  275.                         if (substr($this->oid, 0, strlen($parent->oid)+1) == $parent->oid.'.') {
  276.                                 return substr($this->oid, strlen($parent->oid)+1);
  277.                         } else {
  278.                                 return false;
  279.                         }
  280.                 } else {
  281.                         return $this->oid;
  282.                 }
  283.         }
  284.  
  285.         public function getAsn1Ids() {
  286.                 $asn_ids = array();
  287.                 $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ? order by lfd", array("oid:".$this->oid));
  288.                 while ($row_asn = $res_asn->fetch_array()) {
  289.                         $name = $row_asn['name'];
  290.                         $standardized = $row_asn['standardized'];
  291.                         $well_known = $row_asn['well_known'];
  292.                         $asn_ids[] = new OIDplusOidAsn1Id($name, $standardized, $well_known);
  293.                 }
  294.                 return $asn_ids;
  295.         }
  296.  
  297.         public function getIris() {
  298.                 $iri_ids = array();
  299.                 $res_iri = OIDplus::db()->query("select * from ###iri where oid = ? order by lfd", array("oid:".$this->oid));
  300.                 while ($row_iri = $res_iri->fetch_array()) {
  301.                         $name = $row_iri['name'];
  302.                         $longarc = $row_iri['longarc'];
  303.                         $well_known = $row_iri['well_known'];
  304.                         $iri_ids[] = new OIDplusOidIri($name, $longarc, $well_known);
  305.                 }
  306.                 return $iri_ids;
  307.         }
  308.  
  309.         public function viewGetArcAsn1s(OIDplusOid $parent=null, $separator = ' | ') {
  310.                 $asn_ids = array();
  311.  
  312.                 if (is_null($parent)) $parent = OIDplusOid::parse(self::root());
  313.  
  314.                 $part = $this->deltaDotNotation($parent);
  315.  
  316.                 if (strpos($part, '.') === false) {
  317.                         $asn_id_objs = $this->getAsn1Ids();
  318.                         foreach ($asn_id_objs as $asn_id_obj) {
  319.                                 $asn_ids[] = $asn_id_obj->getName().'('.$part.')';
  320.                         }
  321.                 }
  322.  
  323.                 if (count($asn_ids) == 0) $asn_ids = array($part);
  324.                 return implode($separator, $asn_ids);
  325.         }
  326.  
  327.         public function getAsn1Notation($withAbbr=true) {
  328.                 $asn1_notation = '';
  329.                 $arcs = explode('.', $this->oid);
  330.  
  331.                 foreach ($arcs as $arc) {
  332.                         $res = OIDplus::db()->query("select name, standardized from ###asn1id where oid = ? order by lfd", array(self::root().implode('.',$arcs)));
  333.  
  334.                         $names = array();
  335.                         while ($row = $res->fetch_array()) {
  336.                                 $names[] = $row['name']."(".end($arcs).")";
  337.                                 if ($row['standardized']) {
  338.                                         $names[] = $row['name'];
  339.                                 }
  340.                         }
  341.  
  342.                         $numeric = array_pop($arcs);
  343.                         if (count($names) > 1) {
  344.                                 $first_name = array_shift($names);
  345.                                 $abbr = _L('Other identifiers').':&#10;      '.implode('&#10;      ',$names);
  346.                                 if ($withAbbr) {
  347.                                         $asn1_notation = '<abbr title="'.$abbr.'">'.$first_name.'</abbr> '.$asn1_notation;
  348.                                 } else {
  349.                                         $asn1_notation = $first_name.' '.$asn1_notation;
  350.                                 }
  351.                         } else if (count($names) == 1) {
  352.                                 $asn1_notation = array_shift($names).' '.$asn1_notation;
  353.                         } else {
  354.                                 $asn1_notation = $numeric.' '.$asn1_notation;
  355.                         }
  356.                 }
  357.  
  358.                 return "{ ".trim($asn1_notation)." }";
  359.         }
  360.  
  361.         public function getIriNotation($withAbbr=true) {
  362.                 $iri_notation = '';
  363.                 $arcs = explode('.', $this->oid);
  364.  
  365.                 foreach ($arcs as $arc) {
  366.                         $res = OIDplus::db()->query("select name, longarc from ###iri where oid = ? order by lfd", array(self::root().implode('.',$arcs)));
  367.  
  368.                         $is_longarc = false;
  369.                         $names = array();
  370.                         while ($row = $res->fetch_array()) {
  371.                                 $is_longarc = $row['longarc'];
  372.                                 $names[] = $row['name'];
  373.  
  374.                                 if ($is_longarc) {
  375.                                         $names[] = 'Joint-ISO-ITU-T/'.$row['name']; // Long arcs can only be inside root OID 2
  376.                                 }
  377.                         }
  378.  
  379.                         $names[] = array_pop($arcs);
  380.                         if (count($names) > 2) {
  381.                                 $first_name = array_shift($names);
  382.                                 $numeric = array_pop($names);
  383.                                 $abbr = _L('Other identifiers').':&#10;      '.implode('&#10;      ',$names).'&#10;'._L('Numeric value').': '.$numeric;
  384.                                 $iri_notation = $withAbbr ? '<abbr title="'.$abbr.'">'.$first_name.'</abbr>/'.$iri_notation : $first_name.'/'.$iri_notation;
  385.                         } else if (count($names) > 1) {
  386.                                 $first_name = array_shift($names);
  387.                                 $abbr = _L('Numeric value').': '.array_shift($names);
  388.                                 $iri_notation = $withAbbr ? '<abbr title="'.$abbr.'">'.$first_name.'</abbr>/'.$iri_notation : $first_name.'/'.$iri_notation;
  389.                         } else if (count($names) == 1) {
  390.                                 $iri_notation = array_shift($names) . '/' . $iri_notation;
  391.                         }
  392.  
  393.                         if ($is_longarc) break; // we don't write /ITU-T/ at the beginning, when /ITU-T/xyz is a long arc
  394.                 }
  395.                 $iri_notation = '/' . substr($iri_notation, 0, strlen($iri_notation)-1);
  396.  
  397.                 return $iri_notation;
  398.         }
  399.  
  400.         public function getDotNotation() {
  401.                 return $this->oid;
  402.         }
  403.  
  404.         public function isWellKnown() {
  405.                 $res = OIDplus::db()->query("select oid from ###asn1id where oid = ? and well_known = ?", array("oid:".$this->oid,true));
  406.                 if ($res->any()) return true;
  407.  
  408.                 $res = OIDplus::db()->query("select oid from ###iri where oid = ? and well_known = ?", array("oid:".$this->oid,true));
  409.                 if ($res->any()) return true;
  410.  
  411.                 return false;
  412.         }
  413.  
  414.         public function replaceAsn1Ids($demandedASN1s=array(), $simulate=false) {
  415.                 if ($this->isWellKnown()) {
  416.                         throw new OIDplusException(_L('OID "%1" is a "well-known" OID. Its identifiers cannot be changed.',$this->oid));
  417.                 }
  418.  
  419.                 // First do a few checks
  420.                 foreach ($demandedASN1s as &$asn1) {
  421.                         $asn1 = trim($asn1);
  422.  
  423.                         if (strlen($asn1) > OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_ASN1_ID_LEN')) {
  424.                                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_ASN1_ID_LEN');
  425.                                 throw new OIDplusException(_L('ASN.1 alphanumeric identifier "%1" is too long (max allowed length %2)',$asn1,$maxlen));
  426.                         }
  427.  
  428.                         // Validate identifier
  429.                         if (!oid_id_is_valid($asn1)) throw new OIDplusException(_L('"%1" is not a valid ASN.1 identifier!',$asn1));
  430.  
  431.                         // Check if the (real) parent has any conflict
  432.                         // Unlike IRI identifiers, ASN.1 identifiers may be used multiple times (not recommended), except if one of them is standardized
  433.                         $res = OIDplus::db()->query("select oid from ###asn1id where name = ? and standardized = ?", array($asn1,true));
  434.                         while ($row = $res->fetch_array()) {
  435.                                 $check_oid = OIDplusOid::parse($row['oid'])->oid;
  436.                                 if ((oid_up($check_oid) === oid_up($this->oid)) && // same parent
  437.                                    ($check_oid !== $this->oid))                    // different OID
  438.                                 {
  439.                                         throw new OIDplusException(_L('ASN.1 identifier "%1" is a standardized identifier belonging to OID %2',$asn1,$check_oid));
  440.                                 }
  441.                         }
  442.                 }
  443.  
  444.                 // Now do the real replacement
  445.                 if (!$simulate) {
  446.                         OIDplus::db()->query("delete from ###asn1id where oid = ?", array("oid:".$this->oid));
  447.                         foreach ($demandedASN1s as &$asn1) {
  448.                                 OIDplus::db()->query("insert into ###asn1id (oid, name) values (?, ?)", array("oid:".$this->oid, $asn1));
  449.                         }
  450.                 }
  451.         }
  452.  
  453.         public function replaceIris($demandedIris=array(), $simulate=false) {
  454.                 if ($this->isWellKnown()) {
  455.                         throw new OIDplusException(_L('OID "%1" is a "well-known" OID. Its identifiers cannot be changed.',$this->oid));
  456.                 }
  457.  
  458.                 // First do a few checks
  459.                 foreach ($demandedIris as &$iri) {
  460.                         $iri = trim($iri);
  461.  
  462.                         if (strlen($iri) > OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_UNICODE_LABEL_LEN')) {
  463.                                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_UNICODE_LABEL_LEN');
  464.                                 throw new OIDplusException(_L('Unicode label "%1" is too long (max allowed length %2)',$iri,$maxlen));
  465.                         }
  466.  
  467.                         // Validate identifier
  468.                         if (!iri_arc_valid($iri, false)) throw new OIDplusException(_L('"%1" is not a valid IRI!',$iri));
  469.  
  470.                         // Check if the (real) parent has any conflict
  471.                         $res = OIDplus::db()->query("select oid from ###iri where name = ?", array($iri));
  472.                         while ($row = $res->fetch_array()) {
  473.                                 $check_oid = OIDplusOid::parse($row['oid'])->oid;
  474.                                 if ((oid_up($check_oid) === oid_up($this->oid)) && // same parent
  475.                                    ($check_oid !== $this->oid))                    // different OID
  476.                                 {
  477.                                         throw new OIDplusException(_L('IRI "%1" is already used by another OID (%2)',$iri,$check_oid));
  478.                                 }
  479.                         }
  480.                 }
  481.  
  482.                 // Now do the real replacement
  483.                 if (!$simulate) {
  484.                         OIDplus::db()->query("delete from ###iri where oid = ?", array("oid:".$this->oid));
  485.                         foreach ($demandedIris as &$iri) {
  486.                                 OIDplus::db()->query("insert into ###iri (oid, name) values (?, ?)", array("oid:".$this->oid, $iri));
  487.                         }
  488.                 }
  489.         }
  490.  
  491.         public function one_up() {
  492.                 return self::parse(self::ns().':'.oid_up($this->oid));
  493.         }
  494.  
  495.         public function distance($to) {
  496.                 if (!is_object($to)) $to = OIDplusObject::parse($to);
  497.                 if (!($to instanceof $this)) return false;
  498.                 return oid_distance($to->oid, $this->oid);
  499.         }
  500.  
  501.         public function getAltIds() {
  502.                 if ($this->isRoot()) return array();
  503.                 $ids = parent::getAltIds();
  504.  
  505.                 if ($uuid = oid_to_uuid($this->oid)) {
  506.                         // UUID-OIDs are representation of an UUID
  507.                         $ids[] = new OIDplusAltId('guid', $uuid, _L('GUID representation of this OID'));
  508.                 } else {
  509.                         // All other OIDs can be formed into an UUID by making them a namebased OID
  510.                         // You could theoretically also do this to an UUID-OID, but we exclude this case to avoid that users are confused
  511.                         $ids[] = new OIDplusAltId('guid', gen_uuid_md5_namebased(UUID_NAMEBASED_NS_OID, $this->oid), _L('Name based version 3 / MD5 UUID with namespace %1','UUID_NAMEBASED_NS_OID'));
  512.                         $ids[] = new OIDplusAltId('guid', gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OID, $this->oid), _L('Name based version 5 / SHA1 UUID with namespace %1','UUID_NAMEBASED_NS_OID'));
  513.                 }
  514.  
  515.                 $oid_parts = explode('.',$this->nodeId(false));
  516.  
  517.                 // (VTS B1) Members
  518.                 if ($this->nodeId(false) == '1.3.6.1.4.1.37476.1') {
  519.                         $aid = 'D276000186B1';
  520.                         $aid_is_ok = aid_canonize($aid);
  521.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')');
  522.                 } else {
  523.                         if ((count($oid_parts) == 9) && ($oid_parts[0] == '1') && ($oid_parts[1] == '3') && ($oid_parts[2] == '6') && ($oid_parts[3] == '1') && ($oid_parts[4] == '4') && ($oid_parts[5] == '1') && ($oid_parts[6] == '37476') && ($oid_parts[7] == '1')) {
  524.                                 $number = str_pad($oid_parts[8],4,'0',STR_PAD_LEFT);
  525.                                 $aid = 'D276000186B1'.$number;
  526.                                 $aid_is_ok = aid_canonize($aid);
  527.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')');
  528.                         }
  529.                 }
  530.  
  531.                 // (VTS B2) Products
  532.                 if ($this->nodeId(false) == '1.3.6.1.4.1.37476.2') {
  533.                         $aid = 'D276000186B2';
  534.                         $aid_is_ok = aid_canonize($aid);
  535.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')');
  536.                 } else {
  537.                         if ((count($oid_parts) == 9) && ($oid_parts[0] == '1') && ($oid_parts[1] == '3') && ($oid_parts[2] == '6') && ($oid_parts[3] == '1') && ($oid_parts[4] == '4') && ($oid_parts[5] == '1') && ($oid_parts[6] == '37476') && ($oid_parts[7] == '2')) {
  538.                                 $number = str_pad($oid_parts[8],4,'0',STR_PAD_LEFT);
  539.                                 $aid = 'D276000186B2'.$number;
  540.                                 $aid_is_ok = aid_canonize($aid);
  541.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')');
  542.                         }
  543.                 }
  544.  
  545.                 // (VTS B2 00 05) OIDplus System AID / Information Object AID
  546.                 if ((count($oid_parts) == 10) && ($oid_parts[0] == '1') && ($oid_parts[1] == '3') && ($oid_parts[2] == '6') && ($oid_parts[3] == '1') && ($oid_parts[4] == '4') && ($oid_parts[5] == '1') && ($oid_parts[6] == '37476') && ($oid_parts[7] == '30') && ($oid_parts[8] == '9')) {
  547.                         $sid = $oid_parts[9];
  548.                         $sid_hex = strtoupper(str_pad(dechex((int)$sid),8,'0',STR_PAD_LEFT));
  549.                         $aid = 'D276000186B20005'.$sid_hex;
  550.                         $aid_is_ok = aid_canonize($aid);
  551.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('OIDplus System Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')');
  552.                 }
  553.                 else if ((count($oid_parts) == 11) && ($oid_parts[0] == '1') && ($oid_parts[1] == '3') && ($oid_parts[2] == '6') && ($oid_parts[3] == '1') && ($oid_parts[4] == '4') && ($oid_parts[5] == '1') && ($oid_parts[6] == '37476') && ($oid_parts[7] == '30') && ($oid_parts[8] == '9')) {
  554.                         $sid = $oid_parts[9];
  555.                         $obj = $oid_parts[10];
  556.                         $sid_hex = strtoupper(str_pad(dechex((int)$sid),8,'0',STR_PAD_LEFT));
  557.                         $obj_hex = strtoupper(str_pad(dechex((int)$obj),8,'0',STR_PAD_LEFT));
  558.                         $aid = 'D276000186B20005'.$sid_hex.$obj_hex;
  559.                         $aid_is_ok = aid_canonize($aid);
  560.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('OIDplus Information Object Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')');
  561.                 }
  562.  
  563.                 // (VTS F0) IANA PEN to AID Mapping (PIX allowed)
  564.                 if ((count($oid_parts) == 7) && ($oid_parts[0] == '1') && ($oid_parts[1] == '3') && ($oid_parts[2] == '6') && ($oid_parts[3] == '1') && ($oid_parts[4] == '4') && ($oid_parts[5] == '1')) {
  565.                         $pen = $oid_parts[6];
  566.                         $aid = 'D276000186F0'.$pen;
  567.                         if (strlen($aid)%2 == 1) $aid .= 'F';
  568.                         $aid_is_ok = aid_canonize($aid);
  569.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, with "FF" prefix').')');
  570.                         $ids[] = new OIDplusAltId('iana-pen', $pen, _L('IANA Private Enterprise Number (PEN)'));
  571.                 }
  572.  
  573.                 // (VTS F1) FreeOID to AID Mapping (PIX allowed)
  574.                 if ((count($oid_parts) == 9) && ($oid_parts[0] == '1') && ($oid_parts[1] == '3') && ($oid_parts[2] == '6') && ($oid_parts[3] == '1') && ($oid_parts[4] == '4') && ($oid_parts[5] == '1') && ($oid_parts[6] == '37476') && ($oid_parts[7] == '9000')) {
  575.                         $number = $oid_parts[8];
  576.                         $aid = 'D276000186F1'.$number;
  577.                         if (strlen($aid)%2 == 1) $aid .= 'F';
  578.                         $aid_is_ok = aid_canonize($aid);
  579.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, with "FF" prefix').')');
  580.                 }
  581.  
  582.                 // (VTS F6) Mapping OID-to-AID if possible
  583.                 try {
  584.                         $test_der = \OidDerConverter::hexarrayToStr(\OidDerConverter::oidToDER($this->nodeId(false)));
  585.                 } catch (\Exception $e) {
  586.                         $test_der = '00'; // error, should not happen
  587.                 }
  588.                 if (substr($test_der,0,3) == '06 ') { // 06 = ASN.1 type of Absolute ID
  589.                         if (($oid_parts[0] == '2') && ($oid_parts[1] == '999')) {
  590.                                 // Note that "ViaThinkSoft E0" AID are not unique!
  591.                                 // OIDplus will use the relative DER of the 2.999.xx OID as PIX
  592.                                 $aid_candidate = 'D2 76 00 01 86 E0 ' . substr($test_der, strlen('06 xx 88 37 ')); // Remove ASN.1 06=Type, xx=Length and the 2.999 arcs "88 37"
  593.                                 $aid_is_ok = aid_canonize($aid_candidate);
  594.                                 if (!$aid_is_ok) {
  595.                                         // If DER encoding is not possible (too long), then we will use a 32 bit small hash.
  596.                                         $small_hash = str_pad(dechex(smallhash($this->nodeId(false))),8,'0',STR_PAD_LEFT);
  597.                                         $aid_candidate = 'D2 76 00 01 86 E0 ' . strtoupper(implode(' ',str_split($small_hash,2)));
  598.                                         $aid_is_ok = aid_canonize($aid_candidate);
  599.                                 }
  600.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'));
  601.                         } else if (($oid_parts[0] == '0') && ($oid_parts[1] == '4') && ($oid_parts[2] == '0') && ($oid_parts[3] == '127') && ($oid_parts[4] == '0') && ($oid_parts[5] == '7')) {
  602.                                 // Illegal usage of E8 by German BSI, plus using E8+Len+OID instead of E8+OID like ISO does
  603.                                 // PIX probably not used
  604.                                 $aid_candidate = 'E8 '.substr($test_der, strlen('06 ')); // Remove ASN.1 06=Type
  605.                                 $aid_is_ok = aid_canonize($aid_candidate);
  606.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'));
  607.                         } else if (($oid_parts[0] == '1') && ($oid_parts[1] == '0')) {
  608.                                 // ISO Standard AID (OID 1.0.xx)
  609.                                 // Optional PIX allowed
  610.                                 $aid_candidate = 'E8 '.substr($test_der, strlen('06 xx ')); // Remove ASN.1 06=Type and xx=Length
  611.                                 $aid_is_ok = aid_canonize($aid_candidate);
  612.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')');
  613.                         } else {
  614.                                 // All other OIDs can be mapped using the "ViaThinkSoft F6" scheme, but only if the DER encoding is not too long
  615.                                 // No PIX allowed
  616.                                 $aid_candidate = 'D2 76 00 01 86 F6 '.substr($test_der, strlen('06 xx ')); // Remove ASN.1 06=Type and xx=Length
  617.                                 $aid_is_ok = aid_canonize($aid_candidate);
  618.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')');
  619.                         }
  620.                 }
  621.  
  622.                 return $ids;
  623.         }
  624.  
  625.         public function getDirectoryName() {
  626.                 if ($this->isRoot()) return $this->ns();
  627.                 $oid = $this->nodeId(false);
  628.                 return $this->ns().'_'.str_replace('.', '_', $oid);
  629.         }
  630.  
  631.         public static function treeIconFilename($mode) {
  632.                 return 'img/'.$mode.'_icon16.png';
  633.         }
  634. }
  635.