{"record":{"id":"64238e720aafde9f","repo":"Anuken/Mindustry","slug":"data-must-be-exactly-32-bytes-length-data-leng","errorCode":null,"errorMessage":"Data must be exactly 32 bytes (length: ${data.length})","messagePattern":"Data must be exactly 32 bytes \\(length: (.+?)\\)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/mindustry/mod/DataAssetCache.java","lineNumber":53,"sourceCode":"        return hash;\n    }\n\n    public boolean has(String hash){\n        return get(hash) != null;\n    }\n\n    public synchronized @Nullable Fi get(String shaHash){\n        if(shaHash == null) return null;\n        return hashToFile.get(shaHash);\n    }\n\n    public @Nullable Fi get(byte[] shaHash){\n        return get(encodeHash(shaHash));\n    }\n\n    //base32 without padding; data must be exactly 32 bytes\n    public static String encodeHash(byte[] data){\n        if(data.length != 32) throw new IllegalArgumentException(\"Data must be exactly 32 bytes (length: \" + data.length + \")\");\n        char[] out = new char[52];\n        int di = 0, oi = 0;\n\n        for(int i = 0; i < 6; i++){\n            long bits =\n                ((long)(data[di++] & 0xFF) << 32) |\n                ((long)(data[di++] & 0xFF) << 24) |\n                ((long)(data[di++] & 0xFF) << 16) |\n                ((long)(data[di++] & 0xFF) << 8)  |\n                ((long)(data[di++] & 0xFF));\n\n            out[oi++] = base32Alphabet[(int)(bits >>> 35) & 0x1F];\n            out[oi++] = base32Alphabet[(int)(bits >>> 30) & 0x1F];\n            out[oi++] = base32Alphabet[(int)(bits >>> 25) & 0x1F];\n            out[oi++] = base32Alphabet[(int)(bits >>> 20) & 0x1F];\n            out[oi++] = base32Alphabet[(int)(bits >>> 15) & 0x1F];\n            out[oi++] = base32Alphabet[(int)(bits >>> 10) & 0x1F];\n            out[oi++] = base32Alphabet[(int)(bits >>> 5) & 0x1F];","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/Anuken/Mindustry/blob/f695ad7e60323ebced984fa26d0bcf0bc54296b4/core/src/mindustry/mod/DataAssetCache.java#L35-L71","documentation":"DataAssetCache.encodeHash encodes a SHA-256 digest (exactly 32 bytes) into a 52-char unpadded base32 string used as the asset cache key. Passing a byte array of any other length is treated as a programming error and rejected immediately.","triggerScenarios":"encodeHash is called with a hash whose length is not 32 — e.g. MD5 (16), SHA-1 (20), SHA-512 (64), or a truncated/extended buffer.","commonSituations":"Using the wrong digest algorithm to produce asset hashes; manually constructing hash bytes; corrupted/partial hash from a stream.","solutions":["Use SHA-256: MessageDigest.getInstance(\"SHA-256\").digest(...) yields 32 bytes.","Assert data.length == 32 before calling encodeHash.","Do not reuse encodeHash for non-SHA-256 digests."],"exampleFix":"// before\nbyte[] hash = MessageDigest.getInstance(\"MD5\").digest(bytes);\nString key = DataAssetCache.encodeHash(hash); // throws: length 16\n\n// after\nbyte[] hash = MessageDigest.getInstance(\"SHA-256\").digest(bytes);\nString key = DataAssetCache.encodeHash(hash);","handlingStrategy":"validation","validationCode":"// Guarantee a 32-byte digest before encoding.\nif(data == null || data.length != 32) {\n    throw new IllegalArgumentException(\"SHA-256 hash required (32 bytes), got: \" + (data == null ? 0 : data.length));\n}\nString key = DataAssetCache.encodeHash(data);","typeGuard":"boolean isValidSha256(byte[] d){ return d != null && d.length == 32; }","tryCatchPattern":null,"preventionTips":["Always produce hashes with SHA-256.","Never feed digests from other algorithms into encodeHash.","Add a length assertion in test/build code that exercises hashing."],"tags":["hashing","validation","assets","cache"],"backgroundTag":null,"analyzedSha":"f695ad7e60323ebced984fa26d0bcf0bc54296b4","analyzedAt":"2026-08-14T04:31:16.262Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}