Rev 771 | Rev 1116 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
635 | daniel-mar | 1 | <?php |
2 | |||
3 | /** |
||
4 | * WEID<=>OID Converter |
||
5 | * (c) Webfan.de, ViaThinkSoft |
||
771 | daniel-mar | 6 | * Revision 2022-03-06 |
635 | daniel-mar | 7 | **/ |
8 | |||
683 | daniel-mar | 9 | // What is a WEID? |
10 | // A WEID (WEhowski IDentifier) is an alternative representation of an |
||
11 | // OID (Object IDentifier) defined by Till Wehowski. |
||
12 | // In OIDs, arcs are in decimal base 10. In WEIDs, the arcs are in base 36. |
||
13 | // Also, each WEID has a check digit at the end (called WeLohn Check Digit). |
||
14 | // |
||
15 | // Changes in the December 2021 definition by Daniel Marschall: |
||
16 | // - There are several classes of WEIDs which have different OID bases: |
||
17 | // "Class C" WEID: weid:EXAMPLE-3 (base .1.3.6.1.4.1.37553.8.) |
||
18 | // oid:1.3.6.1.4.1.37553.8.32488192274 |
||
19 | // "Class B" WEID: weid:pen:SX0-7PR-6 (base .1.3.6.1.4.1.) |
||
20 | // oid:1.3.6.1.4.1.37476.9999 |
||
21 | // "Class A" WEID: weid:root:2-RR-2 (base .) |
||
22 | // oid:2.999 |
||
23 | // - The namespace (weid:, weid:pen:, weid:root:) is now case insensitive. |
||
24 | // - Padding with '0' characters is valid (e.g. weid:000EXAMPLE-3) |
||
25 | // The paddings do not count into the WeLuhn check-digit. |
||
26 | |||
1050 | daniel-mar | 27 | namespace Frdl\Weid; // TODO: Namespace mit Till abklären |
28 | |||
635 | daniel-mar | 29 | class WeidOidConverter { |
30 | |||
31 | protected static function weLuhnGetCheckDigit($str) { |
||
683 | daniel-mar | 32 | // Padding zeros don't count to the check digit (December 2021) |
33 | $ary = explode('-', $str); |
||
686 | daniel-mar | 34 | foreach ($ary as &$a) { |
35 | $a = ltrim($a, '0'); |
||
36 | if ($a === '') $a = '0'; |
||
37 | } |
||
683 | daniel-mar | 38 | $str = implode('-', $ary); |
39 | |||
686 | daniel-mar | 40 | // remove separators of the WEID string |
41 | $wrkstr = str_replace('-', '', $str); |
||
42 | |||
43 | // Replace 'a' with '10', 'b' with '11', etc. |
||
44 | for ($i=0; $i<26; $i++) { |
||
635 | daniel-mar | 45 | $wrkstr = str_ireplace(chr(ord('a')+$i), (string)($i+10), $wrkstr); |
46 | } |
||
686 | daniel-mar | 47 | |
48 | // At the end, $wrkstr should only contain digits! Verify it! |
||
49 | for ($i=0; $i<strlen($wrkstr); $i++) { |
||
50 | if (($wrkstr[$i]<'0') || ($wrkstr[$i]>'9')) return false; |
||
51 | } |
||
52 | |||
53 | // Now do the standard Luhn algorithm |
||
635 | daniel-mar | 54 | $nbdigits = strlen($wrkstr); |
686 | daniel-mar | 55 | $parity = $nbdigits & 1; // mod 2 |
635 | daniel-mar | 56 | $sum = 0; |
57 | for ($n=$nbdigits-1; $n>=0; $n--) { |
||
686 | daniel-mar | 58 | $digit = (int)$wrkstr[$n]; |
635 | daniel-mar | 59 | if (($n & 1) != $parity) $digit *= 2; |
60 | if ($digit > 9) $digit -= 9; |
||
61 | $sum += $digit; |
||
62 | } |
||
63 | return ($sum%10) == 0 ? 0 : 10-($sum%10); |
||
64 | } |
||
65 | |||
771 | daniel-mar | 66 | private static function oidSanitize($oid) { |
67 | $oid = trim($oid); |
||
68 | |||
69 | if (substr($oid,0,1) == '.') $oid = substr($oid,1); // remove leading dot |
||
70 | |||
71 | if ($oid != '') { |
||
72 | $elements = explode('.', $oid); |
||
73 | foreach ($elements as &$elem) { |
||
74 | if (trim($elem) == '') return false; |
||
75 | |||
76 | if (!preg_match('/^\d+$/', $elem, $m)) return false; |
||
77 | |||
78 | if (preg_match('/^0+$/', $elem, $m)) { |
||
79 | $elem = '0'; |
||
80 | } else { |
||
81 | $elem = preg_replace('/^0+/', '', $elem); |
||
82 | } |
||
83 | }; |
||
84 | $oid = implode('.', $elements); |
||
85 | |||
86 | if ((count($elements) > 0) && ($elements[0] != '0') && ($elements[0] != '1') && ($elements[0] != '2')) return false; |
||
87 | if ((count($elements) > 1) && (($elements[0] == '0') || ($elements[0] == '1')) && ((strlen($elements[1]) > 2) || ($elements[1] > 39))) return false; |
||
88 | } |
||
89 | |||
90 | return $oid; |
||
91 | } |
||
92 | |||
683 | daniel-mar | 93 | // Translates a weid to an oid |
94 | // "weid:EXAMPLE-3" becomes "1.3.6.1.4.1.37553.8.32488192274" |
||
95 | // If it failed (e.g. wrong namespace, wrong checksum, etc.) then false is returned. |
||
96 | // If the weid ends with '?', then it will be replaced with the checksum, |
||
97 | // e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3 |
||
98 | public static function weid2oid(&$weid) { |
||
771 | daniel-mar | 99 | $weid = trim($weid); |
635 | daniel-mar | 100 | |
683 | daniel-mar | 101 | $p = strrpos($weid,':'); |
102 | $namespace = substr($weid, 0, $p+1); |
||
103 | $rest = substr($weid, $p+1); |
||
104 | |||
105 | $namespace = strtolower($namespace); // namespace is case insensitive |
||
106 | if ($namespace == 'weid:') { |
||
107 | // Class C |
||
108 | $base = '1-3-6-1-4-1-SZ5-8'; |
||
109 | } else if ($namespace == 'weid:pen:') { |
||
110 | // Class B |
||
111 | $base = '1-3-6-1-4-1'; |
||
112 | } else if ($namespace == 'weid:root:') { |
||
113 | // Class A |
||
114 | $base = ''; |
||
115 | } else { |
||
116 | // Wrong namespace |
||
117 | return false; |
||
118 | } |
||
119 | |||
120 | $weid = $rest; |
||
121 | |||
122 | $elements = array_merge(($base != '') ? explode('-', $base) : array(), explode('-', $weid)); |
||
771 | daniel-mar | 123 | |
124 | foreach ($elements as $elem) { |
||
125 | if ($elem == '') return false; |
||
126 | } |
||
127 | |||
635 | daniel-mar | 128 | $actual_checksum = array_pop($elements); |
129 | $expected_checksum = self::weLuhnGetCheckDigit(implode('-',$elements)); |
||
130 | if ($actual_checksum != '?') { |
||
771 | daniel-mar | 131 | if ($actual_checksum != $expected_checksum) { |
132 | return false; // wrong checksum |
||
133 | } |
||
635 | daniel-mar | 134 | } else { |
135 | // If checksum is '?', it will be replaced by the actual checksum, |
||
136 | // e.g. weid:EXAMPLE-? becomes weid:EXAMPLE-3 |
||
137 | $weid = str_replace('?', $expected_checksum, $weid); |
||
138 | } |
||
139 | foreach ($elements as &$arc) { |
||
140 | //$arc = strtoupper(base_convert($arc, 36, 10)); |
||
141 | $arc = strtoupper(self::base_convert_bigint($arc, 36, 10)); |
||
142 | } |
||
771 | daniel-mar | 143 | $oid = implode('.', $elements); |
635 | daniel-mar | 144 | |
750 | daniel-mar | 145 | $weid = strtolower($namespace) . strtoupper($weid); // add namespace again |
683 | daniel-mar | 146 | |
771 | daniel-mar | 147 | $oid = self::oidSanitize($oid); |
148 | if ($oid === false) return false; |
||
149 | |||
150 | return $oid; |
||
635 | daniel-mar | 151 | } |
152 | |||
683 | daniel-mar | 153 | // Converts an OID to WEID |
154 | // "1.3.6.1.4.1.37553.8.32488192274" becomes "weid:EXAMPLE-3" |
||
155 | public static function oid2weid($oid) { |
||
771 | daniel-mar | 156 | $oid = self::oidSanitize($oid); |
157 | if ($oid === false) return false; |
||
683 | daniel-mar | 158 | |
159 | if ($oid !== '') { |
||
160 | $elements = explode('.', $oid); |
||
161 | foreach ($elements as &$arc) { |
||
162 | //$arc = strtoupper(base_convert($arc, 10, 36)); |
||
163 | $arc = strtoupper(self::base_convert_bigint($arc, 10, 36)); |
||
164 | } |
||
165 | $weidstr = implode('-', $elements); |
||
166 | } else { |
||
167 | $weidstr = ''; |
||
635 | daniel-mar | 168 | } |
169 | |||
683 | daniel-mar | 170 | $is_class_c = (strpos($weidstr, '1-3-6-1-4-1-SZ5-8-') === 0) || |
171 | ($weidstr === '1-3-6-1-4-1-SZ5-8'); |
||
172 | $is_class_b = ((strpos($weidstr, '1-3-6-1-4-1-') === 0) || |
||
173 | ($weidstr === '1-3-6-1-4-1')) |
||
174 | && !$is_class_c; |
||
175 | $is_class_a = !$is_class_b && !$is_class_c; |
||
635 | daniel-mar | 176 | |
683 | daniel-mar | 177 | $checksum = self::weLuhnGetCheckDigit($weidstr); |
178 | |||
179 | if ($is_class_c) { |
||
180 | $weidstr = substr($weidstr, strlen('1-3-6-1-4-1-SZ5-8-')); |
||
181 | $namespace = 'weid:'; |
||
182 | } else if ($is_class_b) { |
||
183 | $weidstr = substr($weidstr, strlen('1-3-6-1-4-1-')); |
||
184 | $namespace = 'weid:pen:'; |
||
185 | } else if ($is_class_a) { |
||
186 | // $weidstr stays |
||
187 | $namespace = 'weid:root:'; |
||
686 | daniel-mar | 188 | } else { |
189 | // should not happen |
||
190 | return false; |
||
683 | daniel-mar | 191 | } |
192 | |||
771 | daniel-mar | 193 | $weid = $namespace . ($weidstr == '' ? $checksum : $weidstr . '-' . $checksum); |
194 | |||
195 | return $weid; |
||
635 | daniel-mar | 196 | } |
197 | |||
198 | protected static function base_convert_bigint($numstring, $frombase, $tobase) { |
||
199 | $frombase_str = ''; |
||
200 | for ($i=0; $i<$frombase; $i++) { |
||
201 | $frombase_str .= strtoupper(base_convert((string)$i, 10, 36)); |
||
202 | } |
||
203 | |||
204 | $tobase_str = ''; |
||
205 | for ($i=0; $i<$tobase; $i++) { |
||
206 | $tobase_str .= strtoupper(base_convert((string)$i, 10, 36)); |
||
207 | } |
||
208 | |||
209 | $length = strlen($numstring); |
||
210 | $result = ''; |
||
211 | $number = array(); |
||
212 | for ($i = 0; $i < $length; $i++) { |
||
213 | $number[$i] = stripos($frombase_str, $numstring[$i]); |
||
214 | } |
||
215 | do { // Loop until whole number is converted |
||
216 | $divide = 0; |
||
217 | $newlen = 0; |
||
218 | for ($i = 0; $i < $length; $i++) { // Perform division manually (which is why this works with big numbers) |
||
219 | $divide = $divide * $frombase + $number[$i]; |
||
220 | if ($divide >= $tobase) { |
||
221 | $number[$newlen++] = (int)($divide / $tobase); |
||
222 | $divide = $divide % $tobase; |
||
223 | } else if ($newlen > 0) { |
||
224 | $number[$newlen++] = 0; |
||
225 | } |
||
226 | } |
||
227 | $length = $newlen; |
||
228 | $result = $tobase_str[$divide] . $result; // Divide is basically $numstring % $tobase (i.e. the new character) |
||
229 | } |
||
230 | while ($newlen != 0); |
||
231 | |||
232 | return $result; |
||
233 | } |
||
234 | } |
||
235 | |||
236 | |||
683 | daniel-mar | 237 | # --- Usage Example --- |
238 | |||
635 | daniel-mar | 239 | /* |
683 | daniel-mar | 240 | echo "Class C tests:\n\n"; |
635 | daniel-mar | 241 | |
683 | daniel-mar | 242 | var_dump($oid = '1.3.6.1.4.1.37553.8')."\n"; |
1050 | daniel-mar | 243 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 244 | $weid = 'weid:?'; |
1050 | daniel-mar | 245 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 246 | var_dump($weid)."\n"; |
247 | echo "\n"; |
||
635 | daniel-mar | 248 | |
683 | daniel-mar | 249 | var_dump($oid = '1.3.6.1.4.1.37553.8.32488192274')."\n"; |
1050 | daniel-mar | 250 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 251 | $weid = 'weid:EXAMPLE-?'; |
1050 | daniel-mar | 252 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 253 | var_dump($weid)."\n"; |
254 | $weid = 'weid:00000example-?'; |
||
1050 | daniel-mar | 255 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 256 | var_dump($weid)."\n"; |
257 | echo "\n"; |
||
635 | daniel-mar | 258 | |
683 | daniel-mar | 259 | echo "Class B tests:\n\n"; |
635 | daniel-mar | 260 | |
683 | daniel-mar | 261 | var_dump($oid = '1.3.6.1.4.1')."\n"; |
1050 | daniel-mar | 262 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 263 | $weid = 'weid:pen:?'; |
1050 | daniel-mar | 264 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 265 | var_dump($weid)."\n"; |
266 | echo "\n"; |
||
635 | daniel-mar | 267 | |
683 | daniel-mar | 268 | var_dump($oid = '1.3.6.1.4.1.37553.7.99.99.99')."\n"; |
1050 | daniel-mar | 269 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 270 | $weid = 'weid:pen:SZ5-7-2R-2R-2R-?'; |
1050 | daniel-mar | 271 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 272 | var_dump($weid)."\n"; |
273 | $weid = 'weid:pen:000SZ5-7-02R-00002R-002r-?'; |
||
1050 | daniel-mar | 274 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 275 | var_dump($weid)."\n"; |
276 | echo "\n"; |
||
277 | |||
278 | var_dump($oid = '1.3.6.1.4.1.37476.9999')."\n"; |
||
1050 | daniel-mar | 279 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 280 | $weid = 'weid:pen:SX0-7PR-?'; |
1050 | daniel-mar | 281 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 282 | var_dump($weid)."\n"; |
283 | echo "\n"; |
||
284 | |||
285 | echo "Class A tests:\n\n"; |
||
286 | |||
287 | var_dump($oid = '')."\n"; |
||
1050 | daniel-mar | 288 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 289 | $weid = 'weid:root:?'; |
1050 | daniel-mar | 290 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 291 | var_dump($weid)."\n"; |
292 | echo "\n"; |
||
293 | |||
294 | var_dump($oid = '.2.999')."\n"; |
||
1050 | daniel-mar | 295 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 296 | $weid = 'weid:root:2-RR-?'; |
1050 | daniel-mar | 297 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 298 | var_dump($weid)."\n"; |
299 | echo "\n"; |
||
300 | |||
301 | var_dump($oid = '2.999')."\n"; |
||
1050 | daniel-mar | 302 | var_dump(\Frdl\Weid\WeidOidConverter::oid2weid($oid))."\n"; |
683 | daniel-mar | 303 | $weid = 'weid:root:2-RR-?'; |
1050 | daniel-mar | 304 | var_dump(\Frdl\Weid\WeidOidConverter::weid2oid($weid))."\n"; |
683 | daniel-mar | 305 | var_dump($weid)."\n"; |
306 | echo "\n"; |
||
635 | daniel-mar | 307 | */ |