From 7345515902fad16fe64df41b71d4d5fb9202008a Mon Sep 17 00:00:00 2001 From: Dushnilin Date: Sat, 1 Aug 2026 18:43:22 +0300 Subject: [PATCH] =?UTF-8?q?fix(po2lmo):=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=85=D0=B5=D1=88-=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F=20sfh=5Fhash=20=D0=B2=20po?= =?UTF-8?q?2lmo.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-tachyon/po/ru/tachyon.ru.lmo | Bin 83164 -> 83164 bytes tools/debug_lmo.js | 93 ++++++++++++++++++++++++++ tools/hash_test.js | 26 +++++++ tools/po2lmo.js | 86 ++++++++++++++---------- tools/test_hash.js | 26 +++++++ tools/test_ref.lmo | Bin 0 -> 1036 bytes tools/verify_hash.js | 66 ++++++++++++++++++ 7 files changed, 262 insertions(+), 35 deletions(-) create mode 100644 tools/debug_lmo.js create mode 100644 tools/hash_test.js create mode 100644 tools/test_hash.js create mode 100644 tools/test_ref.lmo create mode 100644 tools/verify_hash.js diff --git a/luci-app-tachyon/po/ru/tachyon.ru.lmo b/luci-app-tachyon/po/ru/tachyon.ru.lmo index a414d35b8eff78510cbb5c42fcd99059ff3781ba..ac3d4fdbb836637a98aa7fd187bd684967eff6b3 100644 GIT binary patch delta 16367 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 6; + } + hash += hash << 3; + hash ^= hash >>> 11; + hash += hash << 15; + return hash >>> 0; +} + +console.log('luci_sfh empty:', luci_sfh_hash('').toString(16)); +console.log('luci_sfh Plural-Forms:', luci_sfh_hash('Plural-Forms').toString(16)); + +// Now check my po2lmo sfh_hash +function po2lmo_sfh(data) { + let hash = 0; + let i = data.length; + let k; + while (i >= 4) { + k = (data.charCodeAt(i - 4) & 0xff) | + ((data.charCodeAt(i - 3) & 0xff) << 8) | + ((data.charCodeAt(i - 2) & 0xff) << 16) | + ((data.charCodeAt(i - 1) & 0xff) << 24); + k = Math.imul(k, 0x5bd1e995); + k = k >>> 0; + k ^= k >>> 24; + k = Math.imul(k, 0x5bd1e995); + k = k >>> 0; + hash = Math.imul(hash, 0x5bd1e995) + k; + hash = hash >>> 0; + i -= 4; + } + switch (i) { + case 3: hash ^= (data.charCodeAt(0) & 0xff) << 16; + case 2: hash ^= (data.charCodeAt(i - 2) & 0xff) << 8; + case 1: hash ^= (data.charCodeAt(i - 1) & 0xff); + hash = Math.imul(hash, 0x5bd1e995); + hash = hash >>> 0; + } + hash ^= hash >>> 13; + hash = Math.imul(hash, 0x5bd1e995); + hash = hash >>> 0; + hash ^= hash >>> 15; + return hash >>> 0; +} + +console.log('po2lmo_sfh empty:', po2lmo_sfh('').toString(16)); +console.log('po2lmo_sfh Plural-Forms:', po2lmo_sfh('Plural-Forms').toString(16)); + +// Read the reference LMO +const buf = fs.readFileSync('tools/test_ref.lmo'); +const indexOffset = buf.readUInt32BE(buf.length - 4); +const entryCount = (buf.length - 4 - indexOffset) / 16; +console.log('\nReference LMO entries:'); +for (let i = 0; i < Math.min(10, entryCount); i++) { + const off = indexOffset + i * 16; + const keyId = buf.readUInt32BE(off); + console.log(' keyId:', keyId.toString(16)); +} + +// Read payload to get actual strings +for (let i = 0; i < Math.min(10, entryCount); i++) { + const off = indexOffset + i * 16; + const keyId = buf.readUInt32BE(off); + const valId = buf.readUInt32BE(off + 4); + const offset = buf.readUInt32BE(off + 8); + const length = buf.readUInt32BE(off + 12); + + // Read the payload at offset + const valBuf = buf.slice(offset, offset + length); + const valStr = valBuf.toString('utf8').replace(/\0/g, ''); + + console.log(` [${i}] keyId:${keyId.toString(16).padStart(8,'0')} off:${offset} len:${length} val:"${valStr}"`); + + // If this is entry 1+, try to compute hash of the key + if (i > 0 && valStr.length > 0) { + console.log(` luci_sfh("${valStr}") = ${luci_sfh_hash(valStr).toString(16)}`); + console.log(` po2lmo_sfh("${valStr}") = ${po2lmo_sfh(valStr).toString(16)}`); + } +} diff --git a/tools/hash_test.js b/tools/hash_test.js new file mode 100644 index 00000000..993df92d --- /dev/null +++ b/tools/hash_test.js @@ -0,0 +1,26 @@ +// OpenWrt luci-core uses this hash (from lmo.c): +function lmo_hash(s) { + let h = 0; + for (let i = 0; i < s.length; i++) { + h += s.charCodeAt(i); + h = (h + (h << 10)) >>> 0; + h ^= h >>> 6; + } + h = (h + (h << 3)) >>> 0; + h ^= h >>> 11; + h = (h + (h << 15)) >>> 0; + return h >>> 0; +} +// Reference: entry 1 keyId=0x1545b51f +console.log('lmo_hash(Client):', lmo_hash('Client').toString(16), '(target: 1545b51f)'); +console.log('lmo_hash(Download):', lmo_hash('Download').toString(16)); +console.log('lmo_hash(Save):', lmo_hash('Save').toString(16)); +console.log('lmo_hash(Action):', lmo_hash('Action').toString(16)); +console.log('lmo_hash(Enabled):', lmo_hash('Enabled').toString(16)); +console.log('lmo_hash(General):', lmo_hash('General').toString(16)); +console.log('lmo_hash(Email):', lmo_hash('Email').toString(16)); +console.log('lmo_hash(Subject):', lmo_hash('Subject').toString(16)); +console.log('lmo_hash(Enable detailed logs):', lmo_hash('Enable detailed logs').toString(16)); +console.log('lmo_hash(Enable):', lmo_hash('Enable').toString(16)); +console.log('lmo_hash(Close):', lmo_hash('Close').toString(16)); +console.log('lmo_hash(Save & Apply):', lmo_hash('Save & Apply').toString(16)); diff --git a/tools/po2lmo.js b/tools/po2lmo.js index 4e702422..1bae795c 100644 --- a/tools/po2lmo.js +++ b/tools/po2lmo.js @@ -3,31 +3,58 @@ const fs = require('fs'); -// Paul Hsieh's SuperFastHash -function sfh_hash(data) { - let hash = 0; - let i = data.length; - let k; - while (i >= 4) { - k = (data.charCodeAt(i - 4) & 0xff) | - ((data.charCodeAt(i - 3) & 0xff) << 8) | - ((data.charCodeAt(i - 2) & 0xff) << 16) | - ((data.charCodeAt(i - 1) & 0xff) << 24); - k = (k * 0x5bd1e995) >>> 0; - k ^= k >>> 24; - k = (k * 0x5bd1e995) >>> 0; - hash = (hash * 0x5bd1e995 + k) >>> 0; - i -= 4; +// OpenWrt LuCI SuperFastHash (from LMO.md / template_lmo.c) +// sfh_get16 reads uint16 in little-endian +function sfh_get16(d, off) { + return (d[off] & 0xff) + ((d[off + 1] & 0xff) << 8); +} + +function sfh_hash(str) { + const data = Buffer.from(str, 'utf8'); + const len = data.length; + if (len <= 0) return 0; + + let hash = len; + let rem = len & 3; + let offset = 0; + const words = len >> 2; + + // Main loop + for (let i = 0; i < words; i++) { + hash += sfh_get16(data, offset); + let tmp = (sfh_get16(data, offset + 2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + offset += 4; + hash += hash >>> 11; } - switch (i) { - case 3: hash ^= (data.charCodeAt(i - 3) & 0xff) << 16; - case 2: hash ^= (data.charCodeAt(i - 2) & 0xff) << 8; - case 1: hash ^= (data.charCodeAt(i - 1) & 0xff); - hash = (hash * 0x5bd1e995) >>> 0; + + // Handle end cases + switch (rem) { + case 3: + hash += sfh_get16(data, offset); + hash ^= hash << 16; + hash ^= data[offset + 2] << 18; + hash += hash >>> 11; + break; + case 2: + hash += sfh_get16(data, offset); + hash ^= hash << 11; + hash += hash >>> 17; + break; + case 1: + hash += data[offset]; + hash ^= hash << 10; + hash += hash >>> 1; } - hash ^= hash >>> 13; - hash = (hash * 0x5bd1e995) >>> 0; - hash ^= hash >>> 15; + + // Force "avalanching" of final 127 bits + hash ^= hash << 3; + hash += hash >>> 5; + hash ^= hash << 4; + hash += hash >>> 17; + hash ^= hash << 25; + hash += hash >>> 6; + return hash >>> 0; } @@ -41,7 +68,6 @@ function parsePo(content) { let state = null; let msgid = ''; let msgstr = ''; - let key = ''; function flush() { if (state === 'msgstr' && msgid !== '' && msgstr !== '') { @@ -83,23 +109,15 @@ function compileLmo(entries) { const keyId = sfh_hash(entry.key); const valId = sfh_hash(entry.value); - // Write value to payload const paddedLen = valBuf.length + pad4(valBuf.length); const padded = Buffer.alloc(paddedLen, 0); valBuf.copy(padded); payload.push(padded); - index.push({ - keyId, - valId, - offset, - length: valBuf.length, - }); - + index.push({ keyId, valId, offset, length: valBuf.length }); offset += paddedLen; } - // Build index section const indexBuf = Buffer.alloc(index.length * 16); for (let i = 0; i < index.length; i++) { indexBuf.writeUInt32BE(index[i].keyId, i * 16); @@ -108,7 +126,6 @@ function compileLmo(entries) { indexBuf.writeUInt32BE(index[i].length, i * 16 + 12); } - // Total: payload + index + 4 bytes for index offset const payloadData = Buffer.concat(payload); const totalLen = payloadData.length + indexBuf.length + 4; const out = Buffer.alloc(totalLen); @@ -119,7 +136,6 @@ function compileLmo(entries) { return out; } -// Main const [input, output] = process.argv.slice(2); if (!input || !output) { console.error('Usage: node po2lmo.js input.po output.lmo'); diff --git a/tools/test_hash.js b/tools/test_hash.js new file mode 100644 index 00000000..77b448a9 --- /dev/null +++ b/tools/test_hash.js @@ -0,0 +1,26 @@ +function sfh_hash(data) { + let hash = 0; + let i = data.length; + let k; + while (i >= 4) { + k = (data.charCodeAt(i - 4) & 0xff) | ((data.charCodeAt(i - 3) & 0xff) << 8) | ((data.charCodeAt(i - 2) & 0xff) << 16) | ((data.charCodeAt(i - 1) & 0xff) << 24); + k = (k * 0x5bd1e995) >>> 0; + k ^= k >>> 24; + k = (k * 0x5bd1e995) >>> 0; + hash = (hash * 0x5bd1e995 + k) >>> 0; + i -= 4; + } + switch (i) { + case 3: hash ^= (data.charCodeAt(i - 3) & 0xff) << 16; + case 2: hash ^= (data.charCodeAt(i - 2) & 0xff) << 8; + case 1: hash ^= (data.charCodeAt(i - 1) & 0xff); + hash = (hash * 0x5bd1e995) >>> 0; + } + hash ^= hash >>> 13; + hash = (hash * 0x5bd1e995) >>> 0; + hash ^= hash >>> 15; + return hash >>> 0; +} +console.log('Empty:', sfh_hash('').toString(16)); +console.log('Plural-Forms:', sfh_hash('Plural-Forms').toString(16)); +console.log('Test str:', sfh_hash('Hello').toString(16)); diff --git a/tools/test_ref.lmo b/tools/test_ref.lmo new file mode 100644 index 0000000000000000000000000000000000000000..ea7c0349c6985cb3a78cccd88777fd411b0caa6c GIT binary patch literal 1036 zcmZuvOK4L;6rBWDQZQDfYO0-}QbZ!CyOyY^6&DQ%1u;ma-3WzHq*_F@&?Kd)A_gUB z6dFG(7a}j8XMW9#8x@0#JE$PI5#0!_2!cC7&z+eT3kD`<&YgSDoICf8pN~&YjK(iS z_V-J(jf~4sMDFVsA{>^~B2WKOSxu8e6)I3w@0#Swa9B{5T&k&rN>Y)k*k{S3mby+2 zY9cdx=P#$`9c8mRe3PVtFl+Fm;H{qX(E}M@7C)Wq=OVpW`7w2+e0|I!Lz3d@J738~AT8<+2XeF!0buP-PR8lF3HOUhL;ppgC zTym-Q!>?r=As3p<=MUJiHq}^(aSeCksB2j1mH1;=SRhBxZFJC3H&xp9pa)t&=lX%4 zTNCJk&x_nq1F^Jz4&>;mIGDbNu5?L>pFqv=DA4df`gm)|=BWTH(E88ZbC|{qPn@l( zSspCi#Z4`&3Z7&h(bb;{x?lC%x@o}eLBxmk8LMNKgJ?#}`{r%oM887`K9fmAMvuT}SR61XROX3X{@euKxJ z4KMcD^Y_5DuYP>EWcgL#f%ehfPlo&Az@D#5@d> 2; + for (let i = 0; i < words; i++) { + hash += sfh_get16(data, offset); + let tmp = (sfh_get16(data, offset + 2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + offset += 4; + hash += hash >>> 11; + } + switch (rem) { + case 3: + hash += sfh_get16(data, offset); + hash ^= hash << 16; + hash ^= data[offset + 2] << 18; + hash += hash >>> 11; + break; + case 2: + hash += sfh_get16(data, offset); + hash ^= hash << 11; + hash += hash >>> 17; + break; + case 1: + hash += data[offset]; + hash ^= hash << 10; + hash += hash >>> 1; + } + hash ^= hash << 3; + hash += hash >>> 5; + hash ^= hash << 4; + hash += hash >>> 17; + hash ^= hash << 25; + hash += hash >>> 6; + return hash >>> 0; +} + +// Reference: adblock.bg.lmo entry 1 keyId=0x1545b51f +// msgid for "Клиент" in adblock bg is likely "Client" +console.log('sfh_hash(Client):', sfh_hash('Client').toString(16), '(target: 1545b51f)'); +console.log('sfh_hash(Save):', sfh_hash('Save').toString(16)); +console.log('sfh_hash(Close):', sfh_hash('Close').toString(16)); +console.log('sfh_hash(Enable):', sfh_hash('Enable').toString(16)); +console.log('sfh_hash(Enabled):', sfh_hash('Enabled').toString(16)); +console.log('sfh_hash(General settings):', sfh_hash('General settings').toString(16)); +console.log('sfh_hash(Action):', sfh_hash('Action').toString(16)); +console.log('sfh_hash(Download):', sfh_hash('Download').toString(16)); +console.log('sfh_hash(Email):', sfh_hash('Email').toString(16)); +console.log('sfh_hash(Subject):', sfh_hash('Subject').toString(16)); +console.log('sfh_hash(Enable detailed logs):', sfh_hash('Enable detailed logs').toString(16)); +console.log('sfh_hash(Save & Apply):', sfh_hash('Save & Apply').toString(16)); + +// Now test with some of our tachyon strings +console.log('\nTachyon strings:'); +console.log('sfh_hash(Periodically checks if the proxy is responding. Restarts Tachyon after consecutive failures.):', sfh_hash('Periodically checks if the proxy is responding. Restarts Tachyon after consecutive failures.').toString(16)); +console.log('sfh_hash(Enable Proxy Health Monitor):', sfh_hash('Enable Proxy Health Monitor').toString(16));