aprendiendo ( Erlang ).

lunes, 20 de junio de 2011

Trabajando con binarios

| 0 comentarios |

A la hora de almacenar grandes cantidades de datos los mejor es utilizar datos binarios. Los sistemas están pensados para trabajar con ellos (como es normal) y las operaciones de entrada/salida de ficheros binarios son mucho más rápidas.
El tipo binary, se representa como una secuencia de enteros o cadenas, de la siguiente forma:
1> <<15,110,220>>.
<<15,110,220>>
2> <<"perro">>.
<<"perro">>
El rango de valores enteros admitidos va de 0 a 255. Cuando se utiliza cadenas para representar el dato binario Erlang los trasforma automáticamente a su valor ASCII. El Eshell intenta imprimir los binarios como cadena de carácteres pero si no es posible los imprime como secuencia de enteros.
3> <<112,101,114,114,111>>.
<<"perro">>

BIF's para manipulación de binarios

Estas son algunas funciones que nos permite manipular tipos binarios:
  • @spec list_to_binary(IoList) -> binary() convierte una lista en un tipo binario. La lista puede contener enteros en el rango de 0 a 255, cadenas o binarios.
    4> list_to_binary( [1,2,<<112,101,114,114,111>>] ).  
    <<1,2,112,101,114,114,111>> 
    
  • @spec split_binary(Bin, Pos) -> {Bin1, Bin2} divide un binario en dos por la posición dada.
    5> split_binary(<<1,2,112,101,114,114,111>>, 2). 
    {<<1,2>>,<<"perro">>}
    
  • @spec term_to_binary(Term) -> Bin y @spec binary_to_term(Bin) -> Term convierte un termino Erlang en binario y viceversa. Esto nos permite guardar secuencias de términos en ficheros, enviarlos mediante mensajes u otros protocolos, y ser reconstruidos perfectamente posteriormente.
    6> X = term_to_binary( {termino, [1,2,4], "perro", binario} ).     
    <<131,104,4,100,0,7,116,101,114,109,105,110,111,107,0,3,1,
      2,4,107,0,5,112,101,114,114,111,100,0,...>>
    7> binary_to_term(X).
    {termino,[1,2,4],"perro",binario}
    
  • @spec size(Bin) -> Int calcula el número de bytes del binario.
    8> size(<<1,2,3,4,5>>).
    5
    
Como podemos ver, es muy cómodo trabajar con binarios sólo tenemos que trasformarlo en una lista, y trabajar con ella como hasta ahora.

Trabajando a nivel de bit's

La sintaxis para trabajar a nivel de bit's es verdaderamente potente. Se trata de una extensión pattern matching, y permite empaquetar o seleccionar cualquier conjunto de bits de un dato binario. Cuando se trabaja a bajo nivel es muy práctico poder extraer y establecer cada conjunto de bits de forma cómoda.
Lo dicho, es más fácil de entender con un ejemplo. Suponte que tenemos un protocolo con una palabra de 16 bit's. Pues bien, esta palabra está dividida en tres partes o conjuntos de bits. La primera parte, llamémosla X, es el primer bit, la segunda parte (Y), será los siguientes 10 bit's y el resto (Z) de los bits conformará el último grupo. Definamos nuestra palabra:
9> << X:1, Y:10, Z:5 >> = << 23,180 >>.
<<23,180>>
10> X.
0
11> Y.
189
12> Z.
20
El ejemplo es claro, partiendo de un binario de dos bytes (16 bit's), lo hemos decodificado en las partes que nos proponía el protocolo. Potente ¿verdad?. En otros lenguajes, tendríamos que estar utilizando máscaras y operaciones para llegar a obtener los tres campos que componen nuestra palabra. Y nosotros lo hemos hecho en una única operación.
Suponte que ahora queremos cambiar el primer bit, es decir, X y volverlo a codificar para enviarlo o escribirlo en un fichero.
13> Codificado = <<1:1, Y:10, Z:5>>. 
<<151,180>>
Listo. Fantástico.
Veamos la sintaxis de este tipo de expresiones y la potencia que nos proporciona. Su sintaxis viene dada por la expresión <<E1, E2, ..., En>>, donde cada Ei de la expresión, es un conjunto de bit's del binario. Opcionalmente, se puede establecer la base de trabajo como se muestra en los siguientes formatos que Ei puede tomar:
  • [Base#]Ei=Valor establece una valor concreto al elemento Ei
  • [Base#]Ei=Valor:Tamaño indica además el número de bits reservados para Ei
  • [Base#]Ei=Valor/Tipo indica que el valor es del tipo Tipo y su tamaño dependerá del tipo
  • [Base#]Ei=Valor:Tamaño/Tipo establece el valor tamaño y el tipo
Cuando construimos un binario, el Valor quedrá asociado a la variable Ei. Y este valor puede ser una cadena, un entero, un flotante, un binario, o cualquier expresión que evalúe a alguno de los tipos mentados.
El Tamaño es un entero o cualquier expresión que evalúe a un entero. Especifica el tamaño del segmento y depende del tipo el número de bit's que tomará finalmente.
El Tipo es una lista de valores separada por guiones con la forma End-Signo-Tipo-Unidad. Cualquiera de la partes del tipo se puede omitir, se puede poner en cualquier orden, pero si se omite entonces se utilizará el valor por defecto.
  • @type End = big | little | native el valor por defecto es big. Especifica la endianess de la máquina.
  • Es decir, depende como la máquina escriba y lea los bytes de memoria, de izquierda a derecha o viceversa. En el artículo de la Wikipedia, da más información sobre este término.
    14> <<21312333123:16/big>>.   
    <<"eC">>
    15> <<21312333123:16/little>>.
    <<"Ce">>
    16> <<21312333123:16/native>>.
    <<"Ce">>
    17> <<21312333123:16>>.           
    <<"eC">>
    
    Con este pequeño ejemplo podemos ver como se empaqueta con los distintos formatos y cual usa mi máquina.
  • @type Signo = signed | unsigned por defecto es unsigned. He indica si es con signo o sin signo.
  • @type Tipo = integer | float | binary por defecto es integer.
  • @type Unidad = 1 | 2 | ... 255 el valor por defecto es 1 para enteros y flotantes y 8 para datos binarios. Si indicamos la unidad, el tamaño del segmento, números de bits a tomar, es el Tamaño * Unidad.
La sintaxis a nivel de bits es algo complicada y siempre engorrosa. Pero es cuestión de utilizar un método empírico para resolver este tipo de problemas. Prueba y error es la única manera ya que este tipo de patrones nunca se resuelven a la primera.

Truco

Si te manejas bien con C o C++, este truco te resultará muy útil. Pero sino, también, ya que es probable que encuentres formatos de ficheros o protocolos escritos en dichos lenguajes.
Se trata de predefinir algunos formatos estándares en un fichero hrl como macros e incluirlo cuando lo necesites. De la siguiente forma:
-define(DOUBLE, 64/little-float).
-define(DWORD, 32/unsigned-little-integer).
-define(LONG, 32/unsigned-little-integer).
-define(WORD, 16/unsigned-little-integer).
-define(BYTE, 8/unsigned-little-integer).

Ejemplo. Desempaquetando de una cabecera IPv4

Veamos un ejemplo práctico. Implementemos un desempaquetado para la cabecera de un paquete IPv4. Veamos la estructura de un paquete:
  • 4 bits para la versión.
  • 4 bits para la longitud de la cabecera.
  • 8 bits para el tipo de servicio (QoS).
  • 16 bits para la longitud de paquete en bytes.
  • 16 bits para un identificador de fragmento.
  • 3 bits para flags que indican si el paquete es completo, esta fragmentado, etc...
  • 13 bits para el desplazamiento del fragmento para averiguar la posición dentro del paquete completo.
  • 8 bits para el TTL.
  • 8 bits para el protocolo (TCP, UDP, ICMP, etc.).
  • 16 bits para checksum de la cabecera.
  • 32 bits para dirección de origen.
  • 32 bits para dirección de destino.
  • Resto de datos información que porta el paquete.
Supongamos que son ciertos los datos del paquete IP que me he inventado. La solución sería algo así:
18> << Version:4, Long_cabecera:4, Tipo_servicio:8, Longitud_paquete:16, Id_fragmento:16, 
       Flags:3, Offset:13, TTL:8, Protocolo:8, Checksum:16, Ip_origen:32, Ip_destino:32, 
       Datos/binary >> = <<44,165,43,26,54,65,42,132,135,46,54,65,46,35,41,31,65,46,54,
        32,132,165,43,216,54,32,98,79,87,98,79,87,98,72,43,216,55,
        55,55,54,64,65,32,132,141,98,79,87,98,79,87,65,46,54,65,40,
        160,54,48,006,54,65,46,54,54,65,40,65,46,54,06,54,40>>.
<<44,165,43,26,54,65,42,132,135,46,54,65,46,35,41,31,65,
  46,54,32,132,165,43,216,54,32,98,79,87,...>>
19> Version.
2
20> Long_cabecera.
12
21> Ip_origen.    
774056223
Ha resultado sencillo realizar el desempaquetado. Como nota curiosa, mira el último campo del binario (Datos). Este campo contempla el resto de datos que existen en el paquete IPv4, es decir, los datos.

Publicar un comentario

0 comentarios:

 
Licencia Creative Commons
Aprendiendo Erlang por Verdi se encuentra bajo una Licencia Creative Commons Atribución-NoComercial-CompartirIgual 3.0 Unported.