fix(po2lmo): исправлена хеш-функция sfh_hash в po2lmo.js
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// LuCI uses this specific SuperFastHash from OpenWrt
|
||||||
|
// Let me try to match it against the reference lmo
|
||||||
|
// adblock.bg.lmo entry 1: keyId=0x1545b51f
|
||||||
|
// We need to know what string produces this hash
|
||||||
|
|
||||||
|
// Try the OpenWrt luci sfh_hash
|
||||||
|
function luci_sfh_hash(data) {
|
||||||
|
let hash = 0;
|
||||||
|
const len = data.length;
|
||||||
|
let i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
hash += data.charCodeAt(i);
|
||||||
|
hash = (hash << 10) + hash;
|
||||||
|
hash ^= hash >>> 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)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
+51
-35
@@ -3,31 +3,58 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
// Paul Hsieh's SuperFastHash
|
// OpenWrt LuCI SuperFastHash (from LMO.md / template_lmo.c)
|
||||||
function sfh_hash(data) {
|
// sfh_get16 reads uint16 in little-endian
|
||||||
let hash = 0;
|
function sfh_get16(d, off) {
|
||||||
let i = data.length;
|
return (d[off] & 0xff) + ((d[off + 1] & 0xff) << 8);
|
||||||
let k;
|
}
|
||||||
while (i >= 4) {
|
|
||||||
k = (data.charCodeAt(i - 4) & 0xff) |
|
function sfh_hash(str) {
|
||||||
((data.charCodeAt(i - 3) & 0xff) << 8) |
|
const data = Buffer.from(str, 'utf8');
|
||||||
((data.charCodeAt(i - 2) & 0xff) << 16) |
|
const len = data.length;
|
||||||
((data.charCodeAt(i - 1) & 0xff) << 24);
|
if (len <= 0) return 0;
|
||||||
k = (k * 0x5bd1e995) >>> 0;
|
|
||||||
k ^= k >>> 24;
|
let hash = len;
|
||||||
k = (k * 0x5bd1e995) >>> 0;
|
let rem = len & 3;
|
||||||
hash = (hash * 0x5bd1e995 + k) >>> 0;
|
let offset = 0;
|
||||||
i -= 4;
|
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;
|
// Handle end cases
|
||||||
case 2: hash ^= (data.charCodeAt(i - 2) & 0xff) << 8;
|
switch (rem) {
|
||||||
case 1: hash ^= (data.charCodeAt(i - 1) & 0xff);
|
case 3:
|
||||||
hash = (hash * 0x5bd1e995) >>> 0;
|
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;
|
// Force "avalanching" of final 127 bits
|
||||||
hash ^= hash >>> 15;
|
hash ^= hash << 3;
|
||||||
|
hash += hash >>> 5;
|
||||||
|
hash ^= hash << 4;
|
||||||
|
hash += hash >>> 17;
|
||||||
|
hash ^= hash << 25;
|
||||||
|
hash += hash >>> 6;
|
||||||
|
|
||||||
return hash >>> 0;
|
return hash >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +68,6 @@ function parsePo(content) {
|
|||||||
let state = null;
|
let state = null;
|
||||||
let msgid = '';
|
let msgid = '';
|
||||||
let msgstr = '';
|
let msgstr = '';
|
||||||
let key = '';
|
|
||||||
|
|
||||||
function flush() {
|
function flush() {
|
||||||
if (state === 'msgstr' && msgid !== '' && msgstr !== '') {
|
if (state === 'msgstr' && msgid !== '' && msgstr !== '') {
|
||||||
@@ -83,23 +109,15 @@ function compileLmo(entries) {
|
|||||||
const keyId = sfh_hash(entry.key);
|
const keyId = sfh_hash(entry.key);
|
||||||
const valId = sfh_hash(entry.value);
|
const valId = sfh_hash(entry.value);
|
||||||
|
|
||||||
// Write value to payload
|
|
||||||
const paddedLen = valBuf.length + pad4(valBuf.length);
|
const paddedLen = valBuf.length + pad4(valBuf.length);
|
||||||
const padded = Buffer.alloc(paddedLen, 0);
|
const padded = Buffer.alloc(paddedLen, 0);
|
||||||
valBuf.copy(padded);
|
valBuf.copy(padded);
|
||||||
payload.push(padded);
|
payload.push(padded);
|
||||||
|
|
||||||
index.push({
|
index.push({ keyId, valId, offset, length: valBuf.length });
|
||||||
keyId,
|
|
||||||
valId,
|
|
||||||
offset,
|
|
||||||
length: valBuf.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
offset += paddedLen;
|
offset += paddedLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build index section
|
|
||||||
const indexBuf = Buffer.alloc(index.length * 16);
|
const indexBuf = Buffer.alloc(index.length * 16);
|
||||||
for (let i = 0; i < index.length; i++) {
|
for (let i = 0; i < index.length; i++) {
|
||||||
indexBuf.writeUInt32BE(index[i].keyId, i * 16);
|
indexBuf.writeUInt32BE(index[i].keyId, i * 16);
|
||||||
@@ -108,7 +126,6 @@ function compileLmo(entries) {
|
|||||||
indexBuf.writeUInt32BE(index[i].length, i * 16 + 12);
|
indexBuf.writeUInt32BE(index[i].length, i * 16 + 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Total: payload + index + 4 bytes for index offset
|
|
||||||
const payloadData = Buffer.concat(payload);
|
const payloadData = Buffer.concat(payload);
|
||||||
const totalLen = payloadData.length + indexBuf.length + 4;
|
const totalLen = payloadData.length + indexBuf.length + 4;
|
||||||
const out = Buffer.alloc(totalLen);
|
const out = Buffer.alloc(totalLen);
|
||||||
@@ -119,7 +136,6 @@ function compileLmo(entries) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main
|
|
||||||
const [input, output] = process.argv.slice(2);
|
const [input, output] = process.argv.slice(2);
|
||||||
if (!input || !output) {
|
if (!input || !output) {
|
||||||
console.error('Usage: node po2lmo.js input.po output.lmo');
|
console.error('Usage: node po2lmo.js input.po output.lmo');
|
||||||
|
|||||||
@@ -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));
|
||||||
Binary file not shown.
@@ -0,0 +1,66 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
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;
|
||||||
|
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));
|
||||||
Reference in New Issue
Block a user