Librería de codificación Base64 con Arduino

publicado en: Portada | 8

Base64 es un sistema de codificación que utiliza 64 símbolos agrupados en mensajes que tienen una longitud múltiplo de cuatro. Estos mensajes (paquetes de datos) se completan, si es necesario, con un símbolo más (así que se usan 65), frecuentemente el signo igual (=), si la información útil codificada resulta en una longitud menor.

Usando 64 signos se puede trabajar con los 10 números y las letras mayúsculas y minúsculas (26+26) del código ASCII, el problema es que resultan 62 símbolos, digamos, no ambiguos más dos que varían en diferentes implementaciones. Aunque algunas veces se haga referencia con la expresión «caracteres ASCII imprimibles», en realidad son los que van desde el representado por el código 32 (el espacio) hasta el 126 (~) los 95 verdaderamente imprimibles.

La implementación de la codificación Base64 más utilizada, la del PEM, que también es la utilizada por MIME, trabaja con los signos extra «+» y «/» y el signo «=» para rellenar de manera que los paquetes tengan una longitud múltiplo de cuatro. Las letras A-Z ocupan las posiciones 0-25, las letras a-z las posiciones 26-51, los números 0-9 las posiciones 52-61, el signo más (+) la posición 62 y la posición 63 está ocupada por la barra (/).

La forma de representar los datos en formato Base64 consiste en ir tomando, de los datos originales, grupos de 6 bits que se representan con el código correspondiente. Si sobran bits se rellenan con ceros a la derecha. Si la cantidad de códigos resultante no es múltiplo de cuatro se rellena con signos igual a la derecha.

En la siguiente imagen se muestra la codificación ASCII de un texto («ohmio») y la forma en la que se convierte a Base64. Como resultan 7 símbolos, el mensaje final necesitaría el relleno de un signo igual al final. Podría decirse que el texto «ohmio» en ASCII equivale a «b2htaW8=» en Base64.

Ejemplo de codificación en Base64

Los usos específicos de la codificación Base64 suelen imponer además una longitud máxima de línea. La implementación MIME limita a 76 caracteres cada línea. Normalmente las líneas se separarán por un código de fin de línea (CR, representado por el valor 0x0D en ASCII) y otro de nueva línea (NL, que corresponde al código ASCII 0x0A).

El inconveniente que se añade al implementar la codificación Base64 en un dispositivo con pocos recursos, como suele ser el caso de un microcontrolador es que hay que ir codificando a medida que llega la información o con un buffer mínimo, lo que exige, además, prever un sistema que indique que se ha llegado al final del mensaje original, por ejemplo, añadiéndole un código especial, o utilizando una patilla cuyo nivel (sincronizado con la recepción) indique el estado del mensaje.

El código de ejemplo de abajo es una librería para Arduino para codificar en Base64 que está implementado con ambos criterios: ir codificando la información que va llegando (sin un buffer) y esperar a una señal de aviso para terminar.

La parte fundamental del cálculo del código Base64 se realiza con la expresión:
(valor_original>>(2+(numero_valor%3)*2))|resto_base64
y el cálculo del resto con la expresión:
(valor_original&(MASCARA_B64>>desplazamiento))<<desplazamiento,
siendo desplazamiento un valor que se calcula con la expresión:
4-(numero_valor%3)*2

El proceso seguido para obtener a esas expresiones consiste en generalizar el cálculo de cada uno de los cuatro códigos Base64 que resultan al representar tres bytes del valor original.

Base64=((byte_1>>2)|resto)&0b00111111 resto=(byte_1&0b00000011)<<4
Base64=((byte_2>>4)|resto)&0b00111111 resto=(byte_2&0b00001111)<<2
Base64=((byte_3>>6)|resto)&0b00111111 resto=(byte_3&0b00111111)<<0
Base64=((byte_3>>0)|resto)&0b00111111 resto=(byte_3&0b00111111)<<0

Con el texto Base64 del pseudocódigo anterior se hace referencia al código en Base64 que se está calculando. Se ha utilizado la expresión byte_n para hacer referencia al enésimo byte que se está codificando. El texto resto representa los bits sobrantes del byte que se está codificando. Al empezar el cálculo se supone que el resto es cero

Por claridad, en el pseudocódigo anterior se ha incluido la máscara de 6 bits en el cálculo de todos los códigos, aunque solamente es necesaria para determinar el último de ellos, puesto que los demás se rotan de forma que se pierden siempre los dos bits más significativos.

Como puede verse, el cuarto código es todo resto y no es necesario calcular un resto después; solamente es necesario realizar, por tanto, tres pasos, uno por byte codificado. Es importante recordar que, si no se llegara a codificar un tercer byte en un paquete habría que rellenar con ceros a la derecha el último código Base64 obtenido.

Para generalizar, la rotación a la derecha de la expresión que calcula el código en Base64 se puede representar como 2+(numero_byte%3)*2 de forma que la parte dentro del paréntesis rotaría de cero a dos, resultando 2, 4 y 6 en cada paso. Desde luego no es la única forma de generalizar, pero he elegido esta por funcional y sobre todo por claridad. Dado que la máscara (AND) solamente era necesaria en el cuarto código y ya se visto que no es necesario calcularlo (es todo resto) no se incluye en la expresión final para simplificarla, aunque hay que recordar que del tipo de datos utilizado (byte) solamente se toman los 6 bits menos significativos.

La rotación a la izquierda del resto se puede generalizar de una manera análoga a la anterior. También puede observarse que la máscara que se aplica (AND) sufre la misma rotación de bits pero en el sentido contrario. Esa es la razón de calcular el desplazamiento con 4-(numero_valor%3)*2 antes de aplicarlo en el sentido correspondiente a cada parte de la expresión.

En el siguiente ejemplo se muestra cómo usar la librería para codificar una cadena de texto (recordad que Base64 puede utilizarse para cualquier conjunto de datos, como una imagen, por ejemplo). En el código siguiente hay un par de detalles que es interesante aclarar. En primer lugar, se ha utilizado un símbolo especial (el símbolo ~) para indicar el final del texto, en lugar de una señal hardware o de indicar la longitud del texto. Como es lógico, ese símbolo no puede formar parte de los datos que se codifican.

La segunda cuestión que hay que considerar, tan importante como evidente, es que el decodificador en el destino debe conocer cómo se representa la información que le llega. El texto incluye caracteres que no pertenecen al conjunto ASCII imprimible (del 32 al 126), las letras con tilde, por ejemplo. Arduino utilizará dos bytes (UTF-8) para representar estos caracteres. No se podrá utilizar sin más el habitual \0 como terminador de texto ya que, en muchos casos, el primer byte con el que se represente un carácter será precisamente cero.

La línea 26 del ejemplo anterior muestra el uso de la librería para Arduino para codificar en Base64. Solamente es necesario ir indicando al método convertir cada byte que se desea codificar y opcionalmente si es el último o, en caso contrario, detener la conversión con el método terminar al llegar al final.

Como puede verse en la captura de pantalla de abajo, el programa de ejemplo de la librería para Arduino para codificar en Base64 muestra en primer lugar el texto que se va a codificar en Base64, en este caso, el principio de la famosa canción de los gigantes Les Luthiers, y posteriormente el resultado de codificar en Base64 utilizando la longitud de línea del formato MIME.

Codificación Base64 con Arduino. Ejemplo de salida de conversión de texto

Víctor Ventura

Desarrollando aplicaciones para la web conocí el potencial de internet de las cosas, encontré la excusa perfecta para satisfacer la inquietud de aprender electrónica que había tenido desde siempre. Ahora puedo darme el gusto de programar las cosas que yo mismo diseño y fabrico.

Más entradas - Página web

Sígueme:
TwitterLinkedIn

Seguir Víctor Ventura:

Programador multimedia y web + IoT. Mejor con software libre.

Desarrollando aplicaciones para la web conocí el potencial de internet de las cosas, encontré la excusa perfecta para satisfacer la inquietud de aprender electrónica que había tenido desde siempre. Ahora puedo darme el gusto de programar las cosas que yo mismo diseño y fabrico.

8 Respuestas

  1. R Romera

    No entiendo por qué no se codifica todo el texto y se hace por partes. Me parece más complicado.
    codificado=base64("sin codificar");
    Tampoco entiendo por qué hay que partir el resultado en una medida fija y donde termine el texto.
    Por lo demás me parece que explica muy bien el método.
    Gracias.

    • Víctor Ventura

      ¡Hola!

      Como explico en el artículo, se codifica «por partes» (letra a letra), porque los MCU no andan muy sobrados de memoria como para tener un largo texto en ella e ir calculando otro largo texto. Además, algunas veces la información que debe codificarse no está toda disponible sino que se va cargado poco a poco.

      Ciertamente, en algunos casos puede ser mejor disponer de todo el resultado en Base64 sin partir en líneas (aunque me parece que lo más frecuente es limitar la longitud de la línea). Para no partir las líneas basta con comentar el código así:

      PD
      Discúlpame, he editado tu comentario porque el código que has puesto «formateaba» el resto del texto. Solamente he puesto la primera línea, que es lo que me ha parecido relevante, lo de abajo no se veía bien; si te parece que se pierde algo relevante en la explicación, por favor, vuelve a mandarlo.

    • Víctor Ventura

      La forma de codificar en Base64 teniendo acceso a todo el texto es similar a la descrita en la librería. A grandes rasgos sería como juntar los tres métodos principales para codificar, acumular y cerrar (terminar) el código. Sin refactorizar mucho, podría ser algo así:

      Si quieres usar esta función como alternativa a la librería para codificar en Base64 con Arduino puedes descargar la versión de la función de Arduino para codificar en Base64 que implementa la posibilidad de partir las líneas cada 76 caracteres conforme al RFC 2045.

      Espero que esto te ayude ¡Hasta pronto!

      • R Romera

        Muchas gracias por tu respuesta. Hay algunas cosas que no entiendo muy bien
        **codificado ¿Es un error y debe ser *codificado?
        Porque (char*)malloc
        Porque *sizeof(char)

        • Víctor Ventura

          En esta función, por ser para Arduino, no hace falta ni (char*)malloc ni sizeof(char), es un «vicio» que traigo de hacer programas para otras plataformas (no puedo decir categóricamente que es lo genérico).

          Lo que hace (char*)malloc es obligar a que malloc asigne memoria para caracteres y lo que hace sizeof(char) es multiplicar el número de caracteres por «lo que ocupe un carácter». Cuidado con el asterisco *, en el primer caso hace referencia a un puntero y en el segundo es para multiplicar.

          **codificado no es un error, o eso creo 🙂 Sirve para pasar un puntero al puntero a caracteres. La idea es poder reservarle memoria dentro de la función aunque «existe» fuera de la función. Habrás visto que, por eso, se utiliza (*codificado)[] para referirse a un carácter de la cadena de texto codificado.

          Saludos.

  2. guzman jose orlando

    Se puede codificar archivos tipo blob en base64 desde arduino antes de guardarlo en base datos

    • Víctor Ventura

      ¡Hola!

      Claro que es posible, pero seguramente no sea la mejor forma de hacerlo 🙁 …a no ser que el administrador del sistema te haya especificado precisamente ese método porque exista algún tipo de infraestructura al respecto.

      En sentido estricto, una vez codificada la información binaria en Base64 ya no es binaria (genérica) sino texto.

      A los administradores de sistemas no suelen gustarle los BLOB en las bases de datos, menos si la base de datos es para un servidor web, porque no es la forma más óptima de gestionar documentos.

      Además, los documentos codificados en Base64 ocupan más que los originales y tienen que ser procesados, primero para codificarlos (y almacenarlos) y luego para decodificarlos (y usarlos) ¿Qué ventaja aportaría tomarse esas molestias?

      La información (genérica, no texto) suele codificarse en Base64 cuando debe transmitirse por un canal que solo está preparado para trabajar con texto. Obviamente, hay muchos protocolos que ahora serían capaces de trabajar con información genérica pero que han heredado la exigencia de codificación Base64 y otros que lo usan buscando una pequeña ofuscación. El ejemplo típico de esto es el SMTP.

      Gracias por visitar polaridad.es ¡Vuelve pronto!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *