Subversion Repositories oidplus

Rev

Rev 1424 | Rev 1439 | 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.         /**
  28.          * @var string
  29.          */
  30.         private $oid;
  31.  
  32.         /**
  33.          * @param string $oid
  34.          * @throws OIDplusException
  35.          */
  36.         public function __construct(string $oid) {
  37.                 $bak_oid = $oid;
  38.  
  39.                 $oid = sanitizeOID($oid, 'auto');
  40.                 if ($oid === false) {
  41.                         throw new OIDplusException(_L('Invalid OID %1',$bak_oid));
  42.                 }
  43.  
  44.                 if (($oid != '') && (!oid_valid_dotnotation($oid, false, true, 0))) {
  45.                         // avoid OIDs like 3.0
  46.                         throw new OIDplusException(_L('Invalid OID %1',$bak_oid));
  47.                 }
  48.  
  49.                 $this->oid = $oid;
  50.         }
  51.  
  52.         /**
  53.          * @param string $node_id
  54.          * @return OIDplusOid|null
  55.          * @throws OIDplusException
  56.          */
  57.         public static function parse(string $node_id)/*: ?OIDplusOid*/ {
  58.                 @list($namespace, $oid) = explode(':', $node_id, 2);
  59.                 if ($namespace !== self::ns()) return null;
  60.                 return new self($oid);
  61.         }
  62.  
  63.         /**
  64.          * @return string
  65.          */
  66.         public static function objectTypeTitle(): string {
  67.                 return _L('Object Identifier (OID)');
  68.         }
  69.  
  70.         /**
  71.          * @return string
  72.          */
  73.         public static function objectTypeTitleShort(): string {
  74.                 return _L('OID');
  75.         }
  76.  
  77.         /**
  78.          * @return string
  79.          */
  80.         public static function ns(): string {
  81.                 return 'oid';
  82.         }
  83.  
  84.         /**
  85.          * @return string
  86.          */
  87.         public static function root(): string {
  88.                 return self::ns().':';
  89.         }
  90.  
  91.         /**
  92.          * @return bool
  93.          */
  94.         public function isRoot(): bool {
  95.                 return $this->oid == '';
  96.         }
  97.  
  98.         /**
  99.          * @param bool $with_ns
  100.          * @return string
  101.          */
  102.         public function nodeId(bool $with_ns=true): string {
  103.                 return $with_ns ? self::root().$this->oid : $this->oid;
  104.         }
  105.  
  106.         /**
  107.          * @param string $str
  108.          * @return string
  109.          * @throws OIDplusException
  110.          */
  111.         public function addString(string $str): string {
  112.                 if (!$this->isRoot()) {
  113.                         if (strpos($str,'.') !== false) throw new OIDplusException(_L('Please only submit one arc (not an absolute OID or multiple arcs).'));
  114.                 }
  115.  
  116.                 return $this->appendArcs($str)->nodeId();
  117.         }
  118.  
  119.         /**
  120.          * @param OIDplusObject $parent
  121.          * @return string
  122.          */
  123.         public function crudShowId(OIDplusObject $parent): string {
  124.                 if ($parent instanceof OIDplusOid) {
  125.                         return $this->deltaDotNotation($parent);
  126.                 } else {
  127.                         return '';
  128.                 }
  129.         }
  130.  
  131.         /**
  132.          * @param OIDplusObject|null $parent
  133.          * @return string
  134.          * @throws OIDplusException
  135.          */
  136.         public function jsTreeNodeName(OIDplusObject $parent = null): string {
  137.                 if ($parent == null) return $this->objectTypeTitle();
  138.                 if ($parent instanceof OIDplusOid) {
  139.                         return $this->viewGetArcAsn1s($parent);
  140.                 } else {
  141.                         return '';
  142.                 }
  143.         }
  144.  
  145.         /**
  146.          * @return string
  147.          */
  148.         public function defaultTitle(): string {
  149.                 return _L('OID %1',$this->oid);
  150.         }
  151.  
  152.         /**
  153.          * @return bool
  154.          */
  155.         public function isLeafNode(): bool {
  156.                 return false;
  157.         }
  158.  
  159.         /**
  160.          * @return string[]
  161.          * @throws OIDplusException
  162.          */
  163.         private function getTechInfo(): array {
  164.                 $tech_info = array();
  165.  
  166.                 $tmp = _L('Dot notation');
  167.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="http://oid-info.com/faq.htm#14" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  168.                 $tech_info[$tmp] = $this->getDotNotation();
  169.  
  170.                 $tmp = _L('ASN.1 notation');
  171.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="http://oid-info.com/faq.htm#17" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  172.                 $tech_info[$tmp] = $this->getAsn1Notation();
  173.  
  174.                 $tmp = _L('OID-IRI notation');
  175.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="http://oid-info.com/faq.htm#iri" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  176.                 $tech_info[$tmp] = $this->getIriNotation();
  177.  
  178.                 $tmp = _L('WEID notation');
  179.                 $tmp = str_replace(explode(' ', $tmp, 2)[0], '<a href="https://weid.info/" target="_blank">'.explode(' ', $tmp, 2)[0].'</a>', $tmp);
  180.                 $tech_info[$tmp] = $this->getWeidNotation();
  181.  
  182.                 $tmp = _L('DER encoding');
  183.                 $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);
  184.                 $tech_info[$tmp] = str_replace(' ', ':', \OidDerConverter::hexarrayToStr(\OidDerConverter::oidToDER($this->nodeId(false))));
  185.  
  186.                 return $tech_info;
  187.         }
  188.  
  189.         /**
  190.          * @return bool
  191.          */
  192.         protected function isClassCWeid(): bool {
  193.                 $dist = oid_distance($this->oid, '1.3.6.1.4.1.37553.8');
  194.                 if ($dist === false) return false;
  195.                 return $dist >= 0;
  196.         }
  197.  
  198.         /**
  199.          * @param string $title
  200.          * @param string $content
  201.          * @param string $icon
  202.          * @return void
  203.          * @throws OIDplusException
  204.          */
  205.         public function getContentPage(string &$title, string &$content, string &$icon) {
  206.                 if ($this->isClassCWeid()) {
  207.                         // TODO: Also change treeview menu mini-icon?
  208.                         $icon = file_exists(__DIR__.'/img/weid_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/weid_icon.png' : '';
  209.                 } else {
  210.                         $icon = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  211.                 }
  212.  
  213.                 if ($this->isRoot()) {
  214.                         $title = OIDplusOid::objectTypeTitle();
  215.  
  216.                         $res = OIDplus::db()->query("select id from ###objects where parent = ?", array(self::root()));
  217.                         if ($res->any()) {
  218.                                 $content = _L('Please select an OID in the tree view at the left to show its contents.');
  219.                         } else {
  220.                                 $content = _L('Currently, no OID is registered in the system.');
  221.                         }
  222.  
  223.                         if (!$this->isLeafNode()) {
  224.                                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  225.                                         $content .= '<h2>'._L('Manage your root OIDs').'</h2>';
  226.                                 } else {
  227.                                         $content .= '<h2>'._L('Root OIDs').'</h2>';
  228.                                 }
  229.                                 $content .= '%%CRUD%%';
  230.                         }
  231.                 } else {
  232.                         $title = $this->getTitle();
  233.  
  234.                         $tech_info = $this->getTechInfo();
  235.                         $tech_info_html = '';
  236.                         if (count($tech_info) > 0) {
  237.                                 $tech_info_html .= '<h2>'._L('Technical information').'</h2>';
  238.                                 $tech_info_html .= '<div style="overflow:auto"><table border="0">';
  239.                                 foreach ($tech_info as $key => $value) {
  240.                                         $tech_info_html .= '<tr><td valign="top" style="white-space: nowrap;">'.$key.': </td><td><code>'.$value.'</code></td></tr>';
  241.                                 }
  242.                                 $tech_info_html .= '</table></div>';
  243.                         }
  244.  
  245.                         $content = $tech_info_html;
  246.  
  247.                         $content .= '<h2>'._L('Description').'</h2>%%DESC%%'.
  248.                                     '<h2>'._L('Registration Authority').'</h2>%%RA_INFO%%';
  249.  
  250.                         if (!$this->isLeafNode()) {
  251.                                 if ($this->userHasWriteRights()) {
  252.                                         $content .= '<h2>'._L('Create or change subordinate objects').'</h2>';
  253.                                 } else {
  254.                                         $content .= '<h2>'._L('Subordinate objects').'</h2>';
  255.                                 }
  256.                                 $content .= '%%CRUD%%';
  257.                         }
  258.                 }
  259.         }
  260.  
  261.         # ---
  262.  
  263.         /**
  264.          * Gets the last arc of an WEID
  265.          * @return false|string
  266.          */
  267.         public function weidArc() {
  268.                 // Dirty hack: We prepend '0.' in front of the OID to enforce the
  269.                 //             creation of a Class A weid (weid:root:) . Otherwise we could not
  270.                 //             get the hidden arc value "8" from "weid:4" (which is actually "weid:pen:SZ5-8-?"
  271.                 $weid = \Frdl\Weid\WeidOidConverter::oid2weid('0.'.$this->getDotNotation());
  272.                 if ($weid === false) return false;
  273.                 $ary = explode(':', $weid);
  274.                 $weid = array_pop($ary); // remove namespace and sub-namespace if existing
  275.                 $x = explode('-', $weid);
  276.                 if (count($x) < 2) return ''; // WEID root arc. Has no name
  277.                 return $x[count($x)-2];
  278.         }
  279.  
  280.         /**
  281.          * @param bool $withAbbr
  282.          * @return string
  283.          */
  284.         public function getWeidNotation(bool $withAbbr=true): string {
  285.                 $weid = \Frdl\Weid\WeidOidConverter::oid2weid($this->getDotNotation());
  286.                 if ($withAbbr) {
  287.                         $ary = explode(':', $weid);
  288.                         $weid = array_pop($ary); // remove namespace and sub-namespace if existing
  289.                         $ns = implode(':', $ary).':';
  290.  
  291.                         $weid_arcs = explode('-', $weid);
  292.                         foreach ($weid_arcs as $i => &$weid) {
  293.                                 if ($i == count($weid_arcs)-1) {
  294.                                         $weid = '<abbr title="'._L('weLuhn check digit').'">'.$weid.'</abbr>';
  295.                                 } else {
  296.                                         $oid_arcs = explode('.',$this->oid);
  297.                                         $weid_num = $oid_arcs[(count($oid_arcs)-1)-(count($weid_arcs)-1)+($i+1)];
  298.                                         if ($weid_num != $weid) {
  299.                                                 $weid = '<abbr title="'._L('Numeric value').': '.$weid_num.'">'.$weid.'</abbr>';
  300.                                         }
  301.                                 }
  302.                         }
  303.                         $base_arc = '???';
  304.                         if ($ns === 'weid:')      $base_arc = '1.3.6.1.4.1.37553.8';
  305.                         if ($ns === 'weid:pen:')  $base_arc = '1.3.6.1.4.1';
  306.                         if ($ns === 'weid:root:') $base_arc = _L('OID tree root');
  307.  
  308.                         $weid = '<abbr title="'._L('Base OID').': '.$base_arc.'">' . rtrim($ns,':') . '</abbr>:' . implode('-',$weid_arcs);
  309.                 }
  310.                 return $weid;
  311.         }
  312.  
  313.         /**
  314.          * @param string|int $arcs
  315.          * @return OIDplusOid
  316.          * @throws OIDplusException
  317.          */
  318.         public function appendArcs($arcs): OIDplusOid {
  319.                 $out = new self($this->oid);
  320.  
  321.                 if ($out->isRoot()) {
  322.                         $out->oid .= $arcs;
  323.                 } else {
  324.                         $out->oid .= '.' . $arcs;
  325.                 }
  326.  
  327.                 $bak_oid = $out->oid;
  328.                 $out->oid = sanitizeOID($out->oid);
  329.                 if ($out->oid === false) throw new OIDplusException(_L('%1 is not a valid OID!',$bak_oid));
  330.  
  331.                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_ID_LENGTH')-strlen(self::root());
  332.                 if (strlen($out->oid) > $maxlen) {
  333.                         throw new OIDplusException(_L('The resulting OID "%1" is too long (max allowed length: %2).',$out->oid,$maxlen));
  334.                 }
  335.  
  336.                 return $out;
  337.         }
  338.  
  339.         /**
  340.          * @param OIDplusOid $parent
  341.          * @return false|string
  342.          */
  343.         public function deltaDotNotation(OIDplusOid $parent) {
  344.                 if (!$parent->isRoot()) {
  345.                         if (substr($this->oid, 0, strlen($parent->oid)+1) == $parent->oid.'.') {
  346.                                 return substr($this->oid, strlen($parent->oid)+1);
  347.                         } else {
  348.                                 return false;
  349.                         }
  350.                 } else {
  351.                         return $this->oid;
  352.                 }
  353.         }
  354.  
  355.         /**
  356.          * @return OIDplusOidAsn1Id[]
  357.          * @throws OIDplusException
  358.          */
  359.         public function getAsn1Ids(): array {
  360.                 $asn_ids = array();
  361.                 $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ? order by lfd", array("oid:".$this->oid));
  362.                 while ($row_asn = $res_asn->fetch_array()) {
  363.                         $name = $row_asn['name'];
  364.                         $standardized = $row_asn['standardized'] ?? false;
  365.                         $well_known = $row_asn['well_known'] ?? false;
  366.                         $asn_ids[] = new OIDplusOidAsn1Id($name, $standardized, $well_known);
  367.                 }
  368.                 return $asn_ids;
  369.         }
  370.  
  371.         /**
  372.          * @return OIDplusOidIri[]
  373.          * @throws OIDplusException
  374.          */
  375.         public function getIris(): array {
  376.                 $iri_ids = array();
  377.                 $res_iri = OIDplus::db()->query("select * from ###iri where oid = ? order by lfd", array("oid:".$this->oid));
  378.                 while ($row_iri = $res_iri->fetch_array()) {
  379.                         $name = $row_iri['name'];
  380.                         $longarc = $row_iri['longarc'] ?? false;
  381.                         $well_known = $row_iri['well_known'] ?? false;
  382.                         $iri_ids[] = new OIDplusOidIri($name, $longarc, $well_known);
  383.                 }
  384.                 return $iri_ids;
  385.         }
  386.  
  387.         /**
  388.          * @param OIDplusOid|null $parent
  389.          * @param string $separator
  390.          * @return string
  391.          * @throws OIDplusException
  392.          */
  393.         public function viewGetArcAsn1s(OIDplusOid $parent=null, string $separator = ' | '): string {
  394.                 $asn_ids = array();
  395.  
  396.                 if (is_null($parent)) $parent = OIDplusOid::parse(self::root());
  397.  
  398.                 $part = $this->deltaDotNotation($parent);
  399.  
  400.                 if (strpos($part, '.') === false) {
  401.                         $asn_id_objs = $this->getAsn1Ids();
  402.                         foreach ($asn_id_objs as $asn_id_obj) {
  403.                                 $asn_ids[] = $asn_id_obj->getName().'('.$part.')';
  404.                         }
  405.                 }
  406.  
  407.                 if (count($asn_ids) == 0) $asn_ids = array($part);
  408.                 return implode($separator, $asn_ids);
  409.         }
  410.  
  411.         /**
  412.          * @param bool $withAbbr
  413.          * @return string
  414.          * @throws OIDplusException
  415.          */
  416.         public function getAsn1Notation(bool $withAbbr=true): string {
  417.                 $asn1_notation = '';
  418.                 $arcs = explode('.', $this->oid);
  419.  
  420.                 foreach ($arcs as $arc) {
  421.                         $res = OIDplus::db()->query("select name, standardized from ###asn1id where oid = ? order by lfd", array(self::root().implode('.',$arcs)));
  422.  
  423.                         $names = array();
  424.                         while ($row = $res->fetch_array()) {
  425.                                 $names[] = $row['name']."(".end($arcs).")";
  426.                                 if ($row['standardized']) {
  427.                                         $names[] = $row['name'];
  428.                                 }
  429.                         }
  430.  
  431.                         $numeric = array_pop($arcs);
  432.                         if (count($names) > 1) {
  433.                                 $first_name = array_shift($names);
  434.                                 $abbr = _L('Other identifiers').':&#10;      '.implode('&#10;      ',$names);
  435.                                 if ($withAbbr) {
  436.                                         $asn1_notation = '<abbr title="'.$abbr.'">'.$first_name.'</abbr> '.$asn1_notation;
  437.                                 } else {
  438.                                         $asn1_notation = $first_name.' '.$asn1_notation;
  439.                                 }
  440.                         } else if (count($names) == 1) {
  441.                                 $asn1_notation = array_shift($names).' '.$asn1_notation;
  442.                         } else {
  443.                                 $asn1_notation = $numeric.' '.$asn1_notation;
  444.                         }
  445.                 }
  446.  
  447.                 return "{ ".trim($asn1_notation)." }";
  448.         }
  449.  
  450.         /**
  451.          * @param bool $withAbbr
  452.          * @return string
  453.          * @throws OIDplusException
  454.          */
  455.         public function getIriNotation(bool $withAbbr=true): string {
  456.                 $iri_notation = '';
  457.                 $arcs = explode('.', $this->oid);
  458.  
  459.                 foreach ($arcs as $arc) {
  460.                         $res = OIDplus::db()->query("select name, longarc from ###iri where oid = ? order by lfd", array(self::root().implode('.',$arcs)));
  461.  
  462.                         $is_longarc = false;
  463.                         $names = array();
  464.                         while ($row = $res->fetch_array()) {
  465.                                 $is_longarc = $row['longarc'];
  466.                                 $names[] = $row['name'];
  467.  
  468.                                 if ($is_longarc) {
  469.                                         $names[] = 'Joint-ISO-ITU-T/'.$row['name']; // Long arcs can only be inside root OID 2
  470.                                 }
  471.                         }
  472.  
  473.                         $names[] = array_pop($arcs);
  474.                         if (count($names) > 2) {
  475.                                 $first_name = array_shift($names);
  476.                                 $numeric = array_pop($names);
  477.                                 $abbr = _L('Other identifiers').':&#10;      '.implode('&#10;      ',$names).'&#10;'._L('Numeric value').': '.$numeric;
  478.                                 $iri_notation = $withAbbr ? '<abbr title="'.$abbr.'">'.$first_name.'</abbr>/'.$iri_notation : $first_name.'/'.$iri_notation;
  479.                         } else if (count($names) > 1) {
  480.                                 $first_name = array_shift($names);
  481.                                 $abbr = _L('Numeric value').': '.array_shift($names);
  482.                                 $iri_notation = $withAbbr ? '<abbr title="'.$abbr.'">'.$first_name.'</abbr>/'.$iri_notation : $first_name.'/'.$iri_notation;
  483.                         } else if (count($names) == 1) {
  484.                                 $iri_notation = array_shift($names) . '/' . $iri_notation;
  485.                         }
  486.  
  487.                         if ($is_longarc) break; // we don't write /ITU-T/ at the beginning, when /ITU-T/xyz is a long arc
  488.                 }
  489.                 return '/' . substr($iri_notation, 0, strlen($iri_notation)-1);
  490.         }
  491.  
  492.         /**
  493.          * @return string
  494.          */
  495.         public function getDotNotation(): string {
  496.                 return $this->oid;
  497.         }
  498.  
  499.         /**
  500.          * @return bool
  501.          * @throws OIDplusException
  502.          */
  503.         public function isWellKnown(): bool {
  504.                 $res = OIDplus::db()->query("select oid from ###asn1id where oid = ? and well_known = ?", array("oid:".$this->oid,true));
  505.                 if ($res->any()) return true;
  506.  
  507.                 $res = OIDplus::db()->query("select oid from ###iri where oid = ? and well_known = ?", array("oid:".$this->oid,true));
  508.                 if ($res->any()) return true;
  509.  
  510.                 return false;
  511.         }
  512.  
  513.         /**
  514.          * @param array $demandedASN1s
  515.          * @param bool $simulate
  516.          * @return void
  517.          * @throws OIDplusException
  518.          */
  519.         public function replaceAsn1Ids(array $demandedASN1s=array(), bool $simulate=false) {
  520.                 if ($this->isWellKnown()) {
  521.                         throw new OIDplusException(_L('OID "%1" is a "well-known" OID. Its identifiers cannot be changed.',$this->oid));
  522.                 }
  523.  
  524.                 // First do a few checks
  525.                 foreach ($demandedASN1s as &$asn1) {
  526.                         $asn1 = trim($asn1);
  527.  
  528.                         if (strlen($asn1) > OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_ASN1_ID_LEN')) {
  529.                                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_ASN1_ID_LEN');
  530.                                 throw new OIDplusException(_L('ASN.1 alphanumeric identifier "%1" is too long (max allowed length %2)',$asn1,$maxlen));
  531.                         }
  532.  
  533.                         // Validate identifier
  534.                         if (!oid_id_is_valid($asn1)) throw new OIDplusException(_L('"%1" is not a valid ASN.1 identifier!',$asn1));
  535.  
  536.                         // Check if the (real) parent has any conflict
  537.                         // Unlike IRI identifiers, ASN.1 identifiers may be used multiple times (not recommended), except if one of them is standardized
  538.                         $res = OIDplus::db()->query("select oid from ###asn1id where name = ? and standardized = ?", array($asn1,true)); // Attention: Requires case-SENSITIVE database collation!!
  539.                         while ($row = $res->fetch_array()) {
  540.                                 $check_oid = OIDplusOid::parse($row['oid'])->oid;
  541.                                 if ((oid_up($check_oid) === oid_up($this->oid)) && // same parent
  542.                                    ($check_oid !== $this->oid))                    // different OID
  543.                                 {
  544.                                         throw new OIDplusException(_L('ASN.1 identifier "%1" is a standardized identifier belonging to OID %2',$asn1,$check_oid));
  545.                                 }
  546.                         }
  547.                 }
  548.                 unset($asn1); // Very important, otherwise we would modify the array if we later use "foreach ($demandedASN1s as $asn1)"
  549.  
  550.                 // Now do the real replacement
  551.                 if (!$simulate) {
  552.                         OIDplus::db()->query("delete from ###asn1id where oid = ?", array("oid:".$this->oid));
  553.                         foreach ($demandedASN1s as $asn1) {
  554.                                 OIDplus::db()->query("insert into ###asn1id (oid, name, well_known, standardized) values (?, ?, ?, ?)", array("oid:".$this->oid, $asn1, false, false));
  555.                         }
  556.                 }
  557.         }
  558.  
  559.         /**
  560.          * @param array $demandedIris
  561.          * @param bool $simulate
  562.          * @return void
  563.          * @throws OIDplusException
  564.          */
  565.         public function replaceIris(array $demandedIris=array(), bool $simulate=false) {
  566.                 if ($this->isWellKnown()) {
  567.                         throw new OIDplusException(_L('OID "%1" is a "well-known" OID. Its identifiers cannot be changed.',$this->oid));
  568.                 }
  569.  
  570.                 // First do a few checks
  571.                 foreach ($demandedIris as &$iri) {
  572.                         $iri = trim($iri);
  573.  
  574.                         if (strlen($iri) > OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_UNICODE_LABEL_LEN')) {
  575.                                 $maxlen = OIDplus::baseConfig()->getValue('LIMITS_MAX_OID_UNICODE_LABEL_LEN');
  576.                                 throw new OIDplusException(_L('Unicode label "%1" is too long (max allowed length %2)',$iri,$maxlen));
  577.                         }
  578.  
  579.                         // Validate identifier
  580.                         if (!iri_arc_valid($iri, false)) throw new OIDplusException(_L('"%1" is not a valid IRI!',$iri));
  581.  
  582.                         // Check if the (real) parent has any conflict
  583.                         $res = OIDplus::db()->query("select oid from ###iri where name = ?", array($iri)); // Attention: Requires case-SENSITIVE database collation!!
  584.                         while ($row = $res->fetch_array()) {
  585.                                 $check_oid = OIDplusOid::parse($row['oid'])->oid;
  586.                                 if ((oid_up($check_oid) === oid_up($this->oid)) && // same parent
  587.                                    ($check_oid !== $this->oid))                    // different OID
  588.                                 {
  589.                                         throw new OIDplusException(_L('IRI "%1" is already used by another OID (%2)',$iri,$check_oid));
  590.                                 }
  591.                         }
  592.                 }
  593.                 unset($iri); // Very important, otherwise we would modify the array if we later use "foreach ($demandedIris as $iri)"
  594.  
  595.                 // Now do the real replacement
  596.                 if (!$simulate) {
  597.                         OIDplus::db()->query("delete from ###iri where oid = ?", array("oid:".$this->oid));
  598.                         foreach ($demandedIris as $iri) {
  599.                                 OIDplus::db()->query("insert into ###iri (oid, name, longarc, well_known) values (?, ?, ?, ?)", array("oid:".$this->oid, $iri, false, false));
  600.                         }
  601.                 }
  602.         }
  603.  
  604.         /**
  605.          * @return OIDplusOid|null
  606.          */
  607.         public function one_up()/*: ?OIDplusOid*/ {
  608.                 return self::parse(self::ns().':'.oid_up($this->oid));
  609.         }
  610.  
  611.         /**
  612.          * @param OIDplusObject|string $to
  613.          * @return int|null
  614.          */
  615.         public function distance($to) {
  616.                 if (!is_object($to)) $to = OIDplusObject::parse($to);
  617.                 if (!$to) return null;
  618.                 if (!($to instanceof $this)) return null;
  619.                 $res = oid_distance($to->oid, $this->oid);
  620.                 return $res !== false ? $res : null;
  621.         }
  622.  
  623.         /**
  624.          * @return OIDplusAltId[]
  625.          * @throws OIDplusException
  626.          */
  627.         public function getAltIds(): array {
  628.                 if ($this->isRoot()) return array();
  629.                 $ids = parent::getAltIds();
  630.  
  631.                 // R74n "Multiplane", see https://r74n.com/multiplane/
  632.                 // Vendor space:
  633.                 // [0x2aabb] = 1.3.6.1.4.1.61117.1.[0x2aa00].[0xbb]
  634.                 // Every other space:
  635.                 // [0xcaabb] = 1.3.6.1.4.1.61117.1.[0xcaabb]
  636.                 $multiplane = null;
  637.                 $tmp_oid = oid_up($this->oid);
  638.                 for ($i=0; $i<=0xFF; $i++) {
  639.                         $vid = 0x20000 + ($i<<16);
  640.                         if ($tmp_oid == '1.3.6.1.4.1.61117.1.'.$vid) {
  641.                                 $j = (int)substr($this->oid,strlen($tmp_oid)+1);
  642.                                 if (($j>=0) && ($j<=0xFF)) {
  643.                                         $xi = str_pad(dechex($i), 2, '0', STR_PAD_LEFT);
  644.                                         $xj = str_pad(dechex($j), 2, '0', STR_PAD_LEFT);
  645.                                         $multiplane = strtoupper('R2'.$xi.$xj);
  646.                                 }
  647.                                 break;
  648.                         }
  649.                 }
  650.                 if ($tmp_oid == '1.3.6.1.4.1.61117.1') {
  651.                         $ij = (int)substr($this->oid,strlen('1.3.6.1.4.1.61117.1.'));
  652.                         if ((($ij>=0) && ($ij<=0x1FFFF)) || (($ij>0x30000) && ($ij<=0xFFFFF))) {
  653.                                 $multiplane = strtoupper('R'.str_pad(dechex($ij), 5, '0', STR_PAD_LEFT));
  654.                         }
  655.                 }
  656.                 if (!is_null($multiplane)) {
  657.                         $ids[] = new OIDplusAltId('r74n-multiplane', $multiplane, _L('R74n-Multiplane'));
  658.                 }
  659.  
  660.                 if ($uuid = oid_to_uuid($this->oid)) {
  661.                         // UUID-OIDs are representation of an UUID
  662.                         $ids[] = new OIDplusAltId('guid', $uuid, _L('UUID representation of this OID'));
  663.                 } else {
  664.                         // All other OIDs can be formed into an UUID by making them a namebased OID
  665.                         // You could theoretically also do this to an UUID-OID, but we exclude this case to avoid that users are confused
  666.                         $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'));
  667.                         $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'));
  668.                 }
  669.  
  670.                 $oid_parts = explode('.',$this->nodeId(false));
  671.  
  672.                 // (VTS B1) Members
  673.                 if ($this->nodeId(false) == '1.3.6.1.4.1.37476.1') {
  674.                         $aid = 'D276000186B1';
  675.                         $aid_is_ok = aid_canonize($aid);
  676.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186B1');
  677.                 } else {
  678.                         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')) {
  679.                                 $number = str_pad($oid_parts[8],4,'0',STR_PAD_LEFT);
  680.                                 $aid = 'D276000186B1'.$number;
  681.                                 $aid_is_ok = aid_canonize($aid);
  682.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186B1');
  683.                         }
  684.                 }
  685.  
  686.                 // (VTS B2) Products
  687.                 if ($this->nodeId(false) == '1.3.6.1.4.1.37476.2') {
  688.                         $aid = 'D276000186B2';
  689.                         $aid_is_ok = aid_canonize($aid);
  690.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186B2');
  691.                 } else {
  692.                         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')) {
  693.                                 $number = str_pad($oid_parts[8],4,'0',STR_PAD_LEFT);
  694.                                 $aid = 'D276000186B2'.$number;
  695.                                 $aid_is_ok = aid_canonize($aid);
  696.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186B2');
  697.                         }
  698.                 }
  699.  
  700.                 // (VTS B2 00 05) OIDplus System AID / Information Object AID
  701.                 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')) {
  702.                         $sid = $oid_parts[9];
  703.                         $sid_hex = strtoupper(str_pad(dechex((int)$sid),8,'0',STR_PAD_LEFT));
  704.                         $aid = 'D276000186B20005'.$sid_hex;
  705.                         $aid_is_ok = aid_canonize($aid);
  706.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('OIDplus System Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186B20005');
  707.                 }
  708.                 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')) {
  709.                         $sid = $oid_parts[9];
  710.                         $obj = $oid_parts[10];
  711.                         $sid_hex = strtoupper(str_pad(dechex((int)$sid),8,'0',STR_PAD_LEFT));
  712.                         $obj_hex = strtoupper(str_pad(dechex((int)$obj),8,'0',STR_PAD_LEFT));
  713.                         $aid = 'D276000186B20005'.$sid_hex.$obj_hex;
  714.                         $aid_is_ok = aid_canonize($aid);
  715.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('OIDplus Information Object Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186B20005');
  716.                 }
  717.  
  718.                 // (VTS F0) IANA PEN to AID Mapping (PIX allowed)
  719.                 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')) {
  720.                         $pen = $oid_parts[6];
  721.                         $aid = 'D276000186F0'.$pen;
  722.                         if (strlen($aid)%2 == 1) $aid .= 'F';
  723.                         $aid_is_ok = aid_canonize($aid);
  724.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, with "FF" prefix').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186F0');
  725.                         $ids[] = new OIDplusAltId('iana-pen', $pen, _L('IANA Private Enterprise Number (PEN)'));
  726.                 }
  727.  
  728.                 // (VTS F1) FreeOID to AID Mapping (PIX allowed)
  729.                 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')) {
  730.                         $number = $oid_parts[8];
  731.                         $aid = 'D276000186F1'.$number;
  732.                         if (strlen($aid)%2 == 1) $aid .= 'F';
  733.                         $aid_is_ok = aid_canonize($aid);
  734.                         if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, with "FF" prefix').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186F1');
  735.                 }
  736.  
  737.                 // (VTS F6) Mapping OID-to-AID if possible
  738.                 try {
  739.                         $test_der = \OidDerConverter::hexarrayToStr(\OidDerConverter::oidToDER($this->nodeId(false)));
  740.                 } catch (\Exception $e) {
  741.                         $test_der = '00'; // error, should not happen
  742.                 }
  743.                 if (substr($test_der,0,3) == '06 ') { // 06 = ASN.1 type of Absolute ID
  744.                         if (($oid_parts[0] == '2') && ($oid_parts[1] == '999')) {
  745.                                 // Note that "ViaThinkSoft E0" AID are not unique!
  746.                                 // OIDplus will use the relative DER of the 2.999.xx OID as PIX
  747.                                 $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"
  748.                                 $aid_is_ok = aid_canonize($aid_candidate);
  749.                                 if (!$aid_is_ok) {
  750.                                         // If DER encoding is not possible (too long), then we will use a 32 bit small hash.
  751.                                         $small_hash = str_pad(dechex(smallhash($this->nodeId(false))),8,'0',STR_PAD_LEFT);
  752.                                         $aid_candidate = 'D2 76 00 01 86 E0 ' . strtoupper(implode(' ',str_split($small_hash,2)));
  753.                                         $aid_is_ok = aid_canonize($aid_candidate);
  754.                                 }
  755.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'), '', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186E0');
  756.                         } 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')) {
  757.                                 // Illegal usage of E8 by German BSI, plus using E8+Len+OID instead of E8+OID like ISO does
  758.                                 // PIX probably not used
  759.                                 $aid_candidate = 'E8 '.substr($test_der, strlen('06 ')); // Remove ASN.1 06=Type
  760.                                 $aid_is_ok = aid_canonize($aid_candidate);
  761.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'));
  762.                         } else if (($oid_parts[0] == '1') && ($oid_parts[1] == '0')) {
  763.                                 // ISO Standard AID (OID 1.0.xx)
  764.                                 // Optional PIX allowed
  765.                                 $aid_candidate = 'E8 '.substr($test_der, strlen('06 xx ')); // Remove ASN.1 06=Type and xx=Length
  766.                                 $aid_is_ok = aid_canonize($aid_candidate);
  767.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')');
  768.                         } else {
  769.                                 // All other OIDs can be mapped using the "ViaThinkSoft F6" scheme, but only if the DER encoding is not too long
  770.                                 // No PIX allowed
  771.                                 $aid_candidate = 'D2 76 00 01 86 F6 '.substr($test_der, strlen('06 xx ')); // Remove ASN.1 06=Type and xx=Length
  772.                                 $aid_is_ok = aid_canonize($aid_candidate);
  773.                                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid_candidate, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')', 'https://oidplus.viathinksoft.com/oidplus/?goto=aid%3AD276000186F6');
  774.                         }
  775.                 }
  776.  
  777.                 return $ids;
  778.         }
  779.  
  780.         /**
  781.          * @return string
  782.          */
  783.         public function getDirectoryName(): string {
  784.                 if ($this->isRoot()) return $this->ns();
  785.                 $oid = $this->nodeId(false);
  786.                 return $this->ns().'_'.str_replace('.', '_', $oid);
  787.         }
  788.  
  789.         /**
  790.          * @param string $mode
  791.          * @return string
  792.          */
  793.         public static function treeIconFilename(string $mode): string {
  794.                 return 'img/'.$mode.'_icon16.png';
  795.         }
  796. }
  797.