VulpesOS/gaia/apps/email/js/ext/a64.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

188 lines
6.3 KiB
JavaScript
Executable file

// asuth.
/**
* ASCII-encoding tricks, particularly ordered-base64 encoding for
* lexicographically ordered things like IndexedDB or 64-bit number support that
* we can't use JS numbers for.
*
* The math logic is by me (asuth); hopefully it's not too embarassing.
**/
define(
[
'exports'
],
function(
exports
) {
/**
* A lexicographically ordered base64 encoding. Our two extra characters are {
* and } because they are at the top of the ordering space and have a clear (to
* JS coders) ordering which makes it tractable to eyeball an encoded value and
* not be completely confused/misled.
*/
var ORDERED_ARBITRARY_BASE64_CHARS = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '{', '}'
];
/**
* Zero padding to get us up to the maximum encoding length of a 64-bit value in
* our encoding (11) or for decimal re-conversion (16).
*/
var ZERO_PADDING = '0000000000000000';
/**
* Encode a JS int in our base64 encoding.
*/
function encodeInt(v, padTo) {
var sbits = [];
do {
// note: bitwise ops are 32-bit only.
// so, this is fine:
sbits.push(ORDERED_ARBITRARY_BASE64_CHARS[v & 0x3f]);
// but this can't be >>> 6 and has to be a divide.
v = Math.floor(v / 64);
} while (v > 0);
sbits.reverse();
var estr = sbits.join('');
if (padTo && estr.length < padTo)
return ZERO_PADDING.substring(0, padTo - estr.length) + estr;
return estr;
}
exports.encodeInt = encodeInt;
/**
* 10^14 >> 14 so that its 'lowest' binary 1 ends up in the one's place. It
* is encoded in 33 bits itself.
*/
var E10_14_RSH_14 = Math.pow(10, 14) / Math.pow(2, 14),
P2_14 = Math.pow(2, 14),
P2_22 = Math.pow(2, 22),
P2_32 = Math.pow(2, 32),
P2_36 = Math.pow(2, 36),
MASK32 = 0xffffffff;
/**
* Convert a decimal uint64 string to a compact string representation that can
* be compared using our helper method `cmpUI64`. We could do direct straight
* string comparison if we were willing to pad all strings out to 11 characters,
* but that's a lot of overhead considering that we expect a lot of our values
* to be muuuuch smaller. (Appropriate padding can be requested for cases
* where the ordering is explicitly desired, like IndexedDB keys. Just only
* request as many bits as you really need!)
*
* JS can handle up to 2^53 reliably which means that for numbers larger than
* that we will have to do a second parse. For that to work (easily), we need
* to pick a power of 10 to cut at where the smallest '1' in its binary encoding
* is at least in the 14th bit so we can pre-shift off 13 bits so when we
* multiply by 10 we don't go floating point, as it were. (We also need to add
* in the relevant bits from the lower parse appropriately shifted.)
*/
exports.parseUI64 = function p(s, padTo) {
// 2^53 is 16 digits long, so any string shorter than that can be handled
// by the built-in logic.
if (s.length < 16) {
return encodeInt(parseInt(s, 10));
}
var lowParse = parseInt(s.substring(s.length - 14), 10),
highParse = parseInt(s.substring(0, s.length - 14), 10),
// multiply the high parse by our scaled power of 10
rawHighBits = highParse * E10_14_RSH_14;
// Now lowParse's low 14 bits are valid, but everything above that needs to
// be mixed (by addition) with rawHighBits. We'll mix in 22 bits from
// rawHighBits to get lowBits to 36 useful bits. The main thing is to lop off
// the higher bits in rawHighBits that we don't want so they don't go float.
// We do want the 37rd bit if there was addition overflow to carry to the
// upper calculation.
var lowBitsAdded = (((rawHighBits % P2_36) * P2_14) % P2_36 +
lowParse % P2_36),
lowBits = lowBitsAdded % P2_36,
overflow = Math.floor(lowBitsAdded / P2_36) % 2;
// We can lop off the low 22-bits of the high bits (since lowBits is taking
// care of that) and combine that with the bits of low above 36.
var highBits = Math.floor(rawHighBits / P2_22) +
Math.floor(lowParse / P2_36) + overflow;
var outStr = encodeInt(highBits) + encodeInt(lowBits, 6);
if (padTo && outStr.length < padTo)
return ZERO_PADDING.substring(0, padTo - outStr.length) + outStr;
return outStr;
};
exports.cmpUI64 = function(a, b) {
// longer equals bigger!
var c = a.length - b.length;
if (c !== 0)
return c;
if (a < b)
return -1;
else if (a > b)
return 1;
return 0;
};
/**
* Convert the output of `parseUI64` back into a decimal string.
*/
exports.decodeUI64 = function d(es) {
var iNonZero = 0;
for (;es.charCodeAt(iNonZero) === 48; iNonZero++) {
}
if (iNonZero)
es = es.substring(iNonZero);
var v, i;
// 8 characters is 48 bits, JS can do that internally.
if (es.length <= 8) {
v = 0;
for (i = 0; i < es.length; i++) {
v = v * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(es[i]);
}
return v.toString(10);
}
// upper-string gets 28 bits (that could hold 30), lower-string gets 36 bits.
// This is how we did things in encoding is why.
var ues = es.substring(0, es.length - 6), uv = 0,
les = es.substring(es.length - 6), lv = 0;
for (i = 0; i < ues.length; i++) {
uv = uv * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(ues[i]);
}
for (i = 0; i < les.length; i++) {
lv = lv * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(les[i]);
}
// Do the division to figure out the "high" string from our encoding (a whole
// number.) Then subtract that whole number off our effective number, leaving
// us dealing with <53 bits so we can just hand it off to the JS engine.
var rsh14val = (uv * P2_22 + Math.floor(lv / P2_14)),
uraw = rsh14val / E10_14_RSH_14,
udv = Math.floor(uraw),
uds = udv.toString();
var rsh14Leftover = rsh14val - udv * E10_14_RSH_14,
lowBitsRemoved = rsh14Leftover * P2_14 + lv % P2_14;
var lds = lowBitsRemoved.toString();
if (lds.length < 14)
lds = ZERO_PADDING.substring(0, 14 - lds.length) + lds;
return uds + lds;
};
//d(p('10000000000000000'));
//d(p('18014398509481984'));
//d(p('1171221845949812801'));
}); // end define