Rev 16 | Rev 18 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 16 | Rev 17 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * MAC (EUI-48 and EUI-64) utils for PHP |
4 | * MAC (EUI-48 and EUI-64) utils for PHP |
5 | * Copyright 2017 - 2023 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2017 - 2023 Daniel Marschall, ViaThinkSoft |
6 | * Version 2023-04-29 |
6 | * Version 2023-05-01 |
7 | * |
7 | * |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
9 | * you may not use this file except in compliance with the License. |
9 | * you may not use this file except in compliance with the License. |
10 | * You may obtain a copy of the License at |
10 | * You may obtain a copy of the License at |
11 | * |
11 | * |
Line 19... | Line 19... | ||
19 | */ |
19 | */ |
20 | 20 | ||
21 | const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data'; |
21 | const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data'; |
22 | 22 | ||
23 | /** |
23 | /** |
- | 24 | * Checks if a MAC, EUI, or IPv6-LinkLocal address is valid |
|
24 | * @param string $mac |
25 | * @param string $mac MAC, EUI, or IPv6-LinkLocal Address |
25 | * @return bool |
26 | * @return bool True if it is valid |
26 | */ |
27 | */ |
27 | function mac_valid(string $mac): bool { |
28 | function mac_valid(string $mac): bool { |
28 | $tmp = ipv6linklocal_to_eui64($mac); |
29 | $tmp = ipv6linklocal_to_mac48($mac); |
29 | if ($tmp !== false) $mac = $tmp; |
30 | if ($tmp !== false) $mac = $tmp; |
30 | 31 | ||
31 | $mac = str_replace(array('-', ':'), '', $mac); |
32 | $mac = str_replace(array('-', ':'), '', $mac); |
32 | $mac = strtoupper($mac); |
33 | $mac = strtoupper($mac); |
33 | 34 | ||
Line 37... | Line 38... | ||
37 | 38 | ||
38 | return ($mac === ''); |
39 | return ($mac === ''); |
39 | } |
40 | } |
40 | 41 | ||
41 | /** |
42 | /** |
- | 43 | * Returns the amount of bits of a MAC or EUI |
|
42 | * @param string $mac |
44 | * @param string $mac |
43 | * @param string $delimiter |
45 | * @return false|int |
- | 46 | */ |
|
- | 47 | function eui_bits(string $mac) { |
|
- | 48 | if (!mac_valid($mac)) return false; |
|
- | 49 | $mac = mac_canonize($mac, ''); |
|
44 | * @return string|false |
50 | return (int)(strlen($mac)*4); |
- | 51 | } |
|
- | 52 | ||
- | 53 | /** |
|
- | 54 | * Canonizes a MAC, EUI, or IPv6-LinkLocal address |
|
- | 55 | * @param string $mac MAC, EUI, or IPv6-LinkLocal Address |
|
- | 56 | * @param string $delimiter Desired delimiter for inserting between each octet |
|
- | 57 | * @return string|false The canonized address (Note: IPv6-Linklocal becomes EUI-64) |
|
45 | */ |
58 | */ |
46 | function mac_canonize(string $mac, string $delimiter="-") { |
59 | function mac_canonize(string $mac, string $delimiter="-") { |
47 | if (!mac_valid($mac)) return false; |
60 | if (!mac_valid($mac)) return false; |
48 | 61 | ||
49 | $tmp = ipv6linklocal_to_eui64($mac); |
62 | $tmp = ipv6linklocal_to_mac48($mac); |
50 | if ($tmp !== false) $mac = $tmp; |
63 | if ($tmp !== false) $mac = $tmp; |
51 | 64 | ||
52 | $mac = strtoupper($mac); |
65 | $mac = strtoupper($mac); |
53 | $mac = preg_replace('@[^0-9A-F]@', '', $mac); |
66 | $mac = preg_replace('@[^0-9A-F]@', '', $mac); |
54 | if ((strlen($mac) != 12) && (strlen($mac) != 16)) return false; |
67 | if ((strlen($mac) != 12) && (strlen($mac) != 16)) return false; |
Line 110... | Line 123... | ||
110 | 123 | ||
111 | return false; |
124 | return false; |
112 | } |
125 | } |
113 | 126 | ||
114 | /** |
127 | /** |
- | 128 | * Try to Decapsulate EUI-64 into MAC-48 or EUI-48 |
|
115 | * @param string $eui64 |
129 | * @param string $eui64 |
116 | * @return false|string If EUI-64 can be converted into EUI-48 (if it has FFFE in the middle), returns EUI-48, otherwise returns EUI-64. On invalid input, return false. |
130 | * @return false|string If EUI-64 can be converted into EUI-48, returns EUI-48, otherwise returns EUI-64. On invalid input, return false. |
117 | */ |
131 | */ |
118 | function eui64_to_eui48(string $eui64) { |
132 | function eui64_to_eui48(string $eui64) { |
119 | if (!mac_valid($eui64)) return false; |
133 | if (!mac_valid($eui64)) return false; |
120 | $eui64 = mac_canonize($eui64, ''); |
134 | $eui64 = mac_canonize($eui64, ''); |
121 | if (eui_bits($eui64) == 48) return mac_canonize($eui64); |
135 | if (eui_bits($eui64) == 48) return mac_canonize($eui64); |
122 | 136 | ||
123 | if (substr($eui64, 6, 4) == 'FFFE') { |
137 | if (substr($eui64, 6, 4) == 'FFFF') { |
- | 138 | // EUI-64 to MAC-48 |
|
- | 139 | return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6)); |
|
- | 140 | } else if (substr($eui64, 6, 4) == 'FFFE') { |
|
- | 141 | if ((hexdec($eui64[1])&2) == 2) { |
|
- | 142 | // Modified EUI-64 to MAC/EUI-48 |
|
- | 143 | $eui64[1] = dechex(hexdec($eui64[1])&253); // remove bit |
|
124 | return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6)); |
144 | return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6)); |
125 | } else { |
145 | } else { |
- | 146 | // EUI-64 to EUI-48 |
|
- | 147 | return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6)); |
|
- | 148 | } |
|
- | 149 | } else { |
|
126 | return mac_canonize($eui64); |
150 | return mac_canonize($eui64); |
127 | } |
151 | } |
128 | } |
152 | } |
129 | 153 | ||
130 | /** |
154 | /** |
- | 155 | * MAC-48 to EUI-64 Encapsulation |
|
- | 156 | * @param string $mac48 MAC-48 address |
|
- | 157 | * @return false|string EUI-64 address |
|
- | 158 | */ |
|
- | 159 | function mac48_to_eui64(string $mac48) { |
|
- | 160 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
|
- | 161 | // MAC48-to-EUI64 Encapsulation uses 0xFFFF middle part |
|
- | 162 | if (!mac_valid($mac48)) return false; |
|
- | 163 | $mac48 = mac_canonize($mac48, ''); |
|
- | 164 | if (eui_bits($mac48) == 64) return mac_canonize($mac48); |
|
- | 165 | ||
- | 166 | $eui64 = substr($mac48, 0, 6).'FFFF'.substr($mac48, 6, 6); |
|
- | 167 | return mac_canonize($eui64); |
|
- | 168 | } |
|
- | 169 | ||
- | 170 | /** |
|
- | 171 | * EUI-48 to EUI-64 Encapsulation |
|
131 | * @param string $eui48 |
172 | * @param string $eui48 EUI-48 address |
132 | * @return false|string |
173 | * @return false|string EUI-64 address |
133 | */ |
174 | */ |
134 | function eui48_to_eui64(string $eui48) { |
175 | function eui48_to_eui64(string $eui48) { |
- | 176 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
|
- | 177 | // EUI48-to-EUI64 Encapsulation uses 0xFFFF middle part |
|
135 | if (!mac_valid($eui48)) return false; |
178 | if (!mac_valid($eui48)) return false; |
136 | $eui48 = mac_canonize($eui48, ''); |
179 | $eui48 = mac_canonize($eui48, ''); |
137 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
180 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
138 | 181 | ||
139 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
182 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
140 | return mac_canonize($eui64); |
183 | return mac_canonize($eui64); |
141 | } |
184 | } |
142 | 185 | ||
143 | /** |
186 | /** |
- | 187 | * MAC/EUI-48 to Modified EUI-64 Encapsulation |
|
144 | * @param string $ipv6 |
188 | * @param string $eui48 MAC-48 or EUI-48 address |
145 | * @return false|string |
189 | * @return false|string Modified EUI-64 address |
- | 190 | */ |
|
- | 191 | function maceui48_to_modeui64(string $eui48) { |
|
- | 192 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
|
- | 193 | // EUI48-to-ModifiedEUI64 Encapsulation uses 0xFFFE middle part (SIC! This was a mistake by IETF, since it should actually be 0xFFFF!) |
|
- | 194 | if (!mac_valid($eui48)) return false; |
|
- | 195 | $eui48 = mac_canonize($eui48, ''); |
|
- | 196 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
|
- | 197 | ||
- | 198 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
|
- | 199 | ||
- | 200 | $eui64[1] = dechex(hexdec($eui64[1]) | 2); // flip seventh bit |
|
- | 201 | ||
- | 202 | return mac_canonize($eui64); |
|
- | 203 | } |
|
- | 204 | ||
- | 205 | /** |
|
- | 206 | * Try to convert IPv6-LinkLocal address to MAC-48 |
|
- | 207 | * @param string $ipv6 IPv6-LinkLocal address |
|
- | 208 | * @return false|string MAC-48 (or IPv6 if it was no LinkLocal address, or Modified EUI-64 if it decapsulation failed) |
|
146 | */ |
209 | */ |
147 | function ipv6linklocal_to_eui64(string $ipv6) { |
210 | function ipv6linklocal_to_mac48(string $ipv6) { |
148 | // https://stackoverflow.com/questions/12095835/quick-way-of-expanding-ipv6-addresses-with-php (modified) |
211 | // https://stackoverflow.com/questions/12095835/quick-way-of-expanding-ipv6-addresses-with-php (modified) |
149 | $tmp = inet_pton($ipv6); |
212 | $tmp = inet_pton($ipv6); |
150 | if ($tmp === false) return false; |
213 | if ($tmp === false) return false; |
151 | $hex = unpack("H*hex", $tmp); |
214 | $hex = unpack("H*hex", $tmp); |
152 | $ipv6 = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1); |
215 | $ipv6 = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1); |
Line 156... | Line 219... | ||
156 | $cnt = 0; |
219 | $cnt = 0; |
157 | $mac = preg_replace('@^fe80:0000:0000:0000:@i', '', $ipv6, -1, $cnt); |
220 | $mac = preg_replace('@^fe80:0000:0000:0000:@i', '', $ipv6, -1, $cnt); |
158 | if ($cnt == 0) return false; |
221 | if ($cnt == 0) return false; |
159 | 222 | ||
160 | // Set LAA to UAA again |
223 | // Set LAA to UAA again |
- | 224 | $mac_uaa_64 = $mac; |
|
161 | $mac[1] = dechex(hexdec($mac[1]) & 253); |
225 | $mac_uaa_64[1] = dechex(hexdec($mac_uaa_64[1]) & 253); |
162 | 226 | ||
163 | return eui64_to_eui48($mac); |
227 | $mac_uaa_48 = eui64_to_eui48($mac_uaa_64); |
- | 228 | if (eui_bits($mac_uaa_48) == 48) { |
|
- | 229 | return $mac_uaa_48; // Output MAC-48 (UAA) |
|
- | 230 | } else { |
|
- | 231 | return $mac; // Failed decapsulation; output Modified EUI-64 instead |
|
164 | } |
232 | } |
165 | - | ||
166 | /** |
- | |
167 | * @param string $mac |
- | |
168 | * @return false|int |
- | |
169 | */ |
- | |
170 | function eui_bits(string $mac) { |
- | |
171 | if (!mac_valid($mac)) return false; |
- | |
172 | $mac = mac_canonize($mac, ''); |
- | |
173 | return (int)(strlen($mac)*4); |
- | |
174 | } |
233 | } |
175 | 234 | ||
176 | /** |
235 | /** |
- | 236 | * Converts MAC-48 or EUI-48 to IPv6-LinkLocal (based on Modified EUI-64) |
|
177 | * @param string $mac |
237 | * @param string $mac |
178 | * @return false|string |
238 | * @return false|string |
179 | */ |
239 | */ |
180 | function eui_to_ipv6linklocal(string $mac) { |
240 | function maceui_to_ipv6linklocal(string $mac) { |
181 | if (!mac_valid($mac)) return false; |
241 | if (!mac_valid($mac)) return false; |
182 | if (eui_bits($mac) == 48) { |
242 | if (eui_bits($mac) == 48) { |
183 | $mac = eui48_to_eui64($mac); |
243 | $mac = maceui48_to_modeui64($mac); |
184 | } |
244 | } |
185 | $mac = mac_canonize($mac, ''); |
245 | $mac = mac_canonize($mac, ''); |
186 | - | ||
187 | $mac[1] = dechex(hexdec($mac[1]) | 2); |
- | |
188 | - | ||
189 | $mac = str_pad($mac, 16, '0', STR_PAD_LEFT); |
246 | $mac = str_pad($mac, 16, '0', STR_PAD_LEFT); |
190 | return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4)); |
247 | return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4)); |
191 | } |
248 | } |
192 | 249 | ||
193 | /** |
250 | /** |
- | 251 | * Prints information about an IPv6-LinkLocal address, MAC, or EUI. |
|
194 | * @param string $mac |
252 | * @param string $mac IPv6-LinkLocal address, MAC, or EUI |
195 | * @return void |
253 | * @return void |
196 | * @throws Exception |
254 | * @throws Exception |
197 | */ |
255 | */ |
198 | function decode_mac(string $mac) { |
256 | function decode_mac(string $mac) { |
199 | // Amazing website about MAC addresses: https://mac-address.alldatafeeds.com/faq#how-to-recognise-mac-address-application |
257 | // Amazing website about MAC addresses: https://mac-address.alldatafeeds.com/faq#how-to-recognise-mac-address-application |
200 | 258 | ||
201 | echo sprintf("%-32s %s\n", "Input:", $mac); |
259 | echo sprintf("%-32s %s\n", "Input:", $mac); |
202 | 260 | ||
- | 261 | // Format MAC for machine readability |
|
- | 262 | $mac = mac_canonize($mac, ''); |
|
- | 263 | ||
203 | $type = ''; |
264 | $type = ''; |
204 | $tmp = ipv6linklocal_to_eui64($mac); |
265 | $tmp = ipv6linklocal_to_mac48($mac); |
205 | if ($tmp !== false) { |
266 | if ($tmp !== false) { |
206 | $mac = $tmp; |
267 | $mac = $tmp; |
207 | $type = 'IPv6-Link-Local'; |
268 | $type = 'IPv6-Link-Local'; |
208 | } |
269 | } |
209 | if (!mac_valid($mac)) throw new Exception("Invalid MAC address"); |
270 | if (!mac_valid($mac)) throw new Exception("Invalid MAC address"); |
210 | if ($tmp === false) { |
271 | if ($tmp === false) { |
- | 272 | if (eui_bits($mac) == 48) { |
|
- | 273 | $type = 'MAC-48 (network hardware) or EUI-48 (other devices and software)'; |
|
- | 274 | } else if (eui_bits($mac) == 64) { |
|
- | 275 | if (substr($mac,6,4) == 'FFFE') { |
|
- | 276 | if ((hexdec($mac[1])&2) == 2) { |
|
- | 277 | $type = 'EUI-64 (MAC/EUI-48 to Modified EUI-64 Encapsulation)'; |
|
211 | // Size |
278 | } else { |
212 | $type = eui_bits($mac)==48 ? 'EUI-48 (6 Byte)' : 'EUI-64 (8 Byte)'; |
279 | $type = 'EUI-64 (EUI-48 to EUI-64 Encapsulation)'; |
- | 280 | } |
|
- | 281 | } else if (substr($mac,6,4) == 'FFFF') { |
|
- | 282 | $type = 'EUI-64 (MAC-48 to EUI-64 Encapsulation)'; |
|
- | 283 | } else { |
|
- | 284 | $type = 'EUI-64 (Regular)'; |
|
- | 285 | } |
|
- | 286 | } else { |
|
- | 287 | assert(false); /** @phpstan-ignore-line */ |
|
- | 288 | } |
|
213 | } |
289 | } |
214 | echo sprintf("%-32s %s\n", "Type:", $type); |
290 | echo sprintf("%-32s %s\n", "Type:", $type); |
215 | 291 | ||
216 | echo "\n"; |
292 | echo "\n"; |
217 | 293 | ||
218 | // Format MAC |
- | |
219 | $mac = mac_canonize($mac, ''); |
- | |
220 | - | ||
221 | // Show various representations |
294 | // Show various representations |
222 | $eui48 = eui64_to_eui48($mac); |
295 | $eui48 = eui64_to_eui48($mac); |
223 | echo sprintf("%-32s %s\n", "EUI-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48); |
296 | echo sprintf("%-32s %s\n", "EUI-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48); |
- | 297 | if (eui_bits($mac) == 48) { |
|
- | 298 | $eui64 = mac48_to_eui64($mac); |
|
- | 299 | echo sprintf("%-32s %s\n", "EUI-64:", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC-48 to EUI-64 Encapsulation)'); |
|
224 | $eui64 = eui48_to_eui64($mac); |
300 | $eui64 = eui48_to_eui64($mac); |
225 | echo sprintf("%-32s %s\n", "EUI-64:", (eui_bits($eui64) != 64) ? 'Not available' : $eui64); |
301 | echo sprintf("%-32s %s\n", "", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (EUI-48 to EUI-64 Encapsulation)'); |
- | 302 | $eui64 = maceui48_to_modeui64($mac); |
|
- | 303 | echo sprintf("%-32s %s\n", "", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC/EUI-48 to Modified EUI-64 Encapsulation)'); |
|
226 | $ipv6 = eui_to_ipv6linklocal($mac); |
304 | $ipv6 = maceui_to_ipv6linklocal($mac); |
227 | echo sprintf("%-32s %s\n", "IPv6 link local address:", $ipv6); |
305 | echo sprintf("%-32s %s\n", "IPv6 link local address:", $ipv6); |
- | 306 | } else { |
|
- | 307 | $eui64 = mac_canonize($mac); |
|
- | 308 | echo sprintf("%-32s %s\n", "EUI-64:", $eui64); |
|
- | 309 | } |
|
228 | 310 | ||
229 | // Vergabestelle |
311 | // Vergabestelle |
230 | $ul = hexdec($mac[1]) & 2; // Bit #LSB+1 of Byte 1 |
312 | $ul = hexdec($mac[1]) & 2; // Bit #LSB+1 of Byte 1 |
231 | $ul_ = ($ul == 0) ? '[0] Universally Administered Address (UAA)' : '[1] Locally Administered Address (LAA)'; |
313 | $ul_ = ($ul == 0) ? '[0] Universally Administered Address (UAA)' : '[1] Locally Administered Address (LAA)'; |
232 | echo sprintf("%-32s %s\n", "Administration type (U/L flag):", $ul_); |
314 | echo sprintf("%-32s %s\n", "Administration type (U/L flag):", $ul_); |
Line 235... | Line 317... | ||
235 | $ig = hexdec($mac[1]) & 1; // Bit #LSB+0 of Byte 1 |
317 | $ig = hexdec($mac[1]) & 1; // Bit #LSB+0 of Byte 1 |
236 | $ig_ = ($ig == 0) ? '[0] Individual (Unicast)' : '[1] Group (Multicast)'; |
318 | $ig_ = ($ig == 0) ? '[0] Individual (Unicast)' : '[1] Group (Multicast)'; |
237 | echo sprintf("%-32s %s\n", "Transmission type (I/G flag):", $ig_); |
319 | echo sprintf("%-32s %s\n", "Transmission type (I/G flag):", $ig_); |
238 | 320 | ||
239 | // Query IEEE registries |
321 | // Query IEEE registries |
240 | // TODO: gilt OUI nur bei Individual UAA? |
322 | // TODO: gilt OUI nur bei Individual UAA? For LAA, should we convert to UAA and then query the registry? |
241 | if (count(glob(IEEE_MAC_REGISTRY.'/*.txt')) > 0) { |
323 | if (count(glob(IEEE_MAC_REGISTRY.DIRECTORY_SEPARATOR.'*.txt')) > 0) { |
242 | if ( |
324 | if ( |
243 | # The IEEE Registration Authority distinguishes between IABs and OUI-36 values. Both are 36-bit values which may be used to generate EUI-48 values, but IABs may not be used to generate EUI-64 values.[6] |
325 | # The IEEE Registration Authority distinguishes between IABs and OUI-36 values. Both are 36-bit values which may be used to generate EUI-48 values, but IABs may not be used to generate EUI-64 values.[6] |
244 | # Note: The Individual Address Block (IAB) is an inactive registry activity, which has been replaced by the MA-S registry product as of January 1, 2014. |
326 | # Note: The Individual Address Block (IAB) is an inactive registry activity, which has been replaced by the MA-S registry product as of January 1, 2014. |
245 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/iab.txt', 'IAB', $mac)) || |
327 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'iab.txt', 'IAB', $mac)) || |
246 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/oui36.txt', 'OUI-36 (MA-S)', $mac)) || |
328 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'oui36.txt', 'OUI-36 (MA-S)', $mac)) || |
247 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/mam.txt', 'OUI-28 (MA-M)', $mac)) || |
329 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'mam.txt', 'OUI-28 (MA-M)', $mac)) || |
248 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/oui.txt', 'OUI-24 (MA-L)', $mac)) |
330 | ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'oui.txt', 'OUI-24 (MA-L)', $mac)) |
249 | ) { |
331 | ) { |
250 | echo $x; |
332 | echo $x; |
251 | } |
333 | } |
252 | } |
334 | } |
253 | 335 |