poner - php operador logico or
Operaciones CIDR bitwise-¿Podría ser un poco más sabio? (5)
Estoy construyendo una clase para representar una subred IPv4. Estoy almacenando la dirección de red y la máscara de subred como cadenas binarias de 4 bytes, que se crean durante el constructor en función de los argumentos. Una de las representaciones que me gustaría que aceptara el constructor es la notación CIDR .
Mis operaciones bitwise están un poco oxidadas, y donde me he quedado atascado es en la conversión de una representación CIDR de enteros decimales de la máscara de subred en una cadena binaria de 4 bytes, y viceversa. También me he dado cuenta de que no puedo realizar turnos de izquierda / derecha en la cadena, ¿estoy seguro de que lo he hecho antes?
He logrado obtener la conversión a la cadena binaria para trabajar con el siguiente código:
// An example input value.
$mask = 24; // 255.255.255.0
if ($mask < 0 || $mask > 32) {
// Invalid prefix size
throw new RangeException(''Invalid CIDR prefix size'');
} else if ($mask === 0) {
// Handle 0
$mask = "/x00/x00/x00/x00";
} else {
// Left-pad a 4-byte string with $mask set bits
$mask = pack(''N'', (0x01 << 31) >> ($mask - 1));
}
No me gusta esta lógica por dos razones:
- No me gusta tratar el
0
como un caso especial. - No me gusta el cambio a la derecha seguido de un cambio a la izquierda
Estoy seguro de que hay una manera de hacer esto de manera más eficiente de manera que maneje 0
correctamente sin tratarlo como un caso especial.
Al convertir una cadena binaria de nuevo a la representación decimal de un tamaño de prefijo CIDR, actualmente estoy usando el código a continuación. Tengo otro bloque de código muy similar al validar una máscara de subred proporcionada en otros formatos para garantizar que los bits establecidos sean contiguos.
// An example input value.
$mask = "/xff/xff/xff/x00"; // /24
// Convert the binary string to an int so bit shifts will work
$mask = current(unpack(''N'', $mask));
// A counter to represent the CIDR
$cidr = 0;
// Loop and check each bit
for ($i = 31; $i > 0; $i--) {
if (($mask >> $i) & 0x01) {
$cidr++;
} else {
break;
}
}
// Return the result
return $cidr;
No me gusta esto debido al bucle; estoy seguro de que hay una manera más inteligente de hacerlo en el modo bit a bit.
¿Hay alguna forma más inteligente de hacer cualquiera de estas tareas?
Pensamientos / sugerencias / abuso general por favor ...
EDITAR:
Todas las soluciones deben funcionar en PHP 4.3.10 y superiores, y deben funcionar en plataformas de 32 y 64 bits. Tenga en cuenta que todos los enteros en PHP están firmados, y en una plataforma de 32 bits cualquier cosa >= 0x80000000
se almacenará como un doble (y, por lo tanto, no funcionará bien con las operaciones a nivel de bits).
¿Por qué calcularlo en absoluto? Solo crea una matriz de 32 máscaras de subred.
$cidr2mask = array( "/x00/x00/x00/x00", "/x80/x00/x00/x00", "/xc0/x00/x00/x00", "/xe0/x00/x00/x00",
"/xf0/x00/x00/x00", "/xf8/x00/x00/x00", "/xfc/x00/x00/x00", "/xfe/x00/x00/x00",
"/xff/x00/x00/x00", "/xff/x80/x00/x00", "/xff/xc0/x00/x00", "/xff/xe0/x00/x00",
"/xff/xf0/x00/x00", "/xff/xf8/x00/x00", "/xff/xfc/x00/x00", "/xff/xfe/x00/x00",
"/xff/xff/x00/x00", "/xff/xff/x80/x00", "/xff/xff/xc0/x00", "/xff/xff/xe0/x00",
"/xff/xff/xf0/x00", "/xff/xff/xf8/x00", "/xff/xff/xfc/x00", "/xff/xff/xfe/x00",
"/xff/xff/xff/x00", "/xff/xff/xff/x80", "/xff/xff/xff/xc0", "/xff/xff/xff/xe0",
"/xff/xff/xff/xf0", "/xff/xff/xff/xf8", "/xff/xff/xff/xfc", "/xff/xff/xff/xfe");
$mask2cidr = array_flip($cidr2mask);
Luego solo usa $cidr2mask[$cidr];
y $mask2cidr[$mask]
.
(Auto promoción descarada)
He construido una biblioteca de PHP que hace cosas muy similares para las direcciones IP .
Así es como construyo una máscara de subred IPv4:
<?php
$mask = (~0) << (32 - $cidr);
$binary_mask = pack(''N'', $mask);
echo implode(''.'', unpack(''C4'', $binary_mask));
No funcionará en versiones anteriores de PHP debido a los espacios de nombres, pero tiene sucursales desde antes de su adición a las que me complacería aceptar solicitudes de extracción para solucionar problemas de compatibilidad. El código está (casi) 100% cubierto por pruebas unitarias :)
La única dependencia es el paquete pear Math_BigInteger .
El segundo problema puede resolverse mediante un enfoque textual:
$mask = "/xff/xff/xff/x00";
$cidr = strspn(sprintf(''%b'', current(unpack(''N'', $mask))), 1);
Utiliza sprintf()
para convertir el número entero en representación de texto binario y strspn()
cuenta el número de iniciales.
Actualizar
En las máquinas de 64 bits, la representación del texto binario se rellena a la izquierda con 32 ceros, por lo que el código debe parchearse con ltrim()
manera:
$cidr = strspn(ltrim(sprintf(''%b'', current(unpack(''N'', $mask))), 0), 1);
Actualización 2
El primer problema también se puede resolver con un enfoque textual, aunque requiere el uso de str_split()
(que no funciona en PHP 4.x):
$mask = vsprintf(''%c%c%c%c'', array_map(''bindec'', str_split(str_pad(str_repeat(1, $mask), 32, 0), 8)));
Actualización 3
Lo que me funciona es lo siguiente (probado en 32 y 64 bits):
$mask = pack(''N'', 0xffffffff << (32 - $mask));
En el proceso, el número se convierte en un flotador pero conserva la precisión suficiente para lidiar con el cambio de bits.
Por que no hacer
$netmask = ( (1<<32) -1 ) << ( 32 - $cidr);
Usted dijo que no le gustan los turnos de izquierda a derecha, ¿qué tal dos turnos de izquierda;)
Después de esto, lo tiro en ip2long
o long2ip
. Para pasar de la máscara al CIDR, haré:
$mask = ip2long($mask);
$base = ( ( 1 << 32 ) - 1 );
$cidr = 32 - log( ( $mask ^ $base ) + 1 , 2);
Por supuesto, puede pack
o dechex
, o unpack
lo anterior según sea necesario para adaptarse a su tipo de almacenamiento.
Su segundo problema puede verse alternativamente como encontrar el primer bit establecido en el número invertido (en lugar de encontrar el primer bit no establecido en el número no invertido), que es equivalente a encontrar el entero log2 del número.
Este es un problema bastante común en el mundo bitwise y existen numerosos algoritmos optimizados para la velocidad. Está utilizando el algoritmo obvio (lento): http://www-graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
Pero supongo que realmente no te importa la velocidad, sino la brevedad, en ese caso, podrías hacer algo como esto:
$cidr = (int) (32 - log(~current(unpack(''N'', $mask)) & 0xffffffff, 2));
El & 0xffffffff
es necesario para ser compatible con enteros de 64 bits.