Elixir ofrece una sintaxis elegante para trabajar con datos binarios, como ya hemos visto con la forma especial <<>> que se proporciona para trabajar con cadenas de bits.
El tipo binario es una especialización del tipo cadena de bits. Mientras que las cadenas de bits pueden tener cualquier longitud (cualquier número de bits), en los binarios el número de bits se puede dividir exactamente entre 8. Es decir, al trabajar con binarios a menudo pensamos en términos de bytes (8 bits). Un byte puede representar números enteros desde 0 hasta 255. Es habitual trabajar con valores de byte en hexadecimal, 0x00 - 0xFF.
Los literales binarios se definen con la forma especial de cadenas de bits <<>>. Al definir un literal binario, podemos usar literales de tipo entero y de tipo cadena. Los valores enteros mayores que 255 desbordan y solo se usan los últimos 8 bits del entero. De forma predeterminada, se aplica el modificador ::binary al valor. Podemos concatenar binarios con el operador <>/2.
<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>
Un byte nulo es otro nombre para <<0>>.
La coincidencia de patrones se extiende incluso a los binarios, y podemos aplicar coincidencia de patrones sobre una porción de datos binarios igual que podríamos hacerlo con una lista.
# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>
Como con otros tipos de coincidencia de patrones, podemos usar esto en firmas de función para hacer coincidir al seleccionar entre varias cláusulas de función.
Has estado trabajando en un proyecto que permite a los usuarios subir archivos al servidor para compartirlos con otros usuarios. Se te ha encargado escribir una función que verifique que un archivo subido coincide con su tipo de medio. Investigas un poco y descubres que los primeros bytes de un archivo suelen ser únicos de ese tipo de archivo, lo que le da una especie de firma.
Usa la siguiente tabla como referencia:
| Tipo de archivo | Extensión común | Tipo de medio | «firma» binaria |
|---|---|---|---|
| ELF | "exe" |
"application/octet-stream" |
0x7F, 0x45, 0x4C, 0x46 |
| BMP | "bmp" |
"image/bmp" |
0x42, 0x4D |
| PNG | "png" |
"image/png" |
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
| JPG | "jpg" |
"image/jpg" |
0xFF, 0xD8, 0xFF |
| GIF | "gif" |
"image/gif" |
0x47, 0x49, 0x46 |
Implementa la función type_from_extension/1. Debe recibir una extensión de archivo (string) y devolver el tipo de medio (string) o nil si la extensión no coincide con las esperadas.
FileSniffer.type_from_extension("exe")
# => "application/octet-stream"
FileSniffer.type_from_extension("txt")
# => nil
Implementa la función type_from_binary/1. Debe recibir un archivo (binario) y devolver el tipo de medio (string) o nil si la firma del archivo no coincide con las esperadas.
file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"
file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil
No te preocupes por leer el archivo como binario. Da por hecho que eso ya está hecho y que los tests lo proporcionan como argumento.
Implementa la función verify/2. Debe recibir un archivo (binario) y una extensión (string) y devolver una tupla :ok o :error.
file = File.read!("application.exe")
FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}
FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}
Regístrate en Exercism para aprender y dominar Elixir con 58 conceptos168 ejercicios y mentoría humana real, todo gratis.