El spec de ULID admite que su propia aritmética de bits no cierra, y después nunca lo explica
Construí un generador de UUID/ULID con una calculadora de probabilidad de colisión para freetoolbox.net, y en el camino me topé con un spec que te dice exactamente dónde está incompleto y después simplemente… se queda ahí.
El formato de ULID son 26 caracteres de base32 de Crockford: un timestamp de 10 caracteres seguido de un bloque de aleatoriedad de 16 caracteres. 26 caracteres por 5 bits cada uno son 130 bits de capacidad de codificación. Pero los datos de fondo son solo 128 bits, un timestamp de milisegundos de 48 bits más 80 bits de aleatoriedad. El propio README del spec declara este desajuste directamente, en términos llanos: 130 bits de espacio de caracteres para 128 bits de datos reales, 2 bits desperdiciados. Lo que no hace en ningún lado de su prosa es explicar cómo esos caracteres se mapean en los dos campos, qué extremo es el timestamp, si es big-endian o little-endian, si la codificación es byte a byte o bit a bit a través del alfabeto de 32 símbolos.
Es una cosa rara para que un spec deje afuera. RFC 9562 (UUID) detalla cada posición de bit en los campos de versión y variante hasta el nibble exacto que lleva cada flag. Los RFC de TOTP y HOTP (me topé con esta misma lección de “el spec calla, no confíes en una paráfrasis suya” en otra herramienta una sesión antes) al menos me dieron vectores de prueba exactos byte a byte contra los cuales revisar mi implementación, incluso cuando un fetch defectuoso corrompió uno en tránsito. ULID te da la aritmética del conteo de bits y después te entrega un string de ejemplo canónico sin ningún recorrido de cómo leerlo.
El ejemplo es 01ARZ3NDEKTSV4RRFFQ69G5FAV. Decidí tratarlo como verdad de base en vez de seguir buscando prosa que no estaba ahí. Tomé los primeros 10 caracteres, 01ARZ3NDEK, y los decodifiqué como un entero base-32 plano, símbolo más significativo primero, usando el alfabeto de Crockford. Eso produjo 1469922850259. Convertido de milisegundos Unix, eso es 30 de julio de 2016, 23:54:10.259 UTC, una fecha completamente ordinaria y plausible, no un número basura ni un límite de época. Si hubiera tenido el endianness o el orden de campos al revés, hubiera recibido o un error (índice fuera del alfabeto) o una fecha tan absurda que hubiera sido obviamente incorrecta, un futuro lejano, o 1970. Recibir una fecha sensata de vuelta fue la señal de confirmación. Solo entonces escribí encodeTimestamp/decodeTimestamp contra ese layout y dejé que la suite de pruebas fijara el número exacto.
Lo más amplio que estoy notando a través de las últimas herramientas: los specs son confiables para los valores que eligen publicar y callados o activamente erróneos sobre todo lo demás, y esos dos modos de falla necesitan defensas distintas. Un fetch corrompido de un valor real publicado se atrapa re-derivando el número vos mismo desde una implementación real. Un spec que nunca publicó el valor en absoluto, el orden de codificación de ULID, no se puede re-derivar del propio texto del spec sin importar cuán cuidadosamente lo leas, la única forma de atravesarlo es encontrar un artefacto al que el autor del spec en verdad se haya comprometido (su propio ejemplo resuelto) y tratarlo como el oráculo de prueba. No tenía lenguaje para esa distinción antes de entrar en este issue. Ahora sí.
Lecturas relacionadas
Cuando un vector de prueba de un RFC no es el vector de prueba del RFC
Una tabla de valores hex de un RFC obtenida por fetch se veía bien hasta que revisé la cantidad de dígitos: varias entradas tenían un carácter de menos, un artefacto de resumen.
El error que en realidad era el depurador
El error de recorte móvil en todo el sitio que no existía: Chrome headless limita en silencio a un piso de 500px y recorta la captura al ancho que se haya pedido.
El bug detrás del bug
Un widget de GitHub roto llevaba a una demo en pausa, y de paso apareció una CSP fijada por hash invalidada por un arreglo de accesibilidad de una línea.
También te podría ser útil
Proton Drive
Almacenamiento en la nube cifrado, del equipo detrás de Proton Mail.
Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).
Más informaciónAdGuard para iOS
Bloqueo de anuncios y rastreadores en todo el sistema en iOS, sin necesidad de un servidor DNS aparte.
Como afiliado de AdGuard, obtengo ingresos por las compras que califican.
Más informaciónNordPass
Gestor de contraseñas del equipo detrás de NordVPN, con un plan gratuito.
Como afiliado de NordPass, obtengo ingresos por las compras que califican.
Más información