DEV Community

Cover image for CLASE BUFFER EN NODE.JS
jorgeabad
jorgeabad

Posted on

CLASE BUFFER EN NODE.JS

CLASE BUFFER EN NODE.JS

Actualmente, la clase “Buffer” de node.js implementa un API “Uint8Array” (“TypedArray” ES6) de una manera más optimizada y adecuada para los casos de uso Node.js. La clase “Buffer” es global por lo que es poco probable que uno tenga que utilizar alguna vez “require('buffer').Buffer”.

Cadena de prototipos de la clase Buffer

Node.js puede codificar y decodificar empleando las siguientes codificaciones: 'ascii', 'utf8', 'utf16le' (little endian), 'ucs2' que es un alias de 'utf16le', 'base64' codificación Base64, 'latin1', 'binary' que es un alias para 'latin1' y 'hex' codifica cada byte como dos caracteres hexadecimales. Si no se especifica la codificación emplea ‘utf8’.

A continuación se detallan alguno de los métodos y propiedades más importantes del módulo Buffer, para más detalle consultar la documentación.

Los códigos del post están disponibles en github

Creación, escritura y lectura de buffers.

Los códigos de todo apartado 15.4.1 están disponibles en github en el archivo buffer1.js

En versiones de Node.js anteriores a la v6, las instancias de Buffer se creaban utilizando la función constructora Buffer, por ejemplo “new Buffer(10)” asignaba un nuevo objeto “Buffer” de tamaño 10 bytes, el problema es que la memoria asignada para las instancias creadas de esta manera no eran inicializadas y podían contener datos confidenciales. Estas instancias de “Buffer” se debían inicializar manualmente utilizando el método “buffer.fill(0)” o escribiendo los datos completamente en el “Buffer”.

Con el fin de resolver el problema anterior el módulo Buffer ofrece cuatro formas asignar memoria “Buffer.from”, “Buffer.alloc”, “Buffer.allocUnsafe”, “Buffer.allocUnsafeSlow”.

Creación de buffer asignando tamaño.

Buffer.alloc(size[, fill[, encoding]]): Asigna un nuevo Buffer de tamaño “size” bytes, que se llena con el contenido pasado en “fill” que puede ser de tipo “String”, “Buffer” o “Integer”. Si el contenido “fill” es indefinido, el buffer se llenará a ceros. En el caso de que “fill” sea un String hay que indicarle la codificación de caracteres de la cadena, si no se indica por defecto usará “utf-8”

Buffer.allocUnsafe(size) y Buffer.allocUnsafeSlow(size): permiten crear una instancia de “Buffer” de tamaño “size” sin inicializar por lo que puede acceder a datos sensibles, es necesario inicializarlo mediante el método “fill”. El módulo “Buffer” pre asigna una instancia Buffer interna de tamaño “Buffer.poolSize” (por defecto 8192 bytes) que se utiliza como una agrupación para la asignación rápida de memoria a nuevas instancias de “Buffer” que van a ser creadas utilizando “Buffer.allocUnsafe() y allocUnsafeSlow()” solo cuando el tamaño “size” es menor o igual a la mitad de “Buffer.poolSize”, “Buffer.alloc” nunca usará la instancia que tiene pre asignada memoria.

console.log(Buffer.poolSize); //8192
console.log(Buffer.allocUnsafe(8)); //<Buffer b0 1f 62 30 0a 00 00 00>
console.log(Buffer.allocUnsafeSlow(8)); //<Buffer e8 d5 40 30 0a 00 00 00>
console.log(Buffer.allocUnsafe(8).fill(0)); //<Buffer 00 00 00 00 00 00 00 00>
console.log(Buffer.allocUnsafeSlow(8).fill(0)); //<Buffer 00 00 00 00 00 00 00 00>
Enter fullscreen mode Exit fullscreen mode

Mediante la opción de línea de comandos “--zero-fill-buffers” se puede forzar a todos las instancias de “Buffer” creadas con Buffer.allocUnsafe(), Buffer.allocUnsafeSlow() a ser rellenadas automáticamente con ceros en su creación. El uso de esta opción cambia el comportamiento predeterminado de estos métodos y puede tener un impacto significativo en el rendimiento.

    node --zero-fill-buffers
    > Buffer.allocUnsafe(10);
    <Buffer 00 00 00 00 00 00 00 00 00 00>
    > Buffer.allocUnsafeSlow(10);
    <Buffer 00 00 00 00 00 00 00 00 00 00>
Enter fullscreen mode Exit fullscreen mode
Modificar y obtener el valor de un byte por su posición.

buf[index] Mediante el operador [index] se puede obtener y establecer el valor de un byte que se encuentra en una determinada posición, “index”, de una instancia “buf” de “Buffer”. Los valores que se pueden asignar a cada byte estarán siempre entre 0x00 y 0xFF (hex) o 0 y 255 (decimal).

Leer y decodificar buffers

buf.toString([encoding[, start[, end]]]): Decodifica el contenido de una instancia, “buf”, de “Buffer” en una cadena de acuerdo con la codificación de caracteres especificado en “encoding”, “start” y “end” se emplean para decodificar sólo un subconjunto de “buf”.

buf.toJSON(): Devuelve la representación del objeto “buf” instancia de “Buffer” en un objeto “JSON”. JSON.stringify() es llamado implícitamente.

Para leer los valores que están almacenados en una instancia de “Buffer” como numéricos hay diferentes métodos en función de si lo queremos leer es un entero, coma flotante, si tiene signo o no, y si está representado con 8 bits, 16 bits o 32 bits en estos últimos casos si son BE o LE. Algunos de los métodos serían los siguientes: readDoubleBE, readDoubleLE, readFloatBE, readFloatLE, readInt8, readInt16BE, readInt16LE, readInt32BE, readInt32LE, readIntBE, readIntLE, buf.readUInt8, readUInt16BE, readUInt16LE, readUInt32BE, readUInt32LE, readUIntBE, readUIntLE. En todos los casos como parámetros se pasa un entero “offset” que representa el número de bytes que se deben omitir antes de comenzar a leer (en función del tipo de número como máximo podrá tener una determinado valor), y un booleano “noAssert” (Predeterminado: false) que permite saltar la validación de que el desplazamiento proporcionado sea correcto.

Veamos algunos ejemplos de lo expuesto hasta el momento, el código de los siguientes ejemplo y posteriores se encuentra en el archivo “Buffer.js”.

Creamos un buffer de tamaño 5 bytes inicializado todos sus bytes con el valor cero.

//Creamos un buffer 'buf0' de 5 bytes inicializado a ceros.
const buf0 = Buffer.alloc(5);
console.log("buf0:", buf0); //buf0: <Buffer 00 00 00 00 00>
//modificamos el primer byte de buf0 asignándole el valor 1
buf0[0] = 1;
console.log("buf0:", buf0); //buf0: <Buffer 01 00 00 00 00>
Enter fullscreen mode Exit fullscreen mode

Creamos un buffer de 2 bytes y llenamos cada byte con el valor 255, leemos el primer byte y el segundo byte como un entero de 8 bits sin signo.

const bufi = Buffer.alloc(2, 255);
console.log("bufi", bufi); //bufi <Buffer ff ff>
console.log("bufi[0]", bufi, bufi.readUInt8(0)); //bufi[0] <Buffer ff ff> 255
console.log("bufi[1]", bufi, bufi.readUInt8(1)); //bufi[1] <Buffer ff ff> 255
Enter fullscreen mode Exit fullscreen mode

Modificamos el primer byte asignándole el valor 1, leemos el valor del primer byte como un entero de 8 bits sin signo, y el valor de los 16 bits como un entero sin signo BE y LE.

bufi[0] = 1;
console.log("bufi[0]", bufi, bufi.readUInt8(0)); //bufi[0] <Buffer 01 ff> 1
console.log("bufi", bufi, bufi.readUInt16BE()); //bufi <Buffer 01 ff> 511
console.log("bufi", bufi, bufi.readUInt16LE()); //bufi <Buffer 01 ff> 65281
Enter fullscreen mode Exit fullscreen mode

Modificamos el valor del primer byte asignándole el valor -2, leemos el primer byte como un entero de 8 bits con signo, y como un entero de 8 bits sin signo.

bufi[0] = -2;
console.log("bufi[0]", bufi, bufi.readInt8(0)); //bufi[1] <Buffer fe ff> -2
console.log("bufi[0]", bufi, bufi.readUInt8(0)); //bufi[1] <Buffer fe ff> 254
Enter fullscreen mode Exit fullscreen mode

Creamos un nuevo Buffer de 10 bytes y lo rellenamos con los valores de “buf0”.

const buf1 = Buffer.alloc(12, buf0);
console.log("buf1:", buf1); //buf1: <Buffer 01 00 00 00 00 01 00 00 00 00 01 00>
Enter fullscreen mode Exit fullscreen mode

Si queremos almacenar el carácter ‘ñ’ codificado en ‘utf-8’ tendremos que almacenarlo en un buffer que tenga por lo menos un tamaño de 2 bytes.

const buf2 = Buffer.alloc(2, "ñ");
console.log("buf2:", buf2, buf2.toString()); //buf2: <Buffer c3 b1> ñ
Enter fullscreen mode Exit fullscreen mode

Si queremos almacenar el carácter ‘ñ’ codificado en ‘latin1’ lo podemos hacer mediante un buffer que solo tenga 1 byte.

const buf3 = Buffer.alloc(1, "ñ", "latin1");
console.log("buf3:", buf3, buf3.toString("latin1")); //buf3: <Buffer f1> ñ
Enter fullscreen mode Exit fullscreen mode

También se puede pasar en la cadena los códigos Unicode con el carácter de escape “\”.

//Creamos un buffer de 2 bytes pasándole en la cadena el punto de código unicode
const bufUnicode = Buffer.alloc(2, "\u00F1", "utf8"); //ñ
console.log("bufUnicode:", bufUnicode, bufUnicode.toString()); //bufUnicode: <Buffer c3 b1> ñ
//Creamos un buffer de 3 bytes pasándole una cadena con la combinación de dos puntos de código unicode
const bufUniCompuesto = Buffer.alloc(3, "\u006E\u0303", "utf8");
console.log("bufUniCompuesto:", bufUniCompuesto, bufUniCompuesto.toString()); //bufUniCompuesto: <Buffer 6e cc 83> ñ
Enter fullscreen mode Exit fullscreen mode
Creación de Buffer mediante el contenido a almacenar

Buffer.from(string[, encoding]): Crea un nuevo Buffer que contiene la cadena, “string”, pasada. Si se proporciona el parámetro de codificación, “encoding”, identifica la codificación de caracteres del parámetro “string” almacenando los bytes que se corresponden con la codificación especificada, si no se especifica la codificación por defecto emplea UTF-8.

const buf5 = Buffer.from("Buenos días UAH");
console.log("buf5: ", buf5, buf5.toString(), buf5.length, "bytes");
//buf5:  <Buffer 42 75 65 6e 6f 73 20 64 c3 ad 61 73 20 55 41 48> Buenos días UAH 16 bytes
const buf5Latin = Buffer.from("Buenos días UAH", "latin1");
console.log(
  "buf5Latin: ",
  buf5Latin,
  buf5Latin.toString("latin1"),
  buf5Latin.length,
  "bytes"
);
//buf5Latin:  <Buffer 42 75 65 6e 6f 73 20 64 ed 61 73 20 55 41 48> Buenos días UAH 15 bytes
console.log("ascii", buf5.toString("ascii")); //ascii Buenos dC-as UAH
console.log("latin-1", buf5.toString("latin1")); ////latin-1 Buenos dìas UAH
const buf6 = Buffer.from("4275656e6f732064c3ad617320554148", "hex");
console.log("buf6:", buf6, buf6.toString(), buf6.length, "bytes");
//buf6: <Buffer 42 75 65 6e 6f 73 20 64 c3 ad 61 73 20 55 41 48> Buenos días UAH 16 bytes
const buf_compuesto = Buffer.from("\u006E\u0303", "utf8"); //Unicode U+006E "n" + U+0303 “~”.
console.log(
  "buf_compuesto",
  buf_compuesto,
  buf_compuesto.toString(),
  buf_compuesto.length,
  "bytes"
);
//buf_compuesto <Buffer 6e cc 83> ñ 3 bytes
Enter fullscreen mode Exit fullscreen mode

Buffer.from(buffer): Copia los datos pasados en el “buffer” (tipo “Buffer”) a una nueva instancia de Buffer.

const buf7 = Buffer.from(buf6);
buf7[0] = 0x62;
console.log("buf7:", buf7, buf7.toString());
//buf7: <Buffer 62 75 65 6e 6f 73 20 64 c3 ad 61 73 20 55 41 48> buenos días UAH
Enter fullscreen mode Exit fullscreen mode

Buffer.from(array): Asigna un nuevo “Buffer” utilizando una “array” de octetos, que puede venir en codificación decimal o hexadecimal.

console.log("Buffer.from(array)");
console.log(Buffer.from([195, 177]).toString()); //ñ
console.log(Buffer.from([0xc3, 0xb1]).toString()); //ñ
console.log(Buffer.from([195, 0xb1]).toString()); //ñ
console.log(Buffer.from([0xf1]).toString("latin1")); //ñ
console.log(Buffer.from([0x6e, 0xcc, 0x83]).toString()); //ñ;
Enter fullscreen mode Exit fullscreen mode

Buffer.from(arrayBuffer[, byteOffset[, length]]): copia los datos desde un objeto de tipo ArrayBuffer. Cuando pasa una referencia a la propiedad “.buffer” de una instancia TypedArray, el Buffer recién creado compartirá la misma memoria asignada que TypedArray.

const miBuffer = new ArrayBuffer(2);
const vista = new Uint8Array(miBuffer);
vista[0] = 195;
vista[1] = 177;
console.log(miBuffer); //ArrayBuffer { byteLength: 2 }
console.log(vista); //Uint8Array [ 195, 177 ]
const buf = Buffer.from(vista.buffer);
console.log(buf, buf.toString()); //<Buffer c3 b1> 'ñ'
vista[1] = 145;
console.log(buf, buf.toString()); //<Buffer c3 91> 'Ñ'
Enter fullscreen mode Exit fullscreen mode
Llenado de un buffer.

buf.fill(value[, offset[, end]][, encoding]): Rellena una instancia “buf” de Buffer, desde una posición determinada “offset” hasta una posición final “end” con el “value” (String, integer, buffer) especificado. Si el “offset” y el “end” no son pasados, se llena el Buffer completo.

//U2600; bytes UTF8(hex e29880, dec [226 152 128]); char:☀
const buf4 = Buffer.alloc(3, "e29880", "hex");
console.log("buf4:", buf4, buf4.toString());
//buf4: <Buffer e2 98 80> ☀

//llenamos buf1 con la cadena 'hola' en utf8
buf1.fill("hola");
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 68 6f 6c 61 68 6f 6c 61 68 6f 6c 61> holaholahola

//llenamos a partir del 4º byte hasta el 9º byte incluido con cadena de 5 bytes.
buf1.fill(" UAH ", 4, 9);
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 68 6f 6c 61 20 55 41 48 20 6f 6c 61> hola UAH ola

//llenamos a partir del 9º byte hasta el 12º byte incluido con otro buffer de 3 bytes.
buf1.fill(buf4, 9, 12);
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 68 6f 6c 61 20 55 41 48 20 e2 98 80> hola UAH ☀

//llenamos a partir del 9º byte hasta el 12º byte incluido con '!' el código 33 en utf8
buf1.fill(33, 9, 12);
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 68 6f 6c 61 20 55 41 48 20 21 21 21> hola UAH !!!

//llenamos a partir del 9º byte hasta el 12º byte incluido con el caracter unicode u2661.
buf1.fill("\u2661", 9, 12);
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 68 6f 6c 61 20 55 41 48 20 e2 99 a1> hola UAH ♡

//llenamos a partir del 9º byte hasta el 12º byte incluido con 3 bytes.
buf1.fill("e29880", 9, 12, "hex");
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 68 6f 6c 61 20 55 41 48 20 e2 98 80> hola UAH ☀
Enter fullscreen mode Exit fullscreen mode

buf.write(string[, offset[, length]][, encoding]) Escribe en “buf” de tipo “Buffer” el valor de “string”, tipo cadena partiendo desde una posición determinada del buffer, “offset”, en función de la codificación de caracteres “encoding”. El parámetro “lenght” es el número de bytes a escribir. Si “buf” no contenía suficiente espacio para adaptarse a toda la cadena, sólo una cantidad parcial del string será escrito. No se escribirán los caracteres codificados parcialmente.

buf1.write("bye!");
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 62 79 65 21 20 55 41 48 20 e2 98 80> bye! UAH ☀

buf1.write("e298be", 9, 3, "hex");
console.log("buf1:", buf1, buf1.toString());
//buf1: <Buffer 62 79 65 21 20 55 41 48 20 e2 98 be> bye! UAH ☾
Enter fullscreen mode Exit fullscreen mode
Comparar

buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]]): Compara una region de memoria definida por “buf” o un área dentro de “buf” definida por los desplazamientos “sourceStrat” “sourceEnd” con otra región de memoria definida por “target” o por una área definida dentro de “target” por los desplazamientos “targetStar”, “targetEnd”.

Buffer.compare (buf1, buf2): ídem a llamar buf1.compare(buf2).
devuelve 0 si “target”, (buf1), es igual que “buf”(buf2).
devuelve 1 si “target”, (buf1), debe venir antes “buf” (buf2) al ser ordenado.
devuelve -1 si “target”, (buf1), debe aparecer después de “buf” (buf2) al ser ordenado.

buf.equals(otherBuffer): Devuelve “true” si ambos “Buffer”, “buf y “otherBuffer” tienen exactamente los mismos bytes, de lo contrario false.

const item1 = Buffer.from("Barcelona");
console.log("item1:", item1, item1.toString());
const item2 = Buffer.from("Almería");
console.log("item2:", item2, item2.toString());
const item3 = Buffer.from("Valencia");
console.log("item3:", item3, item3.toString());
const item4 = Buffer.from("Madrid");
console.log("item4:", item4, item4.toString());
const item5 = Buffer.from("Valencia");
console.log("item5:", item5, item5.toString());
/*
item1: <Buffer 42 61 72 63 65 6c 6f 6e 61> Barcelona
item2: <Buffer 41 6c 6d 65 72 c3 ad 61> Almería
item3: <Buffer 56 61 6c 65 6e 63 69 61> Valencia
item4: <Buffer 4d 61 64 72 69 64> Madrid
item5: <Buffer 56 61 6c 65 6e 63 69 61> Valencia*/

console.log("item1 vs item2:", Buffer.compare(item1, item2)); //1
console.log("item2 vs item1:", Buffer.compare(item2, item1)); //-1
console.log("item5 vs item3:", Buffer.compare(item5, item3)); //0

console.log(item1.compare(item2)); //1
console.log(item2.compare(item1)); //-1
console.log(item5.compare(item3)); //0

console.log(item3.equals(item1)); //false
console.log(item3.equals(item5)); //true

//buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])
console.log(item1.compare(item3, 1, 2, 1, 2)); // 0 (a===a)
console.log(item1.compare(item2, 1, 2, 1, 2)); //-1 (l>a)
console.log(item2.compare(item3, 2, 3, 1, 2)); //0 (l===l)
const buf_1 = Buffer.from([0, 1, 2, 3]);
const buf_2 = Buffer.from([2, 3, 0, 1]);
console.log(buf_1.compare(buf_2, 0, 1, 2, 3)); //0 (2===2)
console.log(buf_1.compare(buf_2, 1, 2, 3, 4)); //0 (3===3)
console.log(buf_1.compare(buf_2, 2, 4, 0, 2)); //0 [0,1]==[0,1]
console.log(buf_1.compare(buf_2, 0, 1, 0, 1)); //-1 (2 > 0)
console.log(buf_1.compare(buf_2, 3, 4, 3, 4)); //1 (1 < 3)
const lista = [item1, item2, item3, item4];
console.log("lista:", lista.toString()); //lista: Barcelona,Almería,Valencia,Madrid
console.log("lista ordenada:", lista.sort(Buffer.compare).toString());
/*lista ordenada: Almería,Barcelona,Madrid,Valencia*/
Enter fullscreen mode Exit fullscreen mode
Crear copias de un Buffer o copiar bytes entre Buffers.

buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]]): Copia todos los datos de una región de memoria “buf” o de un fragmento de “buf”, definido por “sourceStart” (desplazamiento desde donde comenzar la copia por defecto valor 0) y “sourceEnd” (desplazamiento para detener la copia, no incluido, por defecto: buf.length) a otra región de memoria, “target” (Buffer), pudiendo comenzar a guardar los datos en “target” a partir de una posición definida por el desplazamiento “targetStart”, por defecto 0.

const bufcopia = Buffer.alloc(3);
buf1.copy(bufcopia, 0, 5, 8); //copiamos en bufcopia el 6º, 7º y 8º byte de buf1
console.log("bufcopia", bufcopia, bufcopia.toString());
//bufcopia <Buffer 55 41 48> UAH
Enter fullscreen mode Exit fullscreen mode
Concatenar varios Buffer

Buffer.concat (list [, totalLength]): Devuelve un nuevo Buffer que es el resultado de concatenar todas las instancias Buffer pasadas en una matriz “list” (tipo Array). Se puede especificar mediante “totalLength” (Integer) la longitud total en bytes de todas las instancias cuando se concatenan en “list”.

Si “totalLength” se proporciona, se fuerza a un entero sin signo. Si la longitud combinada de Buffers en “list” excede totalLength, el resultado se trunca al valor “totalLength”. Si totalLength no se proporciona, se calcula a partir de las instancias Buffer en “list”, ejecutando un bucle adicional para calcularlo.

const sep = Buffer.from(", ");
const lista2 = [item1, sep, item2];
const conc = Buffer.concat(lista2);
console.log(conc, conc.toString()); //<Buffer 42 61 72 63 65 6c 6f 6e 61 2c 20 41 6c 6d 65 72 c3 ad 61> 'Barcelona, Almería'
const conc2 = Buffer.concat(lista2, 10);
console.log(conc2, conc2.toString()); //<Buffer 42 61 72 63 65 6c 6f 6e 61 2c> 'Barcelona,'
Enter fullscreen mode Exit fullscreen mode
Iterar un buffer.

Una instancia “buf” de “Buffer” se pueden iterar empleando el uso de la sintaxis ECMAScript 2015 (ES6) for..of. buf.entries() buf.keys() buf.values().

console.log("iteramos:", item1); //<Buffer 42 61 72 63 65 6c 6f 6e 61>
for (let b of item1) {
  console.log(b, b.toString(16), String.fromCharCode(b));
}
/* 66 '42' 'B'
97 '61' 'a'
114 '72' 'r'
99 '63' 'c'
101 '65' 'e'
108 '6c' 'l'
111 '6f' 'o'
110 '6e' 'n'
97 '61' 'a'
*/
for (let par of item1.entries()) {
  console.log(par);
}
//[ 0, 66 ] [ 1, 97 ] [ 2, 114 ] [ 3, 99 ] [ 4, 101 ] [ 5, 108 ] [ 6, 111 ] [ 7, 110 ] [ 8, 97 ]
for (let valor of item1.values()) {
  console.log(valor);
}
//66 97 114 99 101 108 111 110 97
Enter fullscreen mode Exit fullscreen mode
Manipular buffers

buf.slice([start[, end]]): Devuelve una instancia de “Buffer” que hace referencia a la misma memoria que el original, pero acotada por los índices “start”, entero donde debe comenzar el nuevo Buffer por defecto valor 0) y “end” donde debe terminar el nuevo “Buffer” (no incluido) por defecto buf.length.

const sliceItem1 = item1.slice(5, 9);
console.log(sliceItem1.toString()); //lona
//item1 y sliceItem1 comparten memoria
sliceItem1.fill("*"); //modifica memoria original
console.log(item1.toString()); //Barce****
Enter fullscreen mode Exit fullscreen mode

Ejemplos empleando el módulo Buffer.

A continuación unos ejemplos simples del uso de Buffer.

Listado de caracteres con puntos de código utf8.

En el archivo se encuentra un script con un algoritmo que permite mostrar por pantalla un carácter junto con su representación binaria y hexadecimal siguiendo la codificación UTF-8. El algoritmo solo permite representar los caracteres que pueden expresarse hasta con 3 bytes de longitud, empezando desde la posición decimal 32 hasta la posición decimal 65535.

Código disponible en github unicode_utf8.js

const buf3 = Buffer.alloc(3);
const buf2 = Buffer.alloc(2);
const buf = Buffer.alloc(1);
console.log("\nUTF8 (3 bytes) dese 0000 'Basic Latin' .... FFFF 'Specials'\n");
for (let a = 32; a <= 65535; a++) {
  let utf8;
  let hex;
  let decimal;
  let binario;
  if (a <= 127) {
    //solo empleamos un byte buf
    buf[0] = a; //ascii 7 bits (1 byte)
    hex = buf.toString("hex");
    cpUnicode = "U00" + a.toString(16).toUpperCase();
    utf8 = buf.toString();
    decimal = buf.readUInt8();
  }

  if (a >= 128 && a <= 2047) {
    //(7FF hex) entonces UTF-8 es 2 bytes.
    buf2[0] = 192 + a / 64;
    buf2[1] = 128 + (a % 64);
    hex = buf2.toString("hex");
    cpUnicode = "U00" + a.toString(16).toUpperCase();
    utf8 = buf2.toString();
    decimal = `[${buf2.readUInt8(0)} ${buf2.readUInt8(1)}]`;
    binario = `[${buf2.readUInt8(0).toString(2)} ${buf2
      .readUInt8(1)
      .toString(2)}]`;
  }

  if (a >= 2048 && a <= 65535) {
    // (FFFF hex) entonces UTF-8 tiene longitud de 3 bytes.
    buf3[0] = 224 + a / 4096;
    buf3[1] = 128 + ((a / 64) % 64);
    buf3[2] = 128 + (a % 64);
    hex = buf3.toString("hex");
    bin = hex.toString(2);
    cpUnicode = "U" + a.toString(16).toUpperCase();
    utf8 = buf3.toString();
    decimal = `[${buf3.readUInt8(0)} ${buf3.readUInt8(1)} ${buf3.readUInt8(
      2
    )}]`;
    binario = `[${buf3.readUInt8(0).toString(2)} ${buf3
      .readUInt8(1)
      .toString(2)} ${buf3.readUInt8(2).toString(2)}]`;
  }
  let print = `Unicode: ${cpUnicode}; bytes UTF8(hex ${hex}, dec ${decimal}); char:${utf8} ${binario}`;
  let control = `Unicode: ${cpUnicode}; bytes UTF8(hex ${hex}, dec ${decimal}); <ctrl>`;
  console.log(a < 127 || a > 159 ? print : control);
}
Enter fullscreen mode Exit fullscreen mode
Extraer información del flujo de datos de un archivo png.

En este ejemplo se trabajará con los datos leídos de un archivo png (Portable Network Graphics) que han sido almacenados en memoria. Para ello es necesario conocer la estructura del flujo de datos que está asociada a este tipo de archivo que viene reflejado en la norma “Functional specification. ISO/IEC 15948:2003 (E)” que se puede consultar en REC-PNG-20031110 a continuación se exponen algunos conceptos necesarios para entender el posterior ejemplo.

El flujo de datos PNG consiste en una firma PNG formada por 8 bytes con los siguientes valores en hexadecimal: 89 50 4E 47 0D 0A 1A 0A, seguida de una secuencia de fragmentos. Cada fragmento de la secuencia está formado por tres o cuatro campos: un campo “LENGTH” (entero sin signo de 4 bytes) que proporciona el número de bytes del contenido del campo “DATA” del correspondiente fragmento. Un campo “TYPE” formado por 4 bytes que definen el tipo de fragmento. Un campo “DATA” con un número de bytes de datos apropiados para el tipo de fragmento y por último un campo “CRC” de 4 bytes calculado a partir de los anteriores bytes del fragmento, sin incluir el campo de longitud.

Un flujo de datos PNG válido comenzará con la firma PNG, seguida inmediatamente por un fragmento IHDR (encabezado de imagen), luego uno o más fragmentos IDAT, y finalizará con un fragmento IEND (Image trailer). Solo se permite un fragmento IHDR y un fragmento IEND en un flujo de datos PNG. En el ejercicio vamos a emplear solo los fragmentos IHDR e IEND y la firma PNG.

El campo “TYPE” del fragmento “IHDR” siempre contiene los siguientes valores decimales 73 72 68 82 a continuación, su campo “DATA”, comienza con 4 bytes reservados para la anchura y continúa con otros 4 bytes reservados para la altura, después hay una serie de bytes reservados para otras propiedades. Anchura y altura dan las dimensiones de la imagen en píxeles. Son enteros sin signo de cuatro bytes PNG.

El campo “TYPE” del fragmento “IEND”, que marca el final de la transmisión de datos PNG, siempre contiene 4 bytes con los siguientes valores decimales 73 69 78 68 el campo DATA de este fragmento siempre está vacío.

Para leer el archivo “png” y almacenar el contenido en un buffer se va a usar el método “readFileSync” del módulo “fs” (file system) que se explicará en próximos capítulos. Una vez obtenido el buffer de datos, se analizará para determinar si los datos corresponden a un archivo “png”. En el caso de ser un archivo “png”, se accederá a los fragmentos IHDR (para extraer, su campo TYPE y la anchura/altura de la imagen) e IEND (para extrer el valor del campo TYPE) mostrando los datos por pantalla. Por último se codificará el contenido del buffer de datos de todo el archivo en una cadena, usando como codificación base64. En ocasiones puede ser útil el poder incrustar los datos de una imagen dentro del archivo HTML, sin tener que poner una dirección externa, esto se hace mediante DATA URIs especificando los bits del fichero de la imagen en base64, por ejemplo, cualquier navegador web puede interpretar la siguiente URI: “data: image/png; base64,” + “Cadena del png en base64”.

Código disponible en github png.js

const fs = require("fs");
const firmaPng = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); //firma del PNG
const fragmentoIHDR = Buffer.from([73, 72, 68, 82]); //"IHDR" Buffer.from(array de enteros)
const fragmentoIEND = Buffer.from([73, 69, 78, 68]); //"IEND" Buffer.from(array de enteros)
function esPNG(buffer) {
  //comparamos los 8 primeros bytes de un Buffer con la firma png
  if (buffer.compare(firmaPng, 0, 8, 0, 8) === 0) {
    //si son iguales
    const Type = Buffer.alloc(4); //4 bytes para el contenido TYPE de IHDR
    //Después de 8 bytes de firma viene el fragmento IHDR
    //el campo TYPE de IHDR de empieza en la posicion 12 son 4 bytes
    buffer.copy(Type, 0, 12, 16); //copiamos el contenido de TYPE en Type
    if (!Type.equals(fragmentoIHDR)) {
      // si no coincide con "IHDR"
      throw new TypeError("png no valido");
    }
    console.log("encontrado fragmento", Type.toString("utf8"));
    //Extraemos contenido de las posiciones correspondientes al campo type de IEND
    const fin = buffer.toString("utf8", buffer.length - 8, buffer.length - 4);
    if (fin === fragmentoIEND.toString("utf8")) {
      // si coincide con "IEND"
      console.log("encontrado fragmento", fin);
    }
    return true; //es un png
  }
}
/*Partiendo de los datos del buffer creamos una cadena usando base64 */
function encode_base64(buffer) {
  let png_base64 = buffer.toString("base64");
  return png_base64;
}

function dimension(buffer) {
  return {
    //4 bytes después de TYPE de frag IHDR
    anchura: buffer.readUInt32BE(16), //Lee 32 bits sin signo (Big Endian)
    altura: buffer.readUInt32BE(20)
  };
}

//Lee de forma síncrona todo el contenido de un archivo.
const imgData = fs.readFileSync("happy.png");
if (esPNG(imgData)) {
  //si es png muestra dimensiones y uri de datos
  console.log(dimension(imgData));
  console.log("data:image/png;base64,", encode_base64(imgData));
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)