Rev 1387 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1248 | daniel-mar | 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 OIDplusMac extends OIDplusObject { |
||
27 | /** |
||
28 | * @var int|string |
||
29 | */ |
||
30 | private $number; |
||
31 | |||
32 | /** |
||
33 | * @param string|int $number |
||
34 | */ |
||
35 | public function __construct($number) { |
||
36 | // TODO: syntax checks |
||
37 | $this->number = $number; |
||
38 | } |
||
39 | |||
40 | /** |
||
41 | * @param string $node_id |
||
42 | * @return OIDplusMac|null |
||
43 | */ |
||
44 | public static function parse(string $node_id)/*: ?OIDplusMac*/ { |
||
45 | @list($namespace, $number) = explode(':', $node_id, 2); |
||
46 | if ($namespace !== self::ns()) return null; |
||
47 | return new self($number); |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * @return string |
||
52 | */ |
||
53 | public static function objectTypeTitle(): string { |
||
1255 | daniel-mar | 54 | return _L('MAC adresses (EUI/ELI/AAI/SAI)'); |
1248 | daniel-mar | 55 | } |
56 | |||
57 | /** |
||
58 | * @return string |
||
59 | */ |
||
60 | public static function objectTypeTitleShort(): string { |
||
1255 | daniel-mar | 61 | return _L('MAC'); |
1248 | daniel-mar | 62 | } |
63 | |||
64 | /** |
||
65 | * @return string |
||
66 | */ |
||
67 | public static function ns(): string { |
||
68 | return 'mac'; |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * @return string |
||
73 | */ |
||
74 | public static function root(): string { |
||
75 | return self::ns().':'; |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * @return bool |
||
80 | */ |
||
81 | public function isRoot(): bool { |
||
82 | return $this->number == ''; |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * @param bool $with_ns |
||
87 | * @return string |
||
88 | */ |
||
89 | public function nodeId(bool $with_ns=true): string { |
||
90 | return $with_ns ? self::root().$this->number : $this->number; |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * @param string $str |
||
95 | * @return string |
||
96 | * @throws OIDplusException |
||
97 | */ |
||
98 | public function addString(string $str): string { |
||
1261 | daniel-mar | 99 | $str = str_replace(array('-', ':', ' '), '', $str); |
1248 | daniel-mar | 100 | $str = strtoupper($str); |
101 | |||
102 | $test = preg_replace('@[0-9A-F]@', '', $str); |
||
1254 | daniel-mar | 103 | if ($test != '') throw new OIDplusException(_L("Invalid characters entered")); |
1248 | daniel-mar | 104 | |
1255 | daniel-mar | 105 | $new_mac = $this->nodeId(false) . $str; |
1254 | daniel-mar | 106 | |
1255 | daniel-mar | 107 | $type = mac_type(str_pad($new_mac, 12, '0', STR_PAD_RIGHT)); |
108 | $type = substr($type, 0, 3); |
||
1248 | daniel-mar | 109 | |
1255 | daniel-mar | 110 | if (($type == 'ELI') || ($type == 'EUI')) { |
111 | if ($this->isRoot() && (strlen($str) < 6)) { |
||
112 | throw new OIDplusException(_L("The first node must be at least 24 bits long, since this is the smallest assignment for OUI/CID from IEEE.")); |
||
113 | } |
||
1248 | daniel-mar | 114 | } |
115 | |||
1256 | daniel-mar | 116 | if (($type == 'ELI') || ($type == 'EUI') || ($type == 'AAI') || ($type == 'SAI')) { |
117 | // Note: AAI-48, AAI-64, SAI-48, and SAI-64 are defined in IEEE 802c-2017 |
||
1255 | daniel-mar | 118 | if (strlen($new_mac) > 16) { |
119 | throw new OIDplusException(_L("The max length of an EUI-64 or ELI-64 is 64 bit")); |
||
120 | } |
||
121 | } |
||
122 | |||
123 | return $this->root().$new_mac; |
||
1248 | daniel-mar | 124 | } |
125 | |||
126 | /** |
||
127 | * @param OIDplusObject $parent |
||
128 | * @return string |
||
129 | * @throws OIDplusException |
||
130 | */ |
||
131 | public function crudShowId(OIDplusObject $parent): string { |
||
1260 | daniel-mar | 132 | //return $this->chunkedNotation(false); |
133 | return rtrim(chunk_split($this->number, 2, '-'), '-'); |
||
1248 | daniel-mar | 134 | } |
135 | |||
136 | /** |
||
137 | * @return string |
||
138 | * @throws OIDplusException |
||
139 | */ |
||
140 | public function crudInsertPrefix(): string { |
||
141 | return $this->isRoot() ? '' : $this->chunkedNotation(false); |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * @param OIDplusObject|null $parent |
||
146 | * @return string |
||
147 | */ |
||
148 | public function jsTreeNodeName(OIDplusObject $parent = null): string { |
||
149 | if ($parent == null) return $this->objectTypeTitle(); |
||
150 | return substr($this->nodeId(), strlen($parent->nodeId())); |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * @return string |
||
155 | */ |
||
156 | public function defaultTitle(): string { |
||
1261 | daniel-mar | 157 | //return $this->number; |
158 | return rtrim(chunk_split($this->number, 2, '-'), '-'); |
||
1248 | daniel-mar | 159 | } |
160 | |||
161 | /** |
||
162 | * @return bool |
||
163 | */ |
||
164 | public function isLeafNode(): bool { |
||
165 | // Problem with this approach: If we are EUI-48 and want to add more (to get EUI-64), we couldn't. |
||
166 | /* |
||
167 | return mac_valid($this->nodeId(false)); |
||
168 | */ |
||
169 | return eui_bits($this->nodeId(false)) == 64; |
||
170 | } |
||
171 | |||
172 | /** |
||
1324 | daniel-mar | 173 | * @return array |
174 | */ |
||
175 | private function getTechInfo(): array { |
||
176 | $tech_info = array(); |
||
177 | |||
178 | ob_start(); |
||
179 | try { |
||
180 | // TODO: OIDplus should download the *.txt files at "web-data" |
||
181 | decode_mac(mac_canonize($this->nodeId(false))); |
||
182 | $tech_info = ob_get_contents(); |
||
183 | |||
184 | |||
185 | $lines = explode("\n", $tech_info); |
||
186 | $tech_info = []; |
||
187 | $key = ''; |
||
188 | foreach ($lines as $line) { |
||
189 | $m1 = explode(':', $line); |
||
190 | if (!isset($m1[1])) $m1 = array($key, $m1[0]); |
||
191 | $key = $m1[0]; |
||
192 | if (isset($tech_info[$key])) { |
||
193 | $value = $tech_info[$key].'<br>'.$m1[1]; |
||
194 | } else { |
||
195 | $value = $m1[1]; |
||
196 | } |
||
197 | $tech_info[$key] = $value; |
||
198 | } |
||
199 | |||
200 | } catch (\Exception $e) { |
||
201 | $tech_info = []; |
||
202 | } |
||
203 | ob_end_clean(); |
||
204 | |||
205 | return $tech_info; |
||
206 | } |
||
207 | |||
208 | /** |
||
1248 | daniel-mar | 209 | * @param string $title |
210 | * @param string $content |
||
211 | * @param string $icon |
||
212 | * @return void |
||
213 | * @throws OIDplusException |
||
214 | */ |
||
215 | public function getContentPage(string &$title, string &$content, string &$icon) { |
||
216 | $icon = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : ''; |
||
217 | |||
218 | if ($this->isRoot()) { |
||
219 | $title = OIDplusMac::objectTypeTitle(); |
||
220 | |||
221 | $res = OIDplus::db()->query("select * from ###objects where parent = ?", array(self::root())); |
||
222 | if ($res->any()) { |
||
223 | $content = '<p>'._L('Please select an item in the tree view at the left to show its contents.').'</p>'; |
||
224 | } else { |
||
1258 | daniel-mar | 225 | $content = '<p>'._L('Currently, no MAC addresses are registered in the system.').'</p>'; |
1248 | daniel-mar | 226 | } |
227 | |||
228 | if (!$this->isLeafNode()) { |
||
229 | if (OIDplus::authUtils()->isAdminLoggedIn()) { |
||
230 | $content .= '<h2>'._L('Manage root objects').'</h2>'; |
||
231 | } else { |
||
232 | $content .= '<h2>'._L('Available objects').'</h2>'; |
||
233 | } |
||
234 | $content .= '%%CRUD%%'; |
||
235 | } |
||
236 | } else { |
||
237 | $title = $this->getTitle(); |
||
238 | |||
1324 | daniel-mar | 239 | $tech_info = $this->getTechInfo(); |
240 | $tech_info_html = ''; |
||
241 | if (count($tech_info) > 0) { |
||
242 | $tech_info_html .= '<h2>'._L('Technical information').'</h2>'; |
||
1386 | daniel-mar | 243 | $tech_info_html .= '<div style="overflow:auto"><table border="0">'; |
1324 | daniel-mar | 244 | foreach ($tech_info as $key => $value) { |
1386 | daniel-mar | 245 | $tech_info_html .= '<tr><td valign="top" style="white-space: nowrap;">'.$key.': </td><td><code>'.$value.'</code></td></tr>'; |
1324 | daniel-mar | 246 | } |
1387 | daniel-mar | 247 | $tech_info_html .= '</table></div>'; |
1248 | daniel-mar | 248 | } |
1324 | daniel-mar | 249 | $content = $tech_info_html; |
1248 | daniel-mar | 250 | |
1250 | daniel-mar | 251 | $chunked = $this->chunkedNotation(true); |
252 | if (!mac_valid($this->number)) { |
||
253 | $chunked .= ' ...'; |
||
254 | } |
||
255 | |||
1255 | daniel-mar | 256 | $type = ''; |
257 | try { |
||
258 | $type_raw = mac_type(str_pad($this->number, 12, '0', STR_PAD_RIGHT)); |
||
259 | if (preg_match('@(.+) \\((.+)\\)@ismU', $type_raw, $m)) { |
||
260 | $type_short = $m[1]; |
||
261 | $type_long = $m[2]; |
||
262 | $type = '<abbr title="'.htmlentities($type_long).'">'.htmlentities($type_short).'</abbr>'; |
||
263 | } else { |
||
264 | $type = htmlentities($type_raw); |
||
265 | } |
||
266 | } catch (\Exception $e) {}; |
||
1253 | daniel-mar | 267 | |
1255 | daniel-mar | 268 | $content = '<h2>'.$type.' <strong>'.$chunked.'</strong></h2>'; |
1250 | daniel-mar | 269 | $content .= $tech_info_html; |
270 | |||
1248 | daniel-mar | 271 | if ($this->isLeafNode()) { |
272 | $content .= '<h2>'._L('Description').'</h2>%%DESC%%'; |
||
273 | } else { |
||
274 | $content .= '<h2>'._L('Description').'</h2>%%DESC%%'; |
||
275 | if ($this->userHasWriteRights()) { |
||
276 | $content .= '<h2>'._L('Create or change subordinate objects').'</h2>'; |
||
277 | } else { |
||
278 | $content .= '<h2>'._L('Subordinate objects').'</h2>'; |
||
279 | } |
||
280 | $content .= '%%CRUD%%'; |
||
281 | } |
||
282 | } |
||
283 | } |
||
284 | |||
285 | # --- |
||
286 | |||
287 | /** |
||
288 | * @param bool $withAbbr |
||
289 | * @return string |
||
290 | * @throws OIDplusException |
||
291 | */ |
||
292 | public function chunkedNotation(bool $withAbbr=true): string { |
||
293 | $curid = self::root().$this->number; |
||
294 | |||
295 | $obj = OIDplusObject::findFitting($curid); |
||
296 | if (!$obj) return $this->number; |
||
297 | |||
298 | $hints = array(); |
||
299 | $lengths = array(strlen($curid)); |
||
300 | while ($obj = OIDplusObject::findFitting($curid)) { |
||
301 | $objParent = $obj->getParent(); |
||
302 | if (!$objParent) break; |
||
1255 | daniel-mar | 303 | $curid = $objParent->nodeId(true); |
1248 | daniel-mar | 304 | $hints[] = $obj->getTitle(); |
305 | $lengths[] = strlen($curid); |
||
306 | } |
||
307 | |||
308 | array_shift($lengths); |
||
309 | $chunks = array(); |
||
310 | |||
311 | $full = self::root().$this->number; |
||
312 | foreach ($lengths as $len) { |
||
313 | $chunks[] = substr($full, $len); |
||
314 | $full = substr($full, 0, $len); |
||
315 | } |
||
316 | |||
317 | $hints = array_reverse($hints); |
||
318 | $chunks = array_reverse($chunks); |
||
319 | |||
320 | $full = array(); |
||
321 | foreach ($chunks as $c) { |
||
322 | $hint = array_shift($hints); |
||
323 | $full[] = $withAbbr && ($hint !== '') ? '<abbr title="'.htmlentities($hint).'">'.$c.'</abbr>' : $c; |
||
324 | } |
||
1250 | daniel-mar | 325 | |
1248 | daniel-mar | 326 | return implode(' ', $full); |
327 | } |
||
328 | |||
329 | /** |
||
330 | * @return OIDplusMac|null |
||
331 | */ |
||
332 | public function one_up()/*: ?OIDplusMac*/ { |
||
333 | return self::parse($this->ns().':'.substr($this->number,0,strlen($this->number)-1)); |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * @param string $a |
||
338 | * @param string $b |
||
339 | * @return false|int |
||
340 | */ |
||
341 | private static function distance_(string $a, string $b) { |
||
342 | $min_len = min(strlen($a), strlen($b)); |
||
343 | |||
344 | for ($i=0; $i<$min_len; $i++) { |
||
345 | if ($a[$i] != $b[$i]) return false; |
||
346 | } |
||
347 | |||
348 | return strlen($a) - strlen($b); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * @param OIDplusObject|string $to |
||
353 | * @return int|null |
||
354 | */ |
||
355 | public function distance($to) { |
||
356 | if (!is_object($to)) $to = OIDplusObject::parse($to); |
||
357 | if (!$to) return null; |
||
358 | if (!($to instanceof $this)) return null; |
||
359 | |||
360 | if ($this->number == $to->number) return 0; |
||
361 | |||
362 | $b = $this->number; |
||
363 | $a = $to->number; |
||
364 | $tmp = self::distance_($a, $b); |
||
365 | if ($tmp !== false) return $tmp; |
||
366 | |||
367 | return null; |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * @return array|OIDplusAltId[] |
||
372 | * @throws OIDplusException |
||
373 | */ |
||
374 | public function getAltIds(): array { |
||
375 | if ($this->isRoot()) return array(); |
||
376 | $ids = parent::getAltIds(); |
||
377 | |||
1256 | daniel-mar | 378 | // (VTS F2) MAC address (EUI/ELI/...) to AID (PIX allowed) |
379 | $size_nibble = strlen($this->number)-1; |
||
1261 | daniel-mar | 380 | if (($size_nibble >= 0) && ($size_nibble <= 0xF)) { |
1256 | daniel-mar | 381 | $aid = 'D276000186F2'.dechex($size_nibble).$this->number; |
382 | $aid_is_ok = aid_canonize($aid); |
||
1439 | daniel-mar | 383 | if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, without prefix').')', 'https://hosted.oidplus.com/viathinksoft/?goto=aid%3AD276000186F2'); |
1256 | daniel-mar | 384 | } |
1248 | daniel-mar | 385 | |
386 | return $ids; |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * @return string |
||
391 | */ |
||
392 | public function getDirectoryName(): string { |
||
393 | if ($this->isRoot()) return $this->ns(); |
||
394 | return $this->ns().'_'.$this->nodeId(false); // safe, because there are only hexadecimal numbers |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * @param string $mode |
||
399 | * @return string |
||
400 | */ |
||
401 | public static function treeIconFilename(string $mode): string { |
||
402 | return 'img/'.$mode.'_icon16.png'; |
||
403 | } |
||
404 | } |