Para cierta aplicación web, necesitamos enviar un GUID que indetifique la transacción, lo cuál estábamos haciendo con
String uuid = UUID.randomUUID().toString();
El problema es que el programa del banco sólo aceptaba un máximo de 20 caracteres…
Por lo cual nos pusimos a buscar cómo reducir el tamaño del GUID:
Como son identificadores globales en base a ciertas variables (cuáles?), no se puede escoger el tamaño del GUID, ni cortarlo sin perder su característica principal: que sea único. Lo que sí se puede hacer es codificarlo.
Los post más famosos permiten cofigicar a base 64 y decodificar de vuelta de modo que el guid resultante sigue siendo idéntico al inicial, i.e. conservando su unicidad. >> Esto puede bajar el largo de 36 bytes a hasta 24 caracteres… lo que no nos sirve.
La respuesta parece ser la codificación en Ascii85, que por utilizar menos caracteres puede reducirlo hasta los 20 bytes. La base 85, de hecho, está pensada para poder codificar utilizando solamente los caracteres Ascii imprimibles.
Para mayor detalle, en codinghorror tienen un caso de uso casi idéntico >> (sólo que en C#…)
Para esto utilizaremos la implemenación de Ascii 85 disponible en java.net
y escribiremos una implementación similar a la de este artículo >>
/** * Pasa UUID a arreglo de bytes */ public static byte[] guidAsBytes(UUID uuid){ byte[] out = new byte[16]; ByteBuffer bb = ByteBuffer.wrap(out); bb.order(ByteOrder.BIG_ENDIAN); //ascii 85 usa big endian bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); return out; }
el cual luego podemos codificar
public static String codificarBytesBase85(byte[] bytes){ String retorno = ""; ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); Ascii85OutputStream asciiOut = new Ascii85OutputStream(byteOut); try { asciiOut.write(bytes); asciiOut.flush(); retorno = byteOut.toString("ascii"); retorno = retorno.substring(2, 22); //se le quitan los caracteres extra } catch (UnsupportedEncodingException e) { return retorno; } catch (IOException e) { return retorno; } return retorno; }
public static byte[] decodificarStringAscii85(String base85){ ArrayList<Byte> lista = new ArrayList<Byte>(); try { ByteArrayInputStream byteIn = new ByteArrayInputStream(base85.getBytes("ascii")); Ascii85InputStream asciiIn = new Ascii85InputStream(byteIn); int r; while((r=asciiIn.read())!=-1){ lista.add((byte)r); } } catch (UnsupportedEncodingException e) { } catch (IOException e) { } byte[] retorno = new byte[lista.size()]; for(int i=0; i<lista.size(); i++){ retorno[i] = lista.get(i); } return retorno; }
La gracia es que si descodificamos el string anterior y con ese arreglo de bytes generamos un nuevo GUID, son iguales.
Es decir que no hay riesgo de colisión.
byte[] back = codificadorAscii85.decodificarStringAscii85(base85); UUID otroGuid = codificadorAscii85.toUUID(back); //ver toUUID del primer link System.out.println("Iguales ? "+otroGuid.compareTo(uuid));
Esto funciona, salvo que el String así obtenido no es url-friendly. Sí puede enviarse por URL,
pero el navegador va a convertir los caracteres extraños, aumentando el largo.
Ahora, se podría pensar que cualquier programa decente decodifica la URL antes de utilizarla…
bueno, éste no.