mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Merge pull request #1530 from adiwajshing/invalid-qr-patch
Invalid QR Patch
This commit is contained in:
@@ -1,587 +0,0 @@
|
||||
const { hexAt, hexLongIsNegative, hexLongToHex, negateHexLong, NUM_HEX_IN_LONG } = require("./HexHelper");
|
||||
const { inflateSync } = require("zlib")
|
||||
|
||||
var l = "",
|
||||
d = 0;
|
||||
|
||||
const i = 65533,
|
||||
n = new Uint8Array(10),
|
||||
s = new Uint8Array(0);
|
||||
|
||||
function u(e) {
|
||||
if (e === l) return d;
|
||||
for (var t = e.length, r = 0, a = 0; a < t; a++) {
|
||||
var i = e.charCodeAt(a);
|
||||
if (i < 128) r++;
|
||||
else if (i < 2048) r += 2;
|
||||
else if (i < 55296 || (57344 <= i && i <= 65535)) r += 3;
|
||||
else if (55296 <= i && i < 56320 && a + 1 !== t) {
|
||||
var n = e.charCodeAt(a + 1);
|
||||
56320 <= n && n < 57344 ? (a++, (r += 4)) : (r += 3);
|
||||
} else r += 3;
|
||||
}
|
||||
return (l = e), (d = r);
|
||||
}
|
||||
function c(e, t, r) {
|
||||
var a = t >> 21;
|
||||
if (e) {
|
||||
var i = Boolean(2097151 & t || r);
|
||||
return 0 === a || (-1 === a && i);
|
||||
}
|
||||
return 0 === a;
|
||||
}
|
||||
function p(e, t, r, a, i = undefined) {
|
||||
return e.readWithViewParser(t, r, a, i);
|
||||
}
|
||||
function f(e, t, r, a = undefined, i = undefined) {
|
||||
return e.readWithBytesParser(t, r, a, i);
|
||||
}
|
||||
function h(e, t, r, a) {
|
||||
return a ? e.getInt8(t) : e.getUint8(t);
|
||||
}
|
||||
function _(e, t, r, a) {
|
||||
return e.getUint16(t, a);
|
||||
}
|
||||
function m(e, t, r, a) {
|
||||
return e.getInt32(t, a);
|
||||
}
|
||||
function g(e, t, r, a) {
|
||||
return e.getUint32(t, a);
|
||||
}
|
||||
function v(e, t, r, a, i) {
|
||||
return a(e.getInt32(i ? t + 4 : t, i), e.getInt32(i ? t : t + 4, i));
|
||||
}
|
||||
function y(e, t, r, a) {
|
||||
return e.getFloat32(t, a);
|
||||
}
|
||||
function E(e, t, r, a) {
|
||||
return e.getFloat64(t, a);
|
||||
}
|
||||
function S(e, t, r, a) {
|
||||
for (var i = Math.min(a, 10), n = 0, s = 128; n < i && 128 & s; )
|
||||
s = e[t + n++];
|
||||
if (10 === n && s > 1) throw new Error("ParseError: varint exceeds 64 bits");
|
||||
return 128 & s ? n + 1 : n;
|
||||
}
|
||||
function T(e, t, r, a) {
|
||||
var i = 0,
|
||||
n = 0,
|
||||
s = r;
|
||||
10 === r && (n = 1 & e[t + --s]);
|
||||
for (var o = s - 1; o >= 0; o--)
|
||||
(i = (i << 7) | (n >>> 25)), (n = (n << 7) | (127 & e[t + o]));
|
||||
return a(i, n);
|
||||
}
|
||||
function A(e, t, r) {
|
||||
var a = t + e.byteOffset,
|
||||
i = e.buffer;
|
||||
return 0 === a && r === i.byteLength ? i : i.slice(a, a + r);
|
||||
}
|
||||
function b(e, t, r) {
|
||||
return e.subarray(t, t + r);
|
||||
}
|
||||
function C(e, t, r) {
|
||||
for (var a = t + r, n = [], s = null, o = t; o < a; o++) {
|
||||
n.length > 5e3 &&
|
||||
(s || (s = []), s.push(String.fromCharCode.apply(String, n)), (n = []));
|
||||
var l = 0 | e[o];
|
||||
if (0 == (128 & l)) n.push(l);
|
||||
else if (192 == (224 & l)) {
|
||||
var d = H(e, o + 1, a);
|
||||
if (d) {
|
||||
o++;
|
||||
var u = ((31 & l) << 6) | (63 & d);
|
||||
u >= 128 ? n.push(u) : n.push(i);
|
||||
} else n.push(i);
|
||||
} else if (224 == (240 & l)) {
|
||||
var c = H(e, o + 1, a),
|
||||
p = H(e, o + 2, a);
|
||||
if (c && p) {
|
||||
o += 2;
|
||||
var f = ((15 & l) << 12) | ((63 & c) << 6) | (63 & p);
|
||||
f >= 2048 && !(55296 <= f && f < 57344) ? n.push(f) : n.push(i);
|
||||
} else c ? (o++, n.push(i)) : n.push(i);
|
||||
} else if (240 == (248 & l)) {
|
||||
var h = H(e, o + 1, a),
|
||||
_ = H(e, o + 2, a),
|
||||
m = H(e, o + 3, a);
|
||||
if (h && _ && m) {
|
||||
o += 3;
|
||||
var g = ((7 & l) << 18) | ((63 & h) << 12) | ((63 & _) << 6) | (63 & m);
|
||||
if (g >= 65536 && g <= 1114111) {
|
||||
var v = g - 65536;
|
||||
n.push(55296 | (v >> 10), 56320 | (1023 & v));
|
||||
} else n.push(i);
|
||||
} else h && _ ? ((o += 2), n.push(i)) : h ? (o++, n.push(i)) : n.push(i);
|
||||
} else n.push(i);
|
||||
}
|
||||
var y = String.fromCharCode.apply(String, n);
|
||||
return s ? (s.push(y), s.join("")) : y;
|
||||
}
|
||||
function P(e, t, r, a, i) {
|
||||
return e.writeToView(t, r, a, i);
|
||||
}
|
||||
function O(e, t, r, a, i = undefined) {
|
||||
return e.writeToBytes(t, r, a, i);
|
||||
}
|
||||
function M(e, t, r, a) {
|
||||
e[t] = a;
|
||||
}
|
||||
function w(e, t, r, a, i) {
|
||||
e.setUint16(t, a, i);
|
||||
}
|
||||
function I(e, t, r, a, i) {
|
||||
e.setInt16(t, a, i);
|
||||
}
|
||||
function R(e, t, r, a, i) {
|
||||
e.setUint32(t, a, i);
|
||||
}
|
||||
function D(e, t, r, a, i) {
|
||||
e.setInt32(t, a, i);
|
||||
}
|
||||
function N(e, t, r, a, i) {
|
||||
var n = a < 0,
|
||||
s = n ? -a : a,
|
||||
o = Math.floor(s / 4294967296),
|
||||
l = s - 4294967296 * o;
|
||||
n && ((o = ~o), 0 === l ? o++ : (l = -l)),
|
||||
e.setUint32(i ? t + 4 : t, o, i),
|
||||
e.setUint32(i ? t : t + 4, l, i);
|
||||
}
|
||||
function L(e, t, r, a, i) {
|
||||
e.setFloat32(t, a, i);
|
||||
}
|
||||
function k(e, t, r, a, i) {
|
||||
e.setFloat64(t, a, i);
|
||||
}
|
||||
function U(e, t, r, a, i) {
|
||||
for (var n = a, s = i, o = t + r - 1, l = t; l < o; l++)
|
||||
(e[l] = 128 | (127 & s)), (s = (n << 25) | (s >>> 7)), (n >>>= 7);
|
||||
e[o] = s;
|
||||
}
|
||||
function G(e, t, r, a) {
|
||||
for (var i = t, n = a.length, s = 0; s < n; s++) {
|
||||
var o = a.charCodeAt(s);
|
||||
if (o < 128) e[i++] = o;
|
||||
else if (o < 2048) (e[i++] = 192 | (o >> 6)), (e[i++] = 128 | (63 & o));
|
||||
else if (o < 55296 || 57344 <= o)
|
||||
(e[i++] = 224 | (o >> 12)),
|
||||
(e[i++] = 128 | ((o >> 6) & 63)),
|
||||
(e[i++] = 128 | (63 & o));
|
||||
else if (55296 <= o && o < 56320 && s + 1 !== n) {
|
||||
var l = a.charCodeAt(s + 1);
|
||||
if (56320 <= l && l < 57344) {
|
||||
s++;
|
||||
var d = 65536 + (((1023 & o) << 10) | (1023 & l));
|
||||
(e[i++] = 240 | (d >> 18)),
|
||||
(e[i++] = 128 | ((d >> 12) & 63)),
|
||||
(e[i++] = 128 | ((d >> 6) & 63)),
|
||||
(e[i++] = 128 | (63 & d));
|
||||
} else (e[i++] = 239), (e[i++] = 191), (e[i++] = 189);
|
||||
} else (e[i++] = 239), (e[i++] = 191), (e[i++] = 189);
|
||||
}
|
||||
}
|
||||
function F(e, t, r, i, n) {
|
||||
for (
|
||||
var s = hexLongIsNegative(i),
|
||||
o = hexLongToHex(i),
|
||||
l = 0,
|
||||
d = 0,
|
||||
u = 0;
|
||||
u < 16;
|
||||
u++
|
||||
)
|
||||
(l = (l << 4) | (d >>> 28)), (d = (d << 4) | hexAt(o, u));
|
||||
s && ((l = ~l), 0 === d ? l++ : (d = -d)),
|
||||
e.setUint32(n ? t + 4 : t, l, n),
|
||||
e.setUint32(n ? t : t + 4, d, n);
|
||||
}
|
||||
function x(e, t, r, a) {
|
||||
for (var i = 0; i < r; i++) e[t + i] = a[i];
|
||||
}
|
||||
function B(e, t) {
|
||||
var r, a;
|
||||
for (e ? ((r = 5), (a = e >>> 3)) : ((r = 1), (a = t >>> 7)); a; )
|
||||
r++, (a >>>= 7);
|
||||
return r;
|
||||
}
|
||||
function Y(e, t, r, a) {
|
||||
if ("number" != typeof e || e != e || Math.floor(e) !== e || e < t || e >= r) {
|
||||
console.trace('here')
|
||||
throw new TypeError(
|
||||
"string" == typeof e
|
||||
? `WriteError: string "${e}" is not a valid ${a}`
|
||||
: `WriteError: ${String(e)} is not a valid ${a}`
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
function K(e, t, r) {
|
||||
var a =
|
||||
4294967296 * (t >= 0 || e ? t : 4294967296 + t) +
|
||||
(r >= 0 ? r : 4294967296 + r);
|
||||
if (!c(e, t, r))
|
||||
throw new Error(`ReadError: integer exceeded 53 bits (${a})`);
|
||||
return a;
|
||||
}
|
||||
function j(e, t) {
|
||||
return K(!0, e, t);
|
||||
}
|
||||
function W(e, t) {
|
||||
return K(!1, e, t);
|
||||
}
|
||||
function H(e, t, r) {
|
||||
if (t >= r) return 0;
|
||||
var a = 0 | e[t];
|
||||
return 128 == (192 & a) ? a : 0;
|
||||
}
|
||||
|
||||
module.exports.numUtf8Bytes = u;
|
||||
module.exports.longFitsInDouble = c;
|
||||
module.exports.parseInt64OrThrow = j;
|
||||
module.exports.parseUint64OrThrow = W;
|
||||
|
||||
class Binary {
|
||||
/** @type {Uint8Array} */
|
||||
buffer;
|
||||
readEndIndex;
|
||||
writeIndex;
|
||||
bytesTrashed = 0;
|
||||
earliestIndex = 0;
|
||||
readIndex = 0;
|
||||
/** @type {DataView} */
|
||||
view = null;
|
||||
littleEndian = false;
|
||||
hiddenReads = 0;
|
||||
hiddenWrites = 0;
|
||||
|
||||
constructor(data = new Uint8Array(0), littleEndian = false) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
this.buffer = new Uint8Array(data);
|
||||
this.readEndIndex = data.byteLength;
|
||||
this.writeIndex = data.byteLength;
|
||||
}
|
||||
|
||||
if (data instanceof Uint8Array) {
|
||||
this.buffer = data;
|
||||
this.readEndIndex = data.length;
|
||||
this.writeIndex = data.length;
|
||||
}
|
||||
|
||||
this.littleEndian = littleEndian;
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.readEndIndex - this.readIndex;
|
||||
}
|
||||
|
||||
peek(e, t = undefined) {
|
||||
this.hiddenReads++;
|
||||
|
||||
const r = this.readIndex;
|
||||
const a = this.bytesTrashed;
|
||||
|
||||
try {
|
||||
return e(this, t);
|
||||
} finally {
|
||||
this.hiddenReads--, (this.readIndex = r - (this.bytesTrashed - a));
|
||||
}
|
||||
}
|
||||
|
||||
advance(e) {
|
||||
this.shiftReadOrThrow(e);
|
||||
}
|
||||
|
||||
readWithViewParser(e, t, r, a) {
|
||||
return t(this.getView(), this.shiftReadOrThrow(e), e, r, a);
|
||||
}
|
||||
|
||||
readWithBytesParser(e, t, r, a) {
|
||||
return t(this.buffer, this.shiftReadOrThrow(e), e, r, a);
|
||||
}
|
||||
|
||||
readUint8() {
|
||||
//return this.readWithViewParser(1, h, false)
|
||||
return p(this, 1, h, !1);
|
||||
}
|
||||
readInt8() {
|
||||
return p(this, 1, h, !0);
|
||||
}
|
||||
readUint16(e = this.littleEndian) {
|
||||
return p(this, 2, _, e);
|
||||
}
|
||||
readInt32(e = this.littleEndian) {
|
||||
return p(this, 4, m, e);
|
||||
}
|
||||
readUint32(e = this.littleEndian) {
|
||||
return p(this, 4, g, e);
|
||||
}
|
||||
readInt64(e = this.littleEndian) {
|
||||
return p(this, 8, v, j, e);
|
||||
}
|
||||
readUint64(e = this.littleEndian) {
|
||||
return p(this, 8, v, W, e);
|
||||
}
|
||||
readLong(e, t = this.littleEndian) {
|
||||
return p(this, 8, v, e, t);
|
||||
}
|
||||
readFloat32(e = this.littleEndian) {
|
||||
return p(this, 4, y, e);
|
||||
}
|
||||
readFloat64(e = this.littleEndian) {
|
||||
return p(this, 8, E, e);
|
||||
}
|
||||
readVarInt(e) {
|
||||
var t = f(this, 0, S, this.size());
|
||||
return f(this, t, T, e);
|
||||
}
|
||||
readBuffer(e = this.size()) {
|
||||
return 0 === e ? new ArrayBuffer(0) : f(this, e, A);
|
||||
}
|
||||
readByteArray(e = this.size()) {
|
||||
return 0 === e ? new Uint8Array(0) : f(this, e, b);
|
||||
}
|
||||
readBinary(e = this.size(), t = this.littleEndian) {
|
||||
if (0 === e) return new Binary(void 0, t);
|
||||
var r = f(this, e, b);
|
||||
return new Binary(r, t);
|
||||
}
|
||||
indexOf(e) {
|
||||
if (0 === e.length) return 0;
|
||||
for (
|
||||
var t = this.buffer,
|
||||
r = this.readEndIndex,
|
||||
a = this.readIndex,
|
||||
i = 0,
|
||||
n = a,
|
||||
s = a;
|
||||
s < r;
|
||||
s++
|
||||
)
|
||||
if (t[s] === e[i]) {
|
||||
if ((0 === i && (n = s), ++i === e.byteLength))
|
||||
return s - a - e.byteLength + 1;
|
||||
} else i > 0 && ((i = 0), (s = n));
|
||||
return -1;
|
||||
1;
|
||||
}
|
||||
readString(e) {
|
||||
return f(this, e, C);
|
||||
}
|
||||
ensureCapacity(e) {
|
||||
this.maybeReallocate(this.readIndex + e);
|
||||
}
|
||||
ensureAdditionalCapacity(e) {
|
||||
this.maybeReallocate(this.writeIndex + e);
|
||||
}
|
||||
writeToView(e, t, r, a) {
|
||||
var i = this.shiftWriteMaybeReallocate(e);
|
||||
return t(this.getView(), i, e, r, a);
|
||||
}
|
||||
writeToBytes(e, t, r, a) {
|
||||
var i = this.shiftWriteMaybeReallocate(e);
|
||||
return t(this.buffer, i, e, r, a);
|
||||
}
|
||||
write(...e) {
|
||||
for (var t = 0; t < e.length; t++) {
|
||||
var r = e[t];
|
||||
"string" == typeof r
|
||||
? this.writeString(r)
|
||||
: "number" == typeof r
|
||||
? this.writeUint8(r)
|
||||
: r instanceof Binary
|
||||
? this.writeBinary(r)
|
||||
: r instanceof ArrayBuffer
|
||||
? this.writeBuffer(r)
|
||||
: r instanceof Uint8Array && this.writeByteArray(r);
|
||||
}
|
||||
}
|
||||
writeUint8(e) {
|
||||
Y(e, 0, 256, "uint8"), O(this, 1, M, e, !1);
|
||||
}
|
||||
writeInt8(e) {
|
||||
Y(e, -128, 128, "signed int8"), O(this, 1, M, e, !0);
|
||||
}
|
||||
writeUint16(e, t = this.littleEndian) {
|
||||
Y(e, 0, 65536, "uint16"), P(this, 2, w, e, t);
|
||||
}
|
||||
writeInt16(e, t = this.littleEndian) {
|
||||
Y(e, -32768, 32768, "signed int16"), P(this, 2, I, e, t);
|
||||
}
|
||||
writeUint32(e, t = this.littleEndian) {
|
||||
Y(e, 0, 4294967296, "uint32"), P(this, 4, R, e, t);
|
||||
}
|
||||
writeInt32(e, t = this.littleEndian) {
|
||||
Y(e, -2147483648, 2147483648, "signed int32"), P(this, 4, D, e, t);
|
||||
}
|
||||
writeUint64(e, t = this.littleEndian) {
|
||||
Y(e, 0, 0x10000000000000000, "uint64"), P(this, 8, N, e, t);
|
||||
}
|
||||
writeInt64(e, t = this.littleEndian) {
|
||||
Y(e, -0x8000000000000000, 0x8000000000000000, "signed int64"),
|
||||
P(this, 8, N, e, t);
|
||||
}
|
||||
writeFloat32(e, t = this.littleEndian) {
|
||||
P(this, 4, L, e, t);
|
||||
}
|
||||
writeFloat64(e, t = this.littleEndian) {
|
||||
P(this, 8, k, e, t);
|
||||
}
|
||||
writeVarInt(e) {
|
||||
Y(e, -0x8000000000000000, 0x8000000000000000, "varint (signed int64)");
|
||||
var t = e < 0,
|
||||
r = t ? -e : e,
|
||||
a = Math.floor(r / 4294967296),
|
||||
i = r - 4294967296 * a;
|
||||
t && ((a = ~a), 0 === i ? a++ : (i = -i)), O(this, B(a, i), U, a, i);
|
||||
}
|
||||
writeVarIntFromHexLong(e) {
|
||||
for (
|
||||
var t = hexLongIsNegative(e),
|
||||
r = t ? negateHexLong(e) : e,
|
||||
i = hexLongToHex(r),
|
||||
n = 0,
|
||||
s = 0,
|
||||
o = 0;
|
||||
o < NUM_HEX_IN_LONG;
|
||||
o++
|
||||
)
|
||||
(n = (n << 4) | (s >>> 28)), (s = (s << 4) | hexAt(i, o));
|
||||
t && ((n = ~n), 0 === s ? n++ : (s = -s)), O(this, B(n, s), U, n, s);
|
||||
}
|
||||
writeBinary(e) {
|
||||
var t = e.peek((e) => e.readByteArray());
|
||||
if (t.length) {
|
||||
var r = this.shiftWriteMaybeReallocate(t.length);
|
||||
this.buffer.set(t, r);
|
||||
}
|
||||
}
|
||||
writeBuffer(e) {
|
||||
this.writeByteArray(new Uint8Array(e));
|
||||
}
|
||||
writeByteArray(e) {
|
||||
var t = this.shiftWriteMaybeReallocate(e.length);
|
||||
this.buffer.set(e, t);
|
||||
}
|
||||
writeBufferView(e) {
|
||||
this.writeByteArray(new Uint8Array(e.buffer, e.byteOffset, e.byteLength));
|
||||
}
|
||||
writeString(e) {
|
||||
O(this, u(e), G, e);
|
||||
}
|
||||
writeHexLong(e, t = this.littleEndian) {
|
||||
P(this, 8, F, e, t);
|
||||
}
|
||||
writeBytes(...e) {
|
||||
for (var t = 0; t < e.length; t++) Y(e[t], 0, 256, "byte");
|
||||
O(this, e.length, x, e);
|
||||
}
|
||||
writeAtomically(e, t) {
|
||||
this.hiddenWrites++;
|
||||
var r = this.writeIndex,
|
||||
a = this.bytesTrashed;
|
||||
try {
|
||||
var i = e(this, t);
|
||||
return (r = this.writeIndex), (a = this.bytesTrashed), i;
|
||||
} finally {
|
||||
this.hiddenWrites--, (this.writeIndex = r - (this.bytesTrashed - a));
|
||||
}
|
||||
}
|
||||
|
||||
writeWithVarIntLength(e, t) {
|
||||
var r = this.writeIndex,
|
||||
a = this.writeAtomically(e, t),
|
||||
i = this.writeIndex;
|
||||
this.writeVarInt(i - r);
|
||||
for (var s = this.writeIndex - i, o = this.buffer, l = 0; l < s; l++)
|
||||
n[l] = o[i + l];
|
||||
for (var d = i - 1; d >= r; d--) o[d + s] = o[d];
|
||||
for (var u = 0; u < s; u++) o[r + u] = n[u];
|
||||
return a;
|
||||
}
|
||||
|
||||
static build(...e) {
|
||||
let t = 0;
|
||||
let r = 0;
|
||||
for (t = 0, r = 0; r < e.length; r++) {
|
||||
let a = e[r];
|
||||
"string" == typeof a
|
||||
? (t += u(a))
|
||||
: "number" == typeof a
|
||||
? t++
|
||||
: a instanceof Binary
|
||||
? (t += a.size())
|
||||
: a instanceof ArrayBuffer
|
||||
? (t += a.byteLength)
|
||||
: a instanceof Uint8Array && (t += a.length);
|
||||
}
|
||||
|
||||
var i = new Binary();
|
||||
return i.ensureCapacity(t), i.write.apply(i, arguments), i;
|
||||
}
|
||||
|
||||
getView() {
|
||||
return (
|
||||
this.view ||
|
||||
(this.view = new DataView(this.buffer.buffer, this.buffer.byteOffset))
|
||||
);
|
||||
}
|
||||
|
||||
shiftReadOrThrow(e) {
|
||||
if (e < 0)
|
||||
throw new Error("ReadError: given negative number of bytes to read");
|
||||
var t = this.readIndex,
|
||||
r = t + e;
|
||||
if (r > this.readEndIndex)
|
||||
throw new Error(
|
||||
t === this.readEndIndex
|
||||
? "ReadError: tried to read from depleted binary"
|
||||
: "ReadError: tried to read beyond end of binary"
|
||||
);
|
||||
return (
|
||||
(this.readIndex = r), this.hiddenReads || (this.earliestIndex = r), t
|
||||
);
|
||||
}
|
||||
|
||||
maybeReallocate(e) {
|
||||
const t = this.buffer;
|
||||
if (e <= t.length) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const r = this.earliestIndex;
|
||||
const a = e - r;
|
||||
const i = Math.max(a, 2 * (t.length - r), 64);
|
||||
const n = new Uint8Array(i);
|
||||
return (
|
||||
r
|
||||
? (n.set(t.subarray(r)),
|
||||
(this.bytesTrashed += r),
|
||||
(this.readIndex -= r),
|
||||
(this.readEndIndex -= r),
|
||||
(this.writeIndex -= r),
|
||||
(this.earliestIndex = 0))
|
||||
: n.set(t),
|
||||
(this.buffer = n),
|
||||
(this.view = null),
|
||||
a
|
||||
);
|
||||
}
|
||||
|
||||
shiftWriteMaybeReallocate(e) {
|
||||
const t = this.maybeReallocate(this.writeIndex + e);
|
||||
const r = this.writeIndex;
|
||||
return (
|
||||
(this.writeIndex = t), this.hiddenWrites || (this.readEndIndex = t), r
|
||||
);
|
||||
}
|
||||
decompressed = () => {
|
||||
if (2 & this.readUint8()) {
|
||||
const result = inflateSync(this.readByteArray())
|
||||
return new Binary(result)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Binary = Binary
|
||||
File diff suppressed because one or more lines are too long
@@ -1,117 +0,0 @@
|
||||
const { randomBytes } = require('crypto')
|
||||
|
||||
const r = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70],
|
||||
a = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102];
|
||||
|
||||
const i = (e) => {
|
||||
for (var t = [], a = 0; a < e.length; a++) {
|
||||
var i = e[a];
|
||||
t.push(r[i >> 4], r[15 & i]);
|
||||
}
|
||||
return String.fromCharCode.apply(String, t);
|
||||
};
|
||||
|
||||
const n = (e, t) => {
|
||||
var r = e.charCodeAt(t);
|
||||
return r <= 57 ? r - 48 : r <= 70 ? 10 + r - 65 : 10 + r - 97;
|
||||
};
|
||||
|
||||
const s = (e) => {
|
||||
if (/[^0-9a-fA-F]/.test(e)) throw new Error(`"${e}" is not a valid hex`);
|
||||
return e;
|
||||
};
|
||||
|
||||
const o = (e, t) => {
|
||||
for (var r = t - e.length, a = e, i = 0; i < r; i++) a = "0" + a;
|
||||
return a;
|
||||
};
|
||||
|
||||
const l = (e) => {
|
||||
return "-" === e[0];
|
||||
};
|
||||
|
||||
const d = (e) => {
|
||||
if (e > 4294967295 || e < -4294967296)
|
||||
throw new Error("uint32ToLowerCaseHex given number over 32 bits");
|
||||
return o((e >= 0 ? e : 4294967296 + e).toString(16), 8);
|
||||
};
|
||||
|
||||
module.exports.NUM_HEX_IN_LONG = 16;
|
||||
module.exports.HEX_LOWER = a;
|
||||
|
||||
module.exports.randomHex = function (e) {
|
||||
var t = new Uint8Array(e);
|
||||
var bytes = randomBytes(t.length);
|
||||
t.set(bytes);
|
||||
return i(t);
|
||||
};
|
||||
|
||||
module.exports.toHex = i;
|
||||
|
||||
module.exports.toLowerCaseHex = function (e) {
|
||||
for (var t = [], r = 0; r < e.length; r++) {
|
||||
var i = e[r];
|
||||
t.push(a[i >> 4], a[15 & i]);
|
||||
}
|
||||
return String.fromCharCode.apply(String, t);
|
||||
};
|
||||
|
||||
module.exports.parseHex = function (e) {
|
||||
var t = s(e);
|
||||
if (t.length % 2 != 0)
|
||||
throw new Error(
|
||||
`parseHex given hex "${t}" which is not a multiple of 8-bits.`
|
||||
);
|
||||
for (
|
||||
var r = new Uint8Array(t.length >> 1), a = 0, i = 0;
|
||||
a < t.length;
|
||||
a += 2, i++
|
||||
)
|
||||
r[i] = (n(t, a) << 4) | n(t, a + 1);
|
||||
return r.buffer;
|
||||
};
|
||||
|
||||
module.exports.hexAt = n;
|
||||
module.exports.hexOrThrow = s;
|
||||
module.exports.bytesToBuffer = function (e) {
|
||||
var t = e.buffer;
|
||||
return 0 === e.byteOffset && e.length === t.byteLength
|
||||
? t
|
||||
: t.slice(e.byteOffset, e.byteOffset + e.length);
|
||||
};
|
||||
|
||||
module.exports.bytesToDebugString = function (e) {
|
||||
var t = !0,
|
||||
r = e.length;
|
||||
for (; t && r; ) {
|
||||
var a = e[--r];
|
||||
t = 32 <= a && a < 127;
|
||||
}
|
||||
return t ? JSON.stringify(String.fromCharCode.apply(String, e)) : i(e);
|
||||
};
|
||||
|
||||
module.exports.createHexLong = function (e, t = !1) {
|
||||
var r = s(e);
|
||||
return (
|
||||
(function (e, t) {
|
||||
if (e.length > t) throw new Error(`"${e}" is longer than ${4 * t} bits.`);
|
||||
})(r, 16),
|
||||
`${t ? "-" : ""}0x${o(r, 16)}`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports.createHexLongFrom32Bits = function (e, t, r = !1) {
|
||||
var a = d(e),
|
||||
i = d(t);
|
||||
return `${r ? "-" : ""}0x${a}${i}`;
|
||||
};
|
||||
|
||||
module.exports.hexLongToHex = function (e) {
|
||||
return e.substring(e.indexOf("0x") + 2);
|
||||
};
|
||||
|
||||
module.exports.hexLongIsNegative = l;
|
||||
|
||||
module.exports.negateHexLong = function (e) {
|
||||
return l(e) ? e.slice(1) : "-" + e;
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
# WABinary
|
||||
|
||||
Contains the raw JS code to parse WA binary messages. WA uses a tree like structure to encode information, the type for which is written below:
|
||||
|
||||
``` ts
|
||||
export type BinaryNode = {
|
||||
tag: string
|
||||
attrs: Attributes
|
||||
content?: BinaryNode[] | string | Uint8Array
|
||||
}
|
||||
```
|
||||
|
||||
Do note, the multi-device binary format is very similar to the one on WA Web, though they are not backwards compatible.
|
||||
|
||||
Originally from [pokearaujo/multidevice](https://github.com/pokearaujo/multidevice)
|
||||
11
package.json
11
package.json
@@ -37,8 +37,8 @@
|
||||
"dependencies": {
|
||||
"@hapi/boom": "^9.1.3",
|
||||
"axios": "^0.24.0",
|
||||
"curve25519-js": "^0.0.4",
|
||||
"libsignal": "git+https://github.com/adiwajshing/libsignal-node",
|
||||
"futoin-hkdf": "^1.5.0",
|
||||
"libsignal": "git+https://github.com/adiwajshing/libsignal-node#proto-patch",
|
||||
"music-metadata": "^7.4.1",
|
||||
"node-cache": "^5.1.2",
|
||||
"pino": "^7.0.0",
|
||||
@@ -48,9 +48,9 @@
|
||||
"peerDependencies": {
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"jimp": "^0.16.1",
|
||||
"link-preview-js": "^2.1.13",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"sharp": "^0.29.3",
|
||||
"link-preview-js": "^2.1.13"
|
||||
"sharp": "^0.29.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@adiwajshing/keyed-db": {
|
||||
@@ -72,8 +72,7 @@
|
||||
"files": [
|
||||
"lib/*",
|
||||
"WAProto/*",
|
||||
"WASignalGroup/*.js",
|
||||
"WABinary/*.js"
|
||||
"WASignalGroup/*.js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@adiwajshing/eslint-config": "git+https://github.com/adiwajshing/eslint-config",
|
||||
|
||||
@@ -13,8 +13,15 @@ export const PHONE_CONNECTION_CB = 'CB:Pong'
|
||||
export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60
|
||||
|
||||
export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
|
||||
export const NOISE_WA_HEADER = new Uint8Array([87, 65, 5, 2]) // last is "DICT_VERSION"
|
||||
|
||||
export const DICT_VERSION = Buffer.from([2])
|
||||
export const KEY_BUNDLE_TYPE = Buffer.from([5])
|
||||
export const NOISE_WA_HEADER = Buffer.concat(
|
||||
[
|
||||
Buffer.from('WA', 'ascii'),
|
||||
KEY_BUNDLE_TYPE,
|
||||
DICT_VERSION
|
||||
]
|
||||
) // last is "DICT_VERSION"
|
||||
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
||||
export const URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)?/gi
|
||||
|
||||
@@ -24,7 +31,7 @@ const BASE_CONNECTION_CONFIG: CommonSocketConfig<any> = {
|
||||
|
||||
waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
|
||||
connectTimeoutMs: 20_000,
|
||||
keepAliveIntervalMs: 25_000,
|
||||
keepAliveIntervalMs: 15_000,
|
||||
logger: logger.child({ class: 'baileys' }),
|
||||
printQRInTerminal: false,
|
||||
emitOwnEvents: true,
|
||||
@@ -59,4 +66,6 @@ export const MEDIA_PATH_MAP: { [T in MediaType]: string } = {
|
||||
|
||||
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
|
||||
|
||||
export const KEY_BUNDLE_TYPE = Buffer.from([5])
|
||||
export const MIN_PREKEY_COUNT = 5
|
||||
|
||||
export const INITIAL_PREKEY_COUNT = 50
|
||||
@@ -466,6 +466,10 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
const processSyncActionsLocal = (actions: ChatMutation[]) => {
|
||||
const events = processSyncActions(actions, authState.creds.me!, logger)
|
||||
emitEventsFromMap(events)
|
||||
// resend available presence to update name on servers
|
||||
if(events['creds.update']?.me?.name) {
|
||||
sendPresenceUpdate('available')
|
||||
}
|
||||
}
|
||||
|
||||
const appPatch = async(patchCreate: WAPatchCreate) => {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
|
||||
import { proto } from '../../WAProto'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults'
|
||||
import { BaileysEventMap, MessageReceiptType, MessageUserReceipt, SocketConfig, WAMessageStubType } from '../Types'
|
||||
import { debouncedTimeout, decodeMessageStanza, delay, encodeBigEndian, generateSignalPubKey, getStatusFromReceiptType, normalizeMessageContent, xmppPreKey, xmppSignedPreKey } from '../Utils'
|
||||
import { debouncedTimeout, decodeMessageStanza, delay, encodeBigEndian, generateSignalPubKey, getNextPreKeys, getStatusFromReceiptType, normalizeMessageContent, xmppPreKey, xmppSignedPreKey } from '../Utils'
|
||||
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex'
|
||||
import processMessage from '../Utils/process-message'
|
||||
import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { makeChatsSocket } from './chats'
|
||||
import { extractGroupMetadata } from './groups'
|
||||
|
||||
const MIN_PREKEY_COUNT = 5
|
||||
|
||||
export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const {
|
||||
logger,
|
||||
@@ -24,7 +22,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
ws,
|
||||
onUnexpectedError,
|
||||
assertSessions,
|
||||
assertingPreKeys,
|
||||
sendNode,
|
||||
relayMessage,
|
||||
sendReceipt,
|
||||
@@ -80,64 +77,70 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
|
||||
|
||||
const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish()
|
||||
await assertingPreKeys(1, async preKeys => {
|
||||
const [keyId] = Object.keys(preKeys)
|
||||
const key = preKeys[+keyId]
|
||||
await authState.keys.transaction(
|
||||
async() => {
|
||||
const { update, preKeys } = await getNextPreKeys(authState, 1)
|
||||
|
||||
const decFrom = node.attrs.from ? jidDecode(node.attrs.from) : undefined
|
||||
const receipt: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: msgId,
|
||||
type: 'retry',
|
||||
to: isGroup ? node.attrs.from : jidEncode(decFrom!.user, 's.whatsapp.net', decFrom!.device, 0)
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'retry',
|
||||
attrs: {
|
||||
count: retryCount.toString(),
|
||||
id: node.attrs.id,
|
||||
t: node.attrs.t,
|
||||
v: '1'
|
||||
}
|
||||
const [keyId] = Object.keys(preKeys)
|
||||
const key = preKeys[+keyId]
|
||||
|
||||
const decFrom = node.attrs.from ? jidDecode(node.attrs.from) : undefined
|
||||
const receipt: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: msgId,
|
||||
type: 'retry',
|
||||
to: isGroup ? node.attrs.from : jidEncode(decFrom!.user, 's.whatsapp.net', decFrom!.device, 0)
|
||||
},
|
||||
{
|
||||
tag: 'registration',
|
||||
attrs: { },
|
||||
content: encodeBigEndian(authState.creds.registrationId)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if(node.attrs.recipient) {
|
||||
receipt.attrs.recipient = node.attrs.recipient
|
||||
}
|
||||
|
||||
if(node.attrs.participant) {
|
||||
receipt.attrs.participant = node.attrs.participant
|
||||
}
|
||||
|
||||
if(retryCount > 1) {
|
||||
const exec = generateSignalPubKey(Buffer.from(KEY_BUNDLE_TYPE)).slice(0, 1)
|
||||
const content = receipt.content! as BinaryNode[]
|
||||
content.push({
|
||||
tag: 'keys',
|
||||
attrs: { },
|
||||
content: [
|
||||
{ tag: 'type', attrs: { }, content: exec },
|
||||
{ tag: 'identity', attrs: { }, content: identityKey.public },
|
||||
xmppPreKey(key, +keyId),
|
||||
xmppSignedPreKey(signedPreKey),
|
||||
{ tag: 'device-identity', attrs: { }, content: deviceIdentity }
|
||||
{
|
||||
tag: 'retry',
|
||||
attrs: {
|
||||
count: retryCount.toString(),
|
||||
id: node.attrs.id,
|
||||
t: node.attrs.t,
|
||||
v: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'registration',
|
||||
attrs: { },
|
||||
content: encodeBigEndian(authState.creds.registrationId)
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
if(node.attrs.recipient) {
|
||||
receipt.attrs.recipient = node.attrs.recipient
|
||||
}
|
||||
|
||||
if(node.attrs.participant) {
|
||||
receipt.attrs.participant = node.attrs.participant
|
||||
}
|
||||
|
||||
if(retryCount > 1) {
|
||||
const exec = generateSignalPubKey(Buffer.from(KEY_BUNDLE_TYPE)).slice(0, 1)
|
||||
const content = receipt.content! as BinaryNode[]
|
||||
content.push({
|
||||
tag: 'keys',
|
||||
attrs: { },
|
||||
content: [
|
||||
{ tag: 'type', attrs: { }, content: exec },
|
||||
{ tag: 'identity', attrs: { }, content: identityKey.public },
|
||||
xmppPreKey(key, +keyId),
|
||||
xmppSignedPreKey(signedPreKey),
|
||||
{ tag: 'device-identity', attrs: { }, content: deviceIdentity }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
await sendNode(receipt)
|
||||
|
||||
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
|
||||
|
||||
ev.emit('creds.update', update)
|
||||
}
|
||||
|
||||
await sendNode(receipt)
|
||||
|
||||
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const processMessageLocal = async(msg: proto.IWebMessageInfo) => {
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { randomBytes } from 'crypto'
|
||||
import EventEmitter from 'events'
|
||||
import { promisify } from 'util'
|
||||
import WebSocket from 'ws'
|
||||
import { proto } from '../../WAProto'
|
||||
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, DEFAULT_ORIGIN, KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, DEFAULT_ORIGIN, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT } from '../Defaults'
|
||||
import { AuthenticationCreds, BaileysEventEmitter, BaileysEventMap, DisconnectReason, SocketConfig } from '../Types'
|
||||
import { addTransactionCapability, bindWaitForConnectionUpdate, configureSuccessfulPairing, Curve, encodeBigEndian, generateLoginNode, generateOrGetPreKeys, generateRegistrationNode, getPreKeys, makeNoiseHandler, printQRIfNecessaryListener, promiseTimeout, useSingleFileAuthState, xmppPreKey, xmppSignedPreKey } from '../Utils'
|
||||
import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, getBinaryNodeChild, S_WHATSAPP_NET } from '../WABinary'
|
||||
|
||||
const INITIAL_PREKEY_COUNT = 30
|
||||
import { addTransactionCapability, bindWaitForConnectionUpdate, configureSuccessfulPairing, Curve, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getNextPreKeysNode, makeNoiseHandler, printQRIfNecessaryListener, promiseTimeout, useSingleFileAuthState } from '../Utils'
|
||||
import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } from '../WABinary'
|
||||
|
||||
/**
|
||||
* Connects to WA servers and performs:
|
||||
@@ -32,22 +29,14 @@ export const makeSocket = ({
|
||||
const ws = new WebSocket(waWebSocketUrl, undefined, {
|
||||
origin: DEFAULT_ORIGIN,
|
||||
timeout: connectTimeoutMs,
|
||||
agent,
|
||||
headers: {
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Host': 'web.whatsapp.com',
|
||||
'Pragma': 'no-cache',
|
||||
'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits'
|
||||
}
|
||||
agent
|
||||
})
|
||||
ws.setMaxListeners(0)
|
||||
const ev = new EventEmitter() as BaileysEventEmitter
|
||||
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
||||
const ephemeralKeyPair = Curve.generateKeyPair()
|
||||
/** WA noise protocol wrapper */
|
||||
const noise = makeNoiseHandler(ephemeralKeyPair)
|
||||
const noise = makeNoiseHandler(ephemeralKeyPair, logger)
|
||||
let authState = initialAuthState
|
||||
if(!authState) {
|
||||
authState = useSingleFileAuthState('./auth-info-multi.json').state
|
||||
@@ -64,16 +53,16 @@ export const makeSocket = ({
|
||||
const keys = addTransactionCapability(authState.keys, logger)
|
||||
|
||||
let lastDateRecv: Date
|
||||
let epoch = 0
|
||||
let epoch = 1
|
||||
let keepAliveReq: NodeJS.Timeout
|
||||
let qrTimer: NodeJS.Timeout
|
||||
|
||||
const uqTagId = `${randomBytes(1).toString('hex')[0]}.${randomBytes(1).toString('hex')[0]}-`
|
||||
const uqTagId = generateMdTagPrefix()
|
||||
const generateMessageTag = () => `${uqTagId}${epoch++}`
|
||||
|
||||
const sendPromise = promisify<void>(ws.send)
|
||||
/** send a raw buffer */
|
||||
const sendRawMessage = async(data: Buffer | Uint8Array) => {
|
||||
const sendRawMessage = async(data: Uint8Array | Buffer) => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
@@ -185,7 +174,7 @@ export const makeSocket = ({
|
||||
}
|
||||
helloMsg = proto.HandshakeMessage.fromObject(helloMsg)
|
||||
|
||||
logger.info({ browser, helloMsg }, 'connected to WA Web')
|
||||
logger.info({ browser, helloMsg, registrationId: creds.registrationId }, 'connected to WA Web')
|
||||
|
||||
const init = proto.HandshakeMessage.encode(helloMsg).finish()
|
||||
|
||||
@@ -195,7 +184,6 @@ export const makeSocket = ({
|
||||
logger.trace({ handshake }, 'handshake recv from WA Web')
|
||||
|
||||
const keyEnc = noise.processHandshake(handshake, creds.noiseKey)
|
||||
logger.info('handshake complete')
|
||||
|
||||
let node: proto.IClientPayload
|
||||
if(!creds.me) {
|
||||
@@ -212,8 +200,8 @@ export const makeSocket = ({
|
||||
await sendRawMessage(
|
||||
proto.HandshakeMessage.encode({
|
||||
clientFinish: {
|
||||
static: new Uint8Array(keyEnc),
|
||||
payload: new Uint8Array(payloadEnc),
|
||||
static: keyEnc,
|
||||
payload: payloadEnc,
|
||||
},
|
||||
}).finish()
|
||||
)
|
||||
@@ -221,57 +209,44 @@ export const makeSocket = ({
|
||||
startKeepAliveRequest()
|
||||
}
|
||||
|
||||
/**
|
||||
* get some pre-keys and do something with them
|
||||
* @param range how many pre-keys to get
|
||||
* @param execute what to do with them
|
||||
*/
|
||||
const assertingPreKeys = async(range: number, execute: (keys: { [_: number]: any }) => Promise<void>) => {
|
||||
const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState.creds, range)
|
||||
|
||||
const update: Partial<AuthenticationCreds> = {
|
||||
nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId),
|
||||
firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1)
|
||||
}
|
||||
if(!creds.serverHasPreKeys) {
|
||||
update.serverHasPreKeys = true
|
||||
}
|
||||
|
||||
await keys.transaction(
|
||||
async() => {
|
||||
await keys.set({ 'pre-key': newPreKeys })
|
||||
|
||||
const preKeys = await getPreKeys(keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1])
|
||||
await execute(preKeys)
|
||||
}
|
||||
)
|
||||
|
||||
ev.emit('creds.update', update)
|
||||
const getAvailablePreKeysOnServer = async() => {
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
id: generateMessageTag(),
|
||||
xmlns: 'encrypt',
|
||||
type: 'get',
|
||||
to: S_WHATSAPP_NET
|
||||
},
|
||||
content: [
|
||||
{ tag: 'count', attrs: { } }
|
||||
]
|
||||
})
|
||||
const countChild = getBinaryNodeChild(result, 'count')
|
||||
return +countChild.attrs.value
|
||||
}
|
||||
|
||||
/** generates and uploads a set of pre-keys to the server */
|
||||
const uploadPreKeys = async(count = INITIAL_PREKEY_COUNT) => {
|
||||
await assertingPreKeys(count, async preKeys => {
|
||||
const node: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
id: generateMessageTag(),
|
||||
xmlns: 'encrypt',
|
||||
type: 'set',
|
||||
to: S_WHATSAPP_NET,
|
||||
},
|
||||
content: [
|
||||
{ tag: 'registration', attrs: { }, content: encodeBigEndian(creds.registrationId) },
|
||||
{ tag: 'type', attrs: { }, content: KEY_BUNDLE_TYPE },
|
||||
{ tag: 'identity', attrs: { }, content: creds.signedIdentityKey.public },
|
||||
{ tag: 'list', attrs: { }, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) },
|
||||
xmppSignedPreKey(creds.signedPreKey)
|
||||
]
|
||||
}
|
||||
await sendNode(node)
|
||||
await keys.transaction(
|
||||
async() => {
|
||||
logger.info({ count }, 'uploading pre-keys')
|
||||
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
|
||||
|
||||
logger.info('uploaded pre-keys')
|
||||
})
|
||||
await query(node)
|
||||
ev.emit('creds.update', update)
|
||||
|
||||
logger.info({ count }, 'uploaded pre-keys')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const uploadPreKeysToServerIfRequired = async() => {
|
||||
const preKeyCount = await getAvailablePreKeysOnServer()
|
||||
logger.info(`${preKeyCount} pre-keys found on server`)
|
||||
if(preKeyCount <= MIN_PREKEY_COUNT) {
|
||||
await uploadPreKeys()
|
||||
}
|
||||
}
|
||||
|
||||
const onMessageRecieved = (data: Buffer) => {
|
||||
@@ -279,7 +254,9 @@ export const makeSocket = ({
|
||||
// reset ping timeout
|
||||
lastDateRecv = new Date()
|
||||
|
||||
ws.emit('frame', frame)
|
||||
let anyTriggered = false
|
||||
|
||||
anyTriggered = ws.emit('frame', frame)
|
||||
// if it's a binary node
|
||||
if(!(frame instanceof Uint8Array)) {
|
||||
const msgId = frame.attrs.id
|
||||
@@ -288,9 +265,8 @@ export const makeSocket = ({
|
||||
logger.trace({ msgId, fromMe: false, frame }, 'communication')
|
||||
}
|
||||
|
||||
let anyTriggered = false
|
||||
/* Check if this is a response to a message we sent */
|
||||
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame)
|
||||
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered
|
||||
/* Check if this is a response to a message we are expecting */
|
||||
const l0 = frame.tag
|
||||
const l1 = frame.attrs || { }
|
||||
@@ -303,7 +279,6 @@ export const makeSocket = ({
|
||||
})
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered
|
||||
anyTriggered = ws.emit('frame', frame) || anyTriggered
|
||||
|
||||
if(!anyTriggered && logger.level === 'debug') {
|
||||
logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv')
|
||||
@@ -467,7 +442,8 @@ export const makeSocket = ({
|
||||
}
|
||||
await sendNode(iq)
|
||||
|
||||
const refs = ((stanza.content[0] as BinaryNode).content as BinaryNode[]).map(n => n.content as string)
|
||||
const pairDeviceNode = getBinaryNodeChild(stanza, 'pair-device')
|
||||
const refNodes = getBinaryNodeChildren(pairDeviceNode, 'ref')
|
||||
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64')
|
||||
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64')
|
||||
const advB64 = creds.advSecretKey
|
||||
@@ -478,12 +454,13 @@ export const makeSocket = ({
|
||||
return
|
||||
}
|
||||
|
||||
const ref = refs.shift()
|
||||
if(!ref) {
|
||||
const refNode = refNodes.shift()
|
||||
if(!refNode) {
|
||||
end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut }))
|
||||
return
|
||||
}
|
||||
|
||||
const ref = (refNode.content as Buffer).toString('utf-8')
|
||||
const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',')
|
||||
|
||||
ev.emit('connection.update', { qr })
|
||||
@@ -513,7 +490,10 @@ export const makeSocket = ({
|
||||
}
|
||||
}
|
||||
|
||||
logger.info({ jid: updatedCreds.me!.id }, 'registered connection, restart server')
|
||||
logger.info(
|
||||
{ me: updatedCreds.me, platform: updatedCreds.platform },
|
||||
'registered connection, restart server'
|
||||
)
|
||||
|
||||
ev.emit('creds.update', updatedCreds)
|
||||
ev.emit('connection.update', { isNewLogin: true, qr: undefined })
|
||||
@@ -529,10 +509,7 @@ export const makeSocket = ({
|
||||
})
|
||||
// login complete
|
||||
ws.on('CB:success', async() => {
|
||||
if(!creds.serverHasPreKeys) {
|
||||
await uploadPreKeys()
|
||||
}
|
||||
|
||||
await uploadPreKeysToServerIfRequired()
|
||||
await sendPassiveIq('active')
|
||||
|
||||
logger.info('opened connection to WA')
|
||||
@@ -597,7 +574,6 @@ export const makeSocket = ({
|
||||
return authState.creds.me
|
||||
},
|
||||
emitEventsFromMap,
|
||||
assertingPreKeys,
|
||||
generateMessageTag,
|
||||
query,
|
||||
waitForMessage,
|
||||
|
||||
@@ -41,10 +41,10 @@ export type AuthenticationCreds = SignalCreds & {
|
||||
signalIdentities?: SignalIdentity[]
|
||||
myAppStateKeyId?: string
|
||||
firstUnuploadedPreKeyId: number
|
||||
serverHasPreKeys: boolean
|
||||
nextPreKeyId: number
|
||||
|
||||
lastAccountSyncTimestamp?: number
|
||||
platform?: string
|
||||
accountSettings: AccountSettings
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,6 @@ export const initAuthCreds = (): AuthenticationCreds => {
|
||||
|
||||
nextPreKeyId: 1,
|
||||
firstUnuploadedPreKeyId: 1,
|
||||
serverHasPreKeys: false,
|
||||
accountSettings: {
|
||||
unarchiveChats: false
|
||||
}
|
||||
|
||||
@@ -583,8 +583,10 @@ export const processSyncActions = (
|
||||
name: action.contactAction!.fullName
|
||||
}
|
||||
} else if(action?.pushNameSetting) {
|
||||
map['creds.update'] = map['creds.update'] || { }
|
||||
map['creds.update'].me = { ...me, name: action?.pushNameSetting?.name! }
|
||||
if(me?.name !== action?.pushNameSetting) {
|
||||
map['creds.update'] = map['creds.update'] || { }
|
||||
map['creds.update'].me = { ...me, name: action?.pushNameSetting?.name! }
|
||||
}
|
||||
} else if(action?.pinAction) {
|
||||
update.pin = action.pinAction?.pinned ? toNumber(action.timestamp) : undefined
|
||||
} else if(action?.unarchiveChatsSetting) {
|
||||
|
||||
@@ -1,36 +1,49 @@
|
||||
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto'
|
||||
import * as curveJs from 'curve25519-js'
|
||||
import HKDF from 'futoin-hkdf'
|
||||
import * as libsignal from 'libsignal'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { KeyPair } from '../Types'
|
||||
|
||||
/** prefix version byte to the pub keys, required for some curve crypto functions */
|
||||
export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) => (
|
||||
pubKey.length === 33
|
||||
? pubKey
|
||||
: Buffer.concat([ KEY_BUNDLE_TYPE, pubKey ])
|
||||
)
|
||||
|
||||
export const Curve = {
|
||||
generateKeyPair: (): KeyPair => {
|
||||
const { public: pubKey, private: privKey } = curveJs.generateKeyPair(randomBytes(32))
|
||||
const { pubKey, privKey } = libsignal.curve.generateKeyPair()
|
||||
return {
|
||||
private: Buffer.from(privKey),
|
||||
public: Buffer.from(pubKey)
|
||||
// remove version byte
|
||||
public: Buffer.from((pubKey as Uint8Array).slice(1))
|
||||
}
|
||||
},
|
||||
sharedKey: (privateKey: Uint8Array, publicKey: Uint8Array) => {
|
||||
const shared = curveJs.sharedKey(privateKey, publicKey)
|
||||
const shared = libsignal.curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey)
|
||||
return Buffer.from(shared)
|
||||
},
|
||||
sign: (privateKey: Uint8Array, buf: Uint8Array) => (
|
||||
Buffer.from(curveJs.sign(privateKey, buf, null))
|
||||
libsignal.curve.calculateSignature(privateKey, buf)
|
||||
),
|
||||
verify: (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => {
|
||||
return curveJs.verify(pubKey, message, signature)
|
||||
try {
|
||||
libsignal.curve.verifySignature(generateSignalPubKey(pubKey), message, signature)
|
||||
return true
|
||||
} catch(error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const signedKeyPair = (keyPair: KeyPair, keyId: number) => {
|
||||
const signKeys = Curve.generateKeyPair()
|
||||
const pubKey = new Uint8Array(33)
|
||||
pubKey.set([5], 0)
|
||||
pubKey.set(signKeys.public, 1)
|
||||
export const signedKeyPair = (identityKeyPair: KeyPair, keyId: number) => {
|
||||
const preKey = Curve.generateKeyPair()
|
||||
const pubKey = generateSignalPubKey(preKey.public)
|
||||
|
||||
const signature = Curve.sign(keyPair.private, pubKey)
|
||||
const signature = Curve.sign(identityKeyPair.private, pubKey)
|
||||
|
||||
return { keyPair: signKeys, signature, keyId }
|
||||
return { keyPair: preKey, signature, keyId }
|
||||
}
|
||||
|
||||
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
||||
@@ -67,33 +80,6 @@ export function sha256(buffer: Buffer) {
|
||||
}
|
||||
|
||||
// HKDF key expansion
|
||||
// from: https://github.com/benadida/node-hkdf
|
||||
export function hkdf(buffer: Uint8Array, expandedLength: number, { info, salt }: { salt?: Buffer, info?: string }) {
|
||||
const hashAlg = 'sha256'
|
||||
const hashLength = 32
|
||||
salt = salt || Buffer.alloc(hashLength)
|
||||
// now we compute the PRK
|
||||
const prk = createHmac(hashAlg, salt).update(buffer).digest()
|
||||
|
||||
let prev = Buffer.from([])
|
||||
const buffers = []
|
||||
const num_blocks = Math.ceil(expandedLength / hashLength)
|
||||
|
||||
const infoBuff = Buffer.from(info || [])
|
||||
|
||||
for(var i = 0; i < num_blocks; i++) {
|
||||
const hmac = createHmac(hashAlg, prk)
|
||||
// XXX is there a more optimal way to build up buffers?
|
||||
const input = Buffer.concat([
|
||||
prev,
|
||||
infoBuff,
|
||||
Buffer.from(String.fromCharCode(i + 1))
|
||||
])
|
||||
hmac.update(input)
|
||||
|
||||
prev = hmac.digest()
|
||||
buffers.push(prev)
|
||||
}
|
||||
|
||||
return Buffer.concat(buffers, expandedLength)
|
||||
export function hkdf(buffer: Uint8Array | Buffer, expandedLength: number, info: { salt?: Buffer, info?: string }) {
|
||||
return HKDF(!Buffer.isBuffer(buffer) ? Buffer.from(buffer) : buffer, expandedLength, info)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { Logger } from 'pino'
|
||||
import { proto } from '../../WAProto'
|
||||
import { version as baileysVersion } from '../Defaults/baileys-version.json'
|
||||
import { CommonBaileysEventEmitter, ConnectionState, DisconnectReason, WAVersion } from '../Types'
|
||||
import { Binary } from '../WABinary'
|
||||
|
||||
const PLATFORM_MAP = {
|
||||
'aix': 'AIX',
|
||||
@@ -41,16 +40,14 @@ export const BufferJSON = {
|
||||
}
|
||||
}
|
||||
|
||||
export const writeRandomPadMax16 = (e: Binary) => {
|
||||
function r(e: Binary, t: number) {
|
||||
for(var r = 0; r < t; r++) {
|
||||
e.writeUint8(t)
|
||||
}
|
||||
export const writeRandomPadMax16 = (msg: Uint8Array) => {
|
||||
const pad = randomBytes(1)
|
||||
pad[0] &= 0xf
|
||||
if(!pad[0]) {
|
||||
pad[0] = 0xf
|
||||
}
|
||||
|
||||
var t = randomBytes(1)
|
||||
r(e, 1 + (15 & t[0]))
|
||||
return e
|
||||
return Buffer.concat([msg, Buffer.alloc(pad[0], pad[0])])
|
||||
}
|
||||
|
||||
export const unpadRandomMax16 = (e: Uint8Array | Buffer) => {
|
||||
@@ -68,24 +65,13 @@ export const unpadRandomMax16 = (e: Uint8Array | Buffer) => {
|
||||
}
|
||||
|
||||
export const encodeWAMessage = (message: proto.IMessage) => (
|
||||
Buffer.from(
|
||||
writeRandomPadMax16(
|
||||
new Binary(proto.Message.encode(message).finish())
|
||||
).readByteArray()
|
||||
writeRandomPadMax16(
|
||||
proto.Message.encode(message).finish()
|
||||
)
|
||||
)
|
||||
|
||||
export const generateRegistrationId = () => (
|
||||
Uint16Array.from(randomBytes(2))[0] & 0x3fff
|
||||
)
|
||||
|
||||
export const encodeInt = (e: number, t: number) => {
|
||||
for(var r = t, a = new Uint8Array(e), i = e - 1; i >= 0; i--) {
|
||||
a[i] = 255 & r
|
||||
r >>>= 8
|
||||
}
|
||||
|
||||
return a
|
||||
export const generateRegistrationId = (): number => {
|
||||
return Uint16Array.from(randomBytes(2))[0] & 16383
|
||||
}
|
||||
|
||||
export const encodeBigEndian = (e: number, t = 4) => {
|
||||
@@ -255,6 +241,12 @@ export const fetchLatestBaileysVersion = async() => {
|
||||
}
|
||||
}
|
||||
|
||||
/** unique message tag prefix for MD clients */
|
||||
export const generateMdTagPrefix = () => {
|
||||
const bytes = randomBytes(4)
|
||||
return `${bytes.readUInt16BE()}.${bytes.readUInt16BE(2)}-`
|
||||
}
|
||||
|
||||
const STATUS_MAP: { [_: string]: proto.WebMessageInfo.WebMessageInfoStatus } = {
|
||||
'played': proto.WebMessageInfo.WebMessageInfoStatus.PLAYED,
|
||||
'read': proto.WebMessageInfo.WebMessageInfoStatus.READ,
|
||||
|
||||
@@ -327,28 +327,30 @@ export const generateWAMessageContent = async(
|
||||
|
||||
m = { buttonsMessage }
|
||||
} else if('templateButtons' in message && !!message.templateButtons) {
|
||||
const templateMessage: proto.ITemplateMessage = {
|
||||
hydratedTemplate: {
|
||||
hydratedButtons: message.templateButtons
|
||||
}
|
||||
const msg: proto.IHydratedFourRowTemplate = {
|
||||
hydratedButtons: message.templateButtons
|
||||
}
|
||||
|
||||
if('text' in message) {
|
||||
templateMessage.hydratedTemplate.hydratedContentText = message.text
|
||||
msg.hydratedContentText = message.text
|
||||
} else {
|
||||
|
||||
if('caption' in message) {
|
||||
templateMessage.hydratedTemplate.hydratedContentText = message.caption
|
||||
msg.hydratedContentText = message.caption
|
||||
}
|
||||
|
||||
Object.assign(templateMessage.hydratedTemplate, m)
|
||||
Object.assign(msg, m)
|
||||
}
|
||||
|
||||
if('footer' in message && !!message.footer) {
|
||||
templateMessage.hydratedTemplate.hydratedFooterText = message.footer
|
||||
msg.hydratedFooterText = message.footer
|
||||
}
|
||||
|
||||
m = { templateMessage }
|
||||
m = {
|
||||
templateMessage: {
|
||||
hydratedTemplate: msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if('sections' in message && !!message.sections) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { createCipheriv, createDecipheriv } from 'crypto'
|
||||
import { Logger } from 'pino'
|
||||
import { proto } from '../../WAProto'
|
||||
import { NOISE_MODE, NOISE_WA_HEADER } from '../Defaults'
|
||||
import { KeyPair } from '../Types'
|
||||
import { Binary } from '../WABinary'
|
||||
import { BinaryNode, decodeBinaryNode } from '../WABinary'
|
||||
import { Curve, hkdf, sha256 } from './crypto'
|
||||
|
||||
const TAG_LENGTH = 128 >> 3
|
||||
|
||||
const generateIV = (counter: number) => {
|
||||
const iv = new ArrayBuffer(12)
|
||||
new DataView(iv).setUint32(8, counter)
|
||||
@@ -14,7 +16,11 @@ const generateIV = (counter: number) => {
|
||||
return new Uint8Array(iv)
|
||||
}
|
||||
|
||||
export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: KeyPair) => {
|
||||
export const makeNoiseHandler = (
|
||||
{ public: publicKey, private: privateKey }: KeyPair,
|
||||
logger: Logger
|
||||
) => {
|
||||
logger = logger.child({ class: 'ns' })
|
||||
|
||||
const authenticate = (data: Uint8Array) => {
|
||||
if(!isFinished) {
|
||||
@@ -23,8 +29,7 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
|
||||
}
|
||||
|
||||
const encrypt = (plaintext: Uint8Array) => {
|
||||
const authTagLength = 128 >> 3
|
||||
const cipher = createCipheriv('aes-256-gcm', encKey, generateIV(writeCounter), { authTagLength })
|
||||
const cipher = createCipheriv('aes-256-gcm', encKey, generateIV(writeCounter), { authTagLength: TAG_LENGTH })
|
||||
cipher.setAAD(hash)
|
||||
|
||||
const result = Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()])
|
||||
@@ -41,9 +46,8 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
|
||||
const iv = generateIV(isFinished ? readCounter : writeCounter)
|
||||
const cipher = createDecipheriv('aes-256-gcm', decKey, iv)
|
||||
// decrypt additional adata
|
||||
const tagLength = 128 >> 3
|
||||
const enc = ciphertext.slice(0, ciphertext.length - tagLength)
|
||||
const tag = ciphertext.slice(ciphertext.length - tagLength)
|
||||
const enc = ciphertext.slice(0, ciphertext.length - TAG_LENGTH)
|
||||
const tag = ciphertext.slice(ciphertext.length - TAG_LENGTH)
|
||||
// set additional data
|
||||
cipher.setAAD(hash)
|
||||
cipher.setAuthTag(tag)
|
||||
@@ -94,8 +98,7 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
|
||||
let isFinished = false
|
||||
let sentIntro = false
|
||||
|
||||
const outBinary = new Binary()
|
||||
const inBinary = new Binary()
|
||||
let inBytes = Buffer.alloc(0)
|
||||
|
||||
authenticate(NOISE_WA_HEADER)
|
||||
authenticate(publicKey)
|
||||
@@ -114,7 +117,7 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
|
||||
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
||||
|
||||
const certDecoded = decrypt(serverHello!.payload!)
|
||||
const { details: certDetails, signature: certSignature } = proto.NoiseCertificate.decode(certDecoded)
|
||||
const { details: certDetails } = proto.NoiseCertificate.decode(certDecoded)
|
||||
|
||||
const { key: certKey } = proto.NoiseCertificateDetails.decode(certDetails)
|
||||
|
||||
@@ -133,47 +136,48 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
|
||||
}
|
||||
|
||||
const introSize = sentIntro ? 0 : NOISE_WA_HEADER.length
|
||||
|
||||
outBinary.ensureAdditionalCapacity(introSize + 3 + data.byteLength)
|
||||
const frame = Buffer.alloc(introSize + 3 + data.byteLength)
|
||||
|
||||
if(!sentIntro) {
|
||||
outBinary.writeByteArray(NOISE_WA_HEADER)
|
||||
frame.set(NOISE_WA_HEADER)
|
||||
sentIntro = true
|
||||
}
|
||||
|
||||
outBinary.writeUint8(data.byteLength >> 16)
|
||||
outBinary.writeUint16(65535 & data.byteLength)
|
||||
outBinary.write(data)
|
||||
frame.writeUInt8(data.byteLength >> 16, introSize)
|
||||
frame.writeUInt16BE(65535 & data.byteLength, introSize + 1)
|
||||
frame.set(data, introSize + 3)
|
||||
|
||||
const bytes = outBinary.readByteArray()
|
||||
return bytes as Uint8Array
|
||||
return frame
|
||||
},
|
||||
decodeFrame: (newData: Buffer | Uint8Array, onFrame: (buff: Uint8Array | BinaryNode) => void) => {
|
||||
// the binary protocol uses its own framing mechanism
|
||||
// on top of the WS frames
|
||||
// so we get this data and separate out the frames
|
||||
const getBytesSize = () => {
|
||||
return (inBinary.readUint8() << 16) | inBinary.readUint16()
|
||||
if(inBytes.length >= 3) {
|
||||
return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1)
|
||||
}
|
||||
}
|
||||
|
||||
const peekSize = () => {
|
||||
return !(inBinary.size() < 3) && getBytesSize() <= inBinary.size()
|
||||
}
|
||||
inBytes = Buffer.concat([ inBytes, newData ])
|
||||
|
||||
logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`)
|
||||
|
||||
let size = getBytesSize()
|
||||
while(size && inBytes.length >= size + 3) {
|
||||
let frame: Uint8Array | BinaryNode = inBytes.slice(3, size + 3)
|
||||
inBytes = inBytes.slice(size + 3)
|
||||
|
||||
inBinary.writeByteArray(newData)
|
||||
while(inBinary.peek(peekSize)) {
|
||||
const bytes = getBytesSize()
|
||||
let frame: Uint8Array | BinaryNode = inBinary.readByteArray(bytes)
|
||||
if(isFinished) {
|
||||
const result = decrypt(frame as Uint8Array)
|
||||
const unpacked = new Binary(result).decompressed()
|
||||
frame = decodeBinaryNode(unpacked)
|
||||
frame = decodeBinaryNode(result)
|
||||
}
|
||||
|
||||
onFrame(frame)
|
||||
}
|
||||
logger.trace({ msg: (frame as any)?.attrs?.id }, 'recv frame')
|
||||
|
||||
inBinary.peek(peekSize)
|
||||
onFrame(frame)
|
||||
size = getBytesSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
import * as libsignal from 'libsignal'
|
||||
import { proto } from '../../WAProto'
|
||||
import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage, SenderKeyName, SenderKeyRecord } from '../../WASignalGroup'
|
||||
import { AuthenticationCreds, KeyPair, SignalAuthState, SignalIdentity, SignalKeyStore, SignedKeyPair } from '../Types/Auth'
|
||||
import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, jidDecode, JidWithDevice } from '../WABinary'
|
||||
import { Curve } from './crypto'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { AuthenticationCreds, AuthenticationState, KeyPair, SignalAuthState, SignalIdentity, SignalKeyStore, SignedKeyPair } from '../Types/Auth'
|
||||
import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, jidDecode, JidWithDevice, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { Curve, generateSignalPubKey } from './crypto'
|
||||
import { encodeBigEndian } from './generics'
|
||||
|
||||
export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) => {
|
||||
const newPub = Buffer.alloc(33)
|
||||
newPub.set([5], 0)
|
||||
newPub.set(pubKey, 1)
|
||||
return newPub
|
||||
}
|
||||
|
||||
const jidToSignalAddress = (jid: string) => jid.split('@')[0]
|
||||
|
||||
export const jidToSignalProtocolAddress = (jid: string) => {
|
||||
@@ -60,7 +54,6 @@ export const generateOrGetPreKeys = (creds: AuthenticationCreds, range: number)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const xmppSignedPreKey = (key: SignedKeyPair): BinaryNode => (
|
||||
{
|
||||
tag: 'skey',
|
||||
@@ -273,4 +266,46 @@ export const extractDeviceJids = (result: BinaryNode, myJid: string, excludeZero
|
||||
}
|
||||
|
||||
return extracted
|
||||
}
|
||||
|
||||
/**
|
||||
* get the next N keys for upload or processing
|
||||
* @param count number of pre-keys to get or generate
|
||||
*/
|
||||
export const getNextPreKeys = async({ creds, keys }: AuthenticationState, count: number) => {
|
||||
const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(creds, count)
|
||||
|
||||
const update: Partial<AuthenticationCreds> = {
|
||||
nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId),
|
||||
firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1)
|
||||
}
|
||||
|
||||
await keys.set({ 'pre-key': newPreKeys })
|
||||
|
||||
const preKeys = await getPreKeys(keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1])
|
||||
|
||||
return { update, preKeys }
|
||||
}
|
||||
|
||||
export const getNextPreKeysNode = async(state: AuthenticationState, count: number) => {
|
||||
const { creds } = state
|
||||
const { update, preKeys } = await getNextPreKeys(state, count)
|
||||
|
||||
const node: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
xmlns: 'encrypt',
|
||||
type: 'set',
|
||||
to: S_WHATSAPP_NET,
|
||||
},
|
||||
content: [
|
||||
{ tag: 'registration', attrs: { }, content: encodeBigEndian(creds.registrationId) },
|
||||
{ tag: 'type', attrs: { }, content: KEY_BUNDLE_TYPE },
|
||||
{ tag: 'identity', attrs: { }, content: creds.signedIdentityKey.public },
|
||||
{ tag: 'list', attrs: { }, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) },
|
||||
xmppSignedPreKey(creds.signedPreKey)
|
||||
]
|
||||
}
|
||||
|
||||
return { update, node }
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { createHash } from 'crypto'
|
||||
import { proto } from '../../WAProto'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import type { AuthenticationCreds, SignalCreds, SocketConfig } from '../Types'
|
||||
import { BinaryNode, getAllBinaryNodeChildren, jidDecode, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { BinaryNode, getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { Curve, hmacSign } from './crypto'
|
||||
import { encodeInt } from './generics'
|
||||
import { encodeBigEndian } from './generics'
|
||||
import { createSignalIdentity } from './signal'
|
||||
|
||||
type ClientPayloadConfig = Pick<SocketConfig, 'version' | 'browser'>
|
||||
|
||||
const getUserAgent = ({ version }: Pick<SocketConfig, 'version'>): proto.IUserAgent => ({
|
||||
const getUserAgent = ({ version, browser }: ClientPayloadConfig): proto.IUserAgent => ({
|
||||
appVersion: {
|
||||
primary: version[0],
|
||||
secondary: version[1],
|
||||
@@ -19,12 +20,12 @@ const getUserAgent = ({ version }: Pick<SocketConfig, 'version'>): proto.IUserAg
|
||||
releaseChannel: proto.UserAgent.UserAgentReleaseChannel.RELEASE,
|
||||
mcc: '000',
|
||||
mnc: '000',
|
||||
osVersion: '0.1',
|
||||
osVersion: browser[2],
|
||||
manufacturer: '',
|
||||
device: 'Desktop',
|
||||
osBuildNumber: '0.1',
|
||||
osBuildNumber: browser[2],
|
||||
localeLanguageIso6391: 'en',
|
||||
localeCountryIso31661Alpha2: 'US',
|
||||
localeCountryIso31661Alpha2: 'en',
|
||||
})
|
||||
|
||||
const getWebInfo = (): proto.IWebInfo => ({
|
||||
@@ -33,7 +34,6 @@ const getWebInfo = (): proto.IWebInfo => ({
|
||||
|
||||
const getClientPayload = (config: ClientPayloadConfig): proto.IClientPayload => {
|
||||
return {
|
||||
passive: true,
|
||||
connectType: proto.ClientPayload.ClientPayloadConnectType.WIFI_UNKNOWN,
|
||||
connectReason: proto.ClientPayload.ClientPayloadConnectReason.USER_ACTIVATED,
|
||||
userAgent: getUserAgent(config),
|
||||
@@ -45,6 +45,7 @@ export const generateLoginNode = (userJid: string, config: ClientPayloadConfig):
|
||||
const { user, device } = jidDecode(userJid)
|
||||
const payload: proto.IClientPayload = {
|
||||
...getClientPayload(config),
|
||||
passive: true,
|
||||
username: +user,
|
||||
device: device,
|
||||
}
|
||||
@@ -65,11 +66,11 @@ export const generateRegistrationNode = (
|
||||
const companion: proto.ICompanionProps = {
|
||||
os: config.browser[0],
|
||||
version: {
|
||||
primary: +(browserVersion[0] || 10),
|
||||
secondary: +(browserVersion[1] || 0),
|
||||
primary: +(browserVersion[0] || 0),
|
||||
secondary: +(browserVersion[1] || 1),
|
||||
tertiary: +(browserVersion[2] || 0),
|
||||
},
|
||||
platformType: proto.CompanionProps.CompanionPropsPlatformType[config.browser[1].toUpperCase()] || proto.CompanionProps.CompanionPropsPlatformType.CHROME,
|
||||
platformType: proto.CompanionProps.CompanionPropsPlatformType[config.browser[1].toUpperCase()] || proto.CompanionProps.CompanionPropsPlatformType.UNKNOWN,
|
||||
requireFullSync: false,
|
||||
}
|
||||
|
||||
@@ -77,13 +78,14 @@ export const generateRegistrationNode = (
|
||||
|
||||
const registerPayload: proto.IClientPayload = {
|
||||
...getClientPayload(config),
|
||||
passive: false,
|
||||
regData: {
|
||||
buildHash: appVersionBuf,
|
||||
companionProps: companionProto,
|
||||
eRegid: encodeInt(4, registrationId),
|
||||
eKeytype: encodeInt(1, 5),
|
||||
eRegid: encodeBigEndian(registrationId),
|
||||
eKeytype: KEY_BUNDLE_TYPE,
|
||||
eIdent: signedIdentityKey.public,
|
||||
eSkeyId: encodeInt(3, signedPreKey.keyId),
|
||||
eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
|
||||
eSkeyVal: signedPreKey.keyPair.public,
|
||||
eSkeySig: signedPreKey.signature,
|
||||
},
|
||||
@@ -96,51 +98,47 @@ export const configureSuccessfulPairing = (
|
||||
stanza: BinaryNode,
|
||||
{ advSecretKey, signedIdentityKey, signalIdentities }: Pick<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>
|
||||
) => {
|
||||
const [pair] = getAllBinaryNodeChildren(stanza)
|
||||
const pairContent = Array.isArray(pair.content) ? pair.content : []
|
||||
|
||||
const msgId = stanza.attrs.id
|
||||
const deviceIdentity = pairContent.find(m => m.tag === 'device-identity')?.content
|
||||
const businessName = pairContent.find(m => m.tag === 'biz')?.attrs?.name
|
||||
const verifiedName = businessName || ''
|
||||
const jid = pairContent.find(m => m.tag === 'device')?.attrs?.jid
|
||||
|
||||
const { details, hmac } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentity as Buffer)
|
||||
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success')
|
||||
|
||||
const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity')
|
||||
const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform')
|
||||
const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device')
|
||||
const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz')
|
||||
|
||||
if(!deviceIdentityNode || !deviceNode) {
|
||||
throw new Boom('Missing device-identity or device in pair success node', { data: stanza })
|
||||
}
|
||||
|
||||
const bizName = businessNode?.attrs.name
|
||||
const jid = deviceNode.attrs.jid
|
||||
|
||||
const { details, hmac } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content as Buffer)
|
||||
// check HMAC matches
|
||||
const advSign = hmacSign(details, Buffer.from(advSecretKey, 'base64'))
|
||||
|
||||
if(Buffer.compare(hmac, advSign) !== 0) {
|
||||
throw new Boom('Invalid pairing')
|
||||
throw new Boom('Invalid account signature')
|
||||
}
|
||||
|
||||
const account = proto.ADVSignedDeviceIdentity.decode(details)
|
||||
const { accountSignatureKey, accountSignature } = account
|
||||
|
||||
const accountMsg = Buffer.concat([
|
||||
Buffer.from([6, 0]),
|
||||
account.details,
|
||||
signedIdentityKey.public
|
||||
])
|
||||
const { accountSignatureKey, accountSignature, details: deviceDetails } = account
|
||||
// verify the device signature matches
|
||||
const accountMsg = Buffer.concat([ Buffer.from([6, 0]), deviceDetails, signedIdentityKey.public ])
|
||||
if(!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
|
||||
throw new Boom('Failed to verify account signature')
|
||||
}
|
||||
|
||||
const deviceMsg = Buffer.concat([
|
||||
new Uint8Array([6, 1]),
|
||||
account.details,
|
||||
signedIdentityKey.public,
|
||||
account.accountSignatureKey
|
||||
])
|
||||
// sign the details with our identity key
|
||||
const deviceMsg = Buffer.concat([ Buffer.from([6, 1]), deviceDetails, signedIdentityKey.public, accountSignatureKey ])
|
||||
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
|
||||
// do not provide the "accountSignatureKey" back
|
||||
account.accountSignatureKey = null
|
||||
|
||||
const identity = createSignalIdentity(jid, accountSignatureKey)
|
||||
const accountEnc = proto.ADVSignedDeviceIdentity.encode(account).finish()
|
||||
|
||||
const keyIndex = proto.ADVDeviceIdentity.decode(account.details).keyIndex
|
||||
|
||||
const accountEnc = proto.ADVSignedDeviceIdentity.encode({
|
||||
...account.toJSON(),
|
||||
accountSignatureKey: undefined
|
||||
}).finish()
|
||||
const deviceIdentity = proto.ADVDeviceIdentity.decode(account.details)
|
||||
|
||||
const reply: BinaryNode = {
|
||||
tag: 'iq',
|
||||
@@ -156,7 +154,7 @@ export const configureSuccessfulPairing = (
|
||||
content: [
|
||||
{
|
||||
tag: 'device-identity',
|
||||
attrs: { 'key-index': `${keyIndex}` },
|
||||
attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
|
||||
content: accountEnc
|
||||
}
|
||||
]
|
||||
@@ -166,9 +164,14 @@ export const configureSuccessfulPairing = (
|
||||
|
||||
const authUpdate: Partial<AuthenticationCreds> = {
|
||||
account,
|
||||
me: { id: jid, verifiedName },
|
||||
signalIdentities: [...(signalIdentities || []), identity]
|
||||
me: { id: jid, name: bizName },
|
||||
signalIdentities: [
|
||||
...(signalIdentities || []),
|
||||
identity
|
||||
],
|
||||
platform: platformNode?.attrs.name
|
||||
}
|
||||
|
||||
return {
|
||||
creds: authUpdate,
|
||||
reply
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
|
||||
export const Tags = {
|
||||
LIST_EMPTY: 0,
|
||||
STREAM_END: 2,
|
||||
DICTIONARY_0: 236,
|
||||
DICTIONARY_1: 237,
|
||||
DICTIONARY_2: 238,
|
||||
DICTIONARY_3: 239,
|
||||
LIST_8: 248,
|
||||
LIST_16: 249,
|
||||
JID_PAIR: 250,
|
||||
HEX_8: 251,
|
||||
BINARY_8: 252,
|
||||
BINARY_20: 253,
|
||||
BINARY_32: 254,
|
||||
NIBBLE_8: 255,
|
||||
SINGLE_BYTE_MAX: 256,
|
||||
PACKED_MAX: 254,
|
||||
}
|
||||
export const DoubleByteTokens = []
|
||||
export const SingleByteTokens = [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'200',
|
||||
'400',
|
||||
'404',
|
||||
'500',
|
||||
'501',
|
||||
'502',
|
||||
'action',
|
||||
'add',
|
||||
'after',
|
||||
'archive',
|
||||
'author',
|
||||
'available',
|
||||
'battery',
|
||||
'before',
|
||||
'body',
|
||||
'broadcast',
|
||||
'chat',
|
||||
'clear',
|
||||
'code',
|
||||
'composing',
|
||||
'contacts',
|
||||
'count',
|
||||
'create',
|
||||
'debug',
|
||||
'delete',
|
||||
'demote',
|
||||
'duplicate',
|
||||
'encoding',
|
||||
'error',
|
||||
'false',
|
||||
'filehash',
|
||||
'from',
|
||||
'g.us',
|
||||
'group',
|
||||
'groups_v2',
|
||||
'height',
|
||||
'id',
|
||||
'image',
|
||||
'in',
|
||||
'index',
|
||||
'invis',
|
||||
'item',
|
||||
'jid',
|
||||
'kind',
|
||||
'last',
|
||||
'leave',
|
||||
'live',
|
||||
'log',
|
||||
'media',
|
||||
'message',
|
||||
'mimetype',
|
||||
'missing',
|
||||
'modify',
|
||||
'name',
|
||||
'notification',
|
||||
'notify',
|
||||
'out',
|
||||
'owner',
|
||||
'participant',
|
||||
'paused',
|
||||
'picture',
|
||||
'played',
|
||||
'presence',
|
||||
'preview',
|
||||
'promote',
|
||||
'query',
|
||||
'raw',
|
||||
'read',
|
||||
'receipt',
|
||||
'received',
|
||||
'recipient',
|
||||
'recording',
|
||||
'relay',
|
||||
'remove',
|
||||
'response',
|
||||
'resume',
|
||||
'retry',
|
||||
's.whatsapp.net',
|
||||
'seconds',
|
||||
'set',
|
||||
'size',
|
||||
'status',
|
||||
'subject',
|
||||
'subscribe',
|
||||
't',
|
||||
'text',
|
||||
'to',
|
||||
'true',
|
||||
'type',
|
||||
'unarchive',
|
||||
'unavailable',
|
||||
'url',
|
||||
'user',
|
||||
'value',
|
||||
'web',
|
||||
'width',
|
||||
'mute',
|
||||
'read_only',
|
||||
'admin',
|
||||
'creator',
|
||||
'short',
|
||||
'update',
|
||||
'powersave',
|
||||
'checksum',
|
||||
'epoch',
|
||||
'block',
|
||||
'previous',
|
||||
'409',
|
||||
'replaced',
|
||||
'reason',
|
||||
'spam',
|
||||
'modify_tag',
|
||||
'message_info',
|
||||
'delivery',
|
||||
'emoji',
|
||||
'title',
|
||||
'description',
|
||||
'canonical-url',
|
||||
'matched-text',
|
||||
'star',
|
||||
'unstar',
|
||||
'media_key',
|
||||
'filename',
|
||||
'identity',
|
||||
'unread',
|
||||
'page',
|
||||
'page_count',
|
||||
'search',
|
||||
'media_message',
|
||||
'security',
|
||||
'call_log',
|
||||
'profile',
|
||||
'ciphertext',
|
||||
'invite',
|
||||
'gif',
|
||||
'vcard',
|
||||
'frequent',
|
||||
'privacy',
|
||||
'blacklist',
|
||||
'whitelist',
|
||||
'verify',
|
||||
'location',
|
||||
'document',
|
||||
'elapsed',
|
||||
'revoke_invite',
|
||||
'expiration',
|
||||
'unsubscribe',
|
||||
'disable',
|
||||
'vname',
|
||||
'old_jid',
|
||||
'new_jid',
|
||||
'announcement',
|
||||
'locked',
|
||||
'prop',
|
||||
'label',
|
||||
'color',
|
||||
'call',
|
||||
'offer',
|
||||
'call-id',
|
||||
'quick_reply',
|
||||
'sticker',
|
||||
'pay_t',
|
||||
'accept',
|
||||
'reject',
|
||||
'sticker_pack',
|
||||
'invalid',
|
||||
'canceled',
|
||||
'missed',
|
||||
'connected',
|
||||
'result',
|
||||
'audio',
|
||||
'video',
|
||||
'recent',
|
||||
]
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
import { BinaryNode } from '../types'
|
||||
import { DoubleByteTokens, SingleByteTokens, Tags } from './constants'
|
||||
import { DOUBLE_BYTE_TOKENS, SINGLE_BYTE_TOKENS, TAGS } from '../constants'
|
||||
import type { BinaryNode } from '../types'
|
||||
|
||||
export const isLegacyBinaryNode = (buffer: Buffer) => {
|
||||
switch (buffer[0]) {
|
||||
case Tags.LIST_EMPTY:
|
||||
case Tags.LIST_8:
|
||||
case Tags.LIST_16:
|
||||
case TAGS.LIST_EMPTY:
|
||||
case TAGS.LIST_8:
|
||||
case TAGS.LIST_16:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -89,9 +89,9 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
}
|
||||
|
||||
const unpackByte = (tag: number, value: number) => {
|
||||
if(tag === Tags.NIBBLE_8) {
|
||||
if(tag === TAGS.NIBBLE_8) {
|
||||
return unpackNibble(value)
|
||||
} else if(tag === Tags.HEX_8) {
|
||||
} else if(tag === TAGS.HEX_8) {
|
||||
return unpackHex(value)
|
||||
} else {
|
||||
throw new Error('unknown tag: ' + tag)
|
||||
@@ -116,16 +116,16 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
}
|
||||
|
||||
const isListTag = (tag: number) => {
|
||||
return tag === Tags.LIST_EMPTY || tag === Tags.LIST_8 || tag === Tags.LIST_16
|
||||
return tag === TAGS.LIST_EMPTY || tag === TAGS.LIST_8 || tag === TAGS.LIST_16
|
||||
}
|
||||
|
||||
const readListSize = (tag: number) => {
|
||||
switch (tag) {
|
||||
case Tags.LIST_EMPTY:
|
||||
case TAGS.LIST_EMPTY:
|
||||
return 0
|
||||
case Tags.LIST_8:
|
||||
case TAGS.LIST_8:
|
||||
return readByte()
|
||||
case Tags.LIST_16:
|
||||
case TAGS.LIST_16:
|
||||
return readInt(2)
|
||||
default:
|
||||
throw new Error('invalid tag for list size: ' + tag)
|
||||
@@ -133,11 +133,11 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
}
|
||||
|
||||
const getToken = (index: number) => {
|
||||
if(index < 3 || index >= SingleByteTokens.length) {
|
||||
if(index < 3 || index >= SINGLE_BYTE_TOKENS.length) {
|
||||
throw new Error('invalid token index: ' + index)
|
||||
}
|
||||
|
||||
return SingleByteTokens[index]
|
||||
return SINGLE_BYTE_TOKENS[index]
|
||||
}
|
||||
|
||||
const readString = (tag: number) => {
|
||||
@@ -147,20 +147,20 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case Tags.DICTIONARY_0:
|
||||
case Tags.DICTIONARY_1:
|
||||
case Tags.DICTIONARY_2:
|
||||
case Tags.DICTIONARY_3:
|
||||
return getTokenDouble(tag - Tags.DICTIONARY_0, readByte())
|
||||
case Tags.LIST_EMPTY:
|
||||
case TAGS.DICTIONARY_0:
|
||||
case TAGS.DICTIONARY_1:
|
||||
case TAGS.DICTIONARY_2:
|
||||
case TAGS.DICTIONARY_3:
|
||||
return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte())
|
||||
case TAGS.LIST_EMPTY:
|
||||
return null
|
||||
case Tags.BINARY_8:
|
||||
case TAGS.BINARY_8:
|
||||
return readStringFromChars(readByte())
|
||||
case Tags.BINARY_20:
|
||||
case TAGS.BINARY_20:
|
||||
return readStringFromChars(readInt20())
|
||||
case Tags.BINARY_32:
|
||||
case TAGS.BINARY_32:
|
||||
return readStringFromChars(readInt(4))
|
||||
case Tags.JID_PAIR:
|
||||
case TAGS.JID_PAIR:
|
||||
const i = readString(readByte())
|
||||
const j = readString(readByte())
|
||||
if(typeof i === 'string' && j) {
|
||||
@@ -168,8 +168,8 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
}
|
||||
|
||||
throw new Error('invalid jid pair: ' + i + ', ' + j)
|
||||
case Tags.HEX_8:
|
||||
case Tags.NIBBLE_8:
|
||||
case TAGS.HEX_8:
|
||||
case TAGS.NIBBLE_8:
|
||||
return readPacked8(tag)
|
||||
default:
|
||||
throw new Error('invalid string with tag: ' + tag)
|
||||
@@ -181,16 +181,16 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
)
|
||||
const getTokenDouble = (index1: number, index2: number) => {
|
||||
const n = 256 * index1 + index2
|
||||
if(n < 0 || n > DoubleByteTokens.length) {
|
||||
if(n < 0 || n > DOUBLE_BYTE_TOKENS.length) {
|
||||
throw new Error('invalid double token index: ' + n)
|
||||
}
|
||||
|
||||
return DoubleByteTokens[n]
|
||||
return DOUBLE_BYTE_TOKENS[n]
|
||||
}
|
||||
|
||||
const listSize = readListSize(readByte())
|
||||
const descrTag = readByte()
|
||||
if(descrTag === Tags.STREAM_END) {
|
||||
if(descrTag === TAGS.STREAM_END) {
|
||||
throw new Error('unexpected stream end')
|
||||
}
|
||||
|
||||
@@ -217,13 +217,13 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
} else {
|
||||
let decoded: Buffer | string
|
||||
switch (tag) {
|
||||
case Tags.BINARY_8:
|
||||
case TAGS.BINARY_8:
|
||||
decoded = readBytes(readByte())
|
||||
break
|
||||
case Tags.BINARY_20:
|
||||
case TAGS.BINARY_20:
|
||||
decoded = readBytes(readInt20())
|
||||
break
|
||||
case Tags.BINARY_32:
|
||||
case TAGS.BINARY_32:
|
||||
decoded = readBytes(readInt(4))
|
||||
break
|
||||
default:
|
||||
@@ -265,13 +265,13 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
|
||||
}
|
||||
|
||||
if(length >= 1 << 20) {
|
||||
pushByte(Tags.BINARY_32)
|
||||
pushByte(TAGS.BINARY_32)
|
||||
pushInt(length, 4) // 32 bit integer
|
||||
} else if(length >= 256) {
|
||||
pushByte(Tags.BINARY_20)
|
||||
pushByte(TAGS.BINARY_20)
|
||||
pushInt20(length)
|
||||
} else {
|
||||
pushByte(Tags.BINARY_8)
|
||||
pushByte(TAGS.BINARY_8)
|
||||
pushByte(length)
|
||||
}
|
||||
}
|
||||
@@ -295,20 +295,20 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
|
||||
token = 's.whatsapp.net'
|
||||
}
|
||||
|
||||
const tokenIndex = SingleByteTokens.indexOf(token)
|
||||
const tokenIndex = SINGLE_BYTE_TOKENS.indexOf(token)
|
||||
if(!i && token === 's.whatsapp.net') {
|
||||
writeToken(tokenIndex)
|
||||
} else if(tokenIndex >= 0) {
|
||||
if(tokenIndex < Tags.SINGLE_BYTE_MAX) {
|
||||
if(tokenIndex < TAGS.SINGLE_BYTE_MAX) {
|
||||
writeToken(tokenIndex)
|
||||
} else {
|
||||
const overflow = tokenIndex - Tags.SINGLE_BYTE_MAX
|
||||
const overflow = tokenIndex - TAGS.SINGLE_BYTE_MAX
|
||||
const dictionaryIndex = overflow >> 8
|
||||
if(dictionaryIndex < 0 || dictionaryIndex > 3) {
|
||||
throw new Error('double byte dict token out of range: ' + token + ', ' + tokenIndex)
|
||||
}
|
||||
|
||||
writeToken(Tags.DICTIONARY_0 + dictionaryIndex)
|
||||
writeToken(TAGS.DICTIONARY_0 + dictionaryIndex)
|
||||
writeToken(overflow % 256)
|
||||
}
|
||||
} else if(token) {
|
||||
@@ -322,18 +322,18 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
|
||||
}
|
||||
|
||||
const writeJid = (left: string, right: string) => {
|
||||
pushByte(Tags.JID_PAIR)
|
||||
left && left.length > 0 ? writeString(left) : writeToken(Tags.LIST_EMPTY)
|
||||
pushByte(TAGS.JID_PAIR)
|
||||
left && left.length > 0 ? writeString(left) : writeToken(TAGS.LIST_EMPTY)
|
||||
writeString(right)
|
||||
}
|
||||
|
||||
const writeListStart = (listSize: number) => {
|
||||
if(listSize === 0) {
|
||||
pushByte(Tags.LIST_EMPTY)
|
||||
pushByte(TAGS.LIST_EMPTY)
|
||||
} else if(listSize < 256) {
|
||||
pushBytes([Tags.LIST_8, listSize])
|
||||
pushBytes([TAGS.LIST_8, listSize])
|
||||
} else {
|
||||
pushBytes([Tags.LIST_16, listSize])
|
||||
pushBytes([TAGS.LIST_16, listSize])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
43
src/WABinary/constants.ts
Normal file
43
src/WABinary/constants.ts
Normal file
File diff suppressed because one or more lines are too long
260
src/WABinary/decode.ts
Normal file
260
src/WABinary/decode.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import { inflateSync } from 'zlib'
|
||||
import { DOUBLE_BYTE_TOKENS, SINGLE_BYTE_TOKENS, TAGS } from './constants'
|
||||
import { jidEncode } from './jid-utils'
|
||||
import type { BinaryNode } from './types'
|
||||
|
||||
export const decompressingIfRequired = (buffer: Buffer) => {
|
||||
if(2 & buffer.readUInt8()) {
|
||||
buffer = inflateSync(buffer.slice(1))
|
||||
} else { // nodes with no compression have a 0x00 prefix, we remove that
|
||||
buffer = buffer.slice(1)
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
export const decodeDecompressedBinaryNode = (buffer: Buffer, indexRef: { index: number } = { index: 0 }): BinaryNode => {
|
||||
|
||||
const checkEOS = (length: number) => {
|
||||
if(indexRef.index + length > buffer.length) {
|
||||
throw new Error('end of stream')
|
||||
}
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
const value = buffer[indexRef.index]
|
||||
indexRef.index += 1
|
||||
return value
|
||||
}
|
||||
|
||||
const readByte = () => {
|
||||
checkEOS(1)
|
||||
return next()
|
||||
}
|
||||
|
||||
const readBytes = (n: number) => {
|
||||
checkEOS(n)
|
||||
const value = buffer.slice(indexRef.index, indexRef.index + n)
|
||||
indexRef.index += n
|
||||
return value
|
||||
}
|
||||
|
||||
const readStringFromChars = (length: number) => {
|
||||
return readBytes(length).toString('utf-8')
|
||||
}
|
||||
|
||||
const readInt = (n: number, littleEndian = false) => {
|
||||
checkEOS(n)
|
||||
let val = 0
|
||||
for(let i = 0; i < n; i++) {
|
||||
const shift = littleEndian ? i : n - 1 - i
|
||||
val |= next() << (shift * 8)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
const readInt20 = () => {
|
||||
checkEOS(3)
|
||||
return ((next() & 15) << 16) + (next() << 8) + next()
|
||||
}
|
||||
|
||||
const unpackHex = (value: number) => {
|
||||
if(value >= 0 && value < 16) {
|
||||
return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10
|
||||
}
|
||||
|
||||
throw new Error('invalid hex: ' + value)
|
||||
}
|
||||
|
||||
const unpackNibble = (value: number) => {
|
||||
if(value >= 0 && value <= 9) {
|
||||
return '0'.charCodeAt(0) + value
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 10:
|
||||
return '-'.charCodeAt(0)
|
||||
case 11:
|
||||
return '.'.charCodeAt(0)
|
||||
case 15:
|
||||
return '\0'.charCodeAt(0)
|
||||
default:
|
||||
throw new Error('invalid nibble: ' + value)
|
||||
}
|
||||
}
|
||||
|
||||
const unpackByte = (tag: number, value: number) => {
|
||||
if(tag === TAGS.NIBBLE_8) {
|
||||
return unpackNibble(value)
|
||||
} else if(tag === TAGS.HEX_8) {
|
||||
return unpackHex(value)
|
||||
} else {
|
||||
throw new Error('unknown tag: ' + tag)
|
||||
}
|
||||
}
|
||||
|
||||
const readPacked8 = (tag: number) => {
|
||||
const startByte = readByte()
|
||||
let value = ''
|
||||
|
||||
for(let i = 0; i < (startByte & 127); i++) {
|
||||
const curByte = readByte()
|
||||
value += String.fromCharCode(unpackByte(tag, (curByte & 0xf0) >> 4))
|
||||
value += String.fromCharCode(unpackByte(tag, curByte & 0x0f))
|
||||
}
|
||||
|
||||
if(startByte >> 7 !== 0) {
|
||||
value = value.slice(0, -1)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
const isListTag = (tag: number) => {
|
||||
return tag === TAGS.LIST_EMPTY || tag === TAGS.LIST_8 || tag === TAGS.LIST_16
|
||||
}
|
||||
|
||||
const readListSize = (tag: number) => {
|
||||
switch (tag) {
|
||||
case TAGS.LIST_EMPTY:
|
||||
return 0
|
||||
case TAGS.LIST_8:
|
||||
return readByte()
|
||||
case TAGS.LIST_16:
|
||||
return readInt(2)
|
||||
default:
|
||||
throw new Error('invalid tag for list size: ' + tag)
|
||||
}
|
||||
}
|
||||
|
||||
const readJidPair = () => {
|
||||
const i = readString(readByte())
|
||||
const j = readString(readByte())
|
||||
if(j) {
|
||||
return (i || '') + '@' + j
|
||||
}
|
||||
|
||||
throw new Error('invalid jid pair: ' + i + ', ' + j)
|
||||
}
|
||||
|
||||
const readAdJid = () => {
|
||||
const agent = readByte()
|
||||
const device = readByte()
|
||||
const user = readString(readByte())
|
||||
|
||||
return jidEncode(user, 's.whatsapp.net', device, agent)
|
||||
}
|
||||
|
||||
const readString = (tag: number): string => {
|
||||
if(tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) {
|
||||
return SINGLE_BYTE_TOKENS[tag]
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case TAGS.DICTIONARY_0:
|
||||
case TAGS.DICTIONARY_1:
|
||||
case TAGS.DICTIONARY_2:
|
||||
case TAGS.DICTIONARY_3:
|
||||
return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte())
|
||||
case TAGS.LIST_EMPTY:
|
||||
return null
|
||||
case TAGS.BINARY_8:
|
||||
return readStringFromChars(readByte())
|
||||
case TAGS.BINARY_20:
|
||||
return readStringFromChars(readInt20())
|
||||
case TAGS.BINARY_32:
|
||||
return readStringFromChars(readInt(4))
|
||||
case TAGS.JID_PAIR:
|
||||
return readJidPair()
|
||||
case TAGS.AD_JID:
|
||||
return readAdJid()
|
||||
case TAGS.HEX_8:
|
||||
case TAGS.NIBBLE_8:
|
||||
return readPacked8(tag)
|
||||
default:
|
||||
throw new Error('invalid string with tag: ' + tag)
|
||||
}
|
||||
}
|
||||
|
||||
const readList = (tag: number) => {
|
||||
const items: BinaryNode[] = []
|
||||
const size = readListSize(tag)
|
||||
for(let i = 0;i < size;i++) {
|
||||
items.push(decodeDecompressedBinaryNode(buffer, indexRef))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
const getTokenDouble = (index1: number, index2: number) => {
|
||||
const dict = DOUBLE_BYTE_TOKENS[index1]
|
||||
if(!dict) {
|
||||
throw new Error(`Invalid double token dict (${index1})`)
|
||||
}
|
||||
|
||||
const value = dict[index2]
|
||||
if(typeof value === 'undefined') {
|
||||
throw new Error(`Invalid double token (${index2})`)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
const listSize = readListSize(readByte())
|
||||
const header = readString(readByte())
|
||||
if(!listSize || !header.length) {
|
||||
throw new Error('invalid node')
|
||||
}
|
||||
|
||||
const attrs: BinaryNode['attrs'] = { }
|
||||
let data: BinaryNode['content']
|
||||
if(listSize === 0 || !header) {
|
||||
throw new Error('invalid node')
|
||||
}
|
||||
|
||||
// read the attributes in
|
||||
const attributesLength = (listSize - 1) >> 1
|
||||
for(let i = 0; i < attributesLength; i++) {
|
||||
const key = readString(readByte())
|
||||
const value = readString(readByte())
|
||||
|
||||
attrs[key] = value
|
||||
}
|
||||
|
||||
if(listSize % 2 === 0) {
|
||||
const tag = readByte()
|
||||
if(isListTag(tag)) {
|
||||
data = readList(tag)
|
||||
} else {
|
||||
let decoded: Buffer | string
|
||||
switch (tag) {
|
||||
case TAGS.BINARY_8:
|
||||
decoded = readBytes(readByte())
|
||||
break
|
||||
case TAGS.BINARY_20:
|
||||
decoded = readBytes(readInt20())
|
||||
break
|
||||
case TAGS.BINARY_32:
|
||||
decoded = readBytes(readInt(4))
|
||||
break
|
||||
default:
|
||||
decoded = readString(tag)
|
||||
break
|
||||
}
|
||||
|
||||
data = decoded
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tag: header,
|
||||
attrs,
|
||||
content: data
|
||||
}
|
||||
}
|
||||
|
||||
export const decodeBinaryNode = (buff: Buffer): BinaryNode => {
|
||||
const decompBuff = decompressingIfRequired(buff)
|
||||
return decodeDecompressedBinaryNode(decompBuff)
|
||||
}
|
||||
229
src/WABinary/encode.ts
Normal file
229
src/WABinary/encode.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
|
||||
import { TAGS, TOKEN_MAP } from './constants'
|
||||
import { jidDecode } from './jid-utils'
|
||||
import type { BinaryNode } from './types'
|
||||
|
||||
export const encodeBinaryNode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = [0]) => {
|
||||
|
||||
const pushByte = (value: number) => buffer.push(value & 0xff)
|
||||
|
||||
const pushInt = (value: number, n: number, littleEndian = false) => {
|
||||
for(let i = 0; i < n; i++) {
|
||||
const curShift = littleEndian ? i : n - 1 - i
|
||||
buffer.push((value >> (curShift * 8)) & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
const pushBytes = (bytes: Uint8Array | Buffer | number[]) => (
|
||||
bytes.forEach (b => buffer.push(b))
|
||||
)
|
||||
const pushInt20 = (value: number) => (
|
||||
pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff])
|
||||
)
|
||||
const writeByteLength = (length: number) => {
|
||||
if(length >= 4294967296) {
|
||||
throw new Error('string too large to encode: ' + length)
|
||||
}
|
||||
|
||||
if(length >= 1 << 20) {
|
||||
pushByte(TAGS.BINARY_32)
|
||||
pushInt(length, 4) // 32 bit integer
|
||||
} else if(length >= 256) {
|
||||
pushByte(TAGS.BINARY_20)
|
||||
pushInt20(length)
|
||||
} else {
|
||||
pushByte(TAGS.BINARY_8)
|
||||
pushByte(length)
|
||||
}
|
||||
}
|
||||
|
||||
const writeStringRaw = (str: string) => {
|
||||
const bytes = Buffer.from (str, 'utf-8')
|
||||
writeByteLength(bytes.length)
|
||||
pushBytes(bytes)
|
||||
}
|
||||
|
||||
const writeJid = ({ agent, device, user, server }: ReturnType<typeof jidDecode>) => {
|
||||
if(typeof agent !== 'undefined' || typeof device !== 'undefined') {
|
||||
pushByte(TAGS.AD_JID)
|
||||
pushByte(agent || 0)
|
||||
pushByte(device || 0)
|
||||
writeString(user)
|
||||
} else {
|
||||
pushByte(TAGS.JID_PAIR)
|
||||
if(user.length) {
|
||||
writeString(user)
|
||||
} else {
|
||||
pushByte(TAGS.LIST_EMPTY)
|
||||
}
|
||||
|
||||
writeString(server)
|
||||
}
|
||||
}
|
||||
|
||||
const packNibble = (char: string) => {
|
||||
switch (char) {
|
||||
case '-':
|
||||
return 10
|
||||
case '.':
|
||||
return 11
|
||||
case '\0':
|
||||
return 15
|
||||
default:
|
||||
if(char >= '0' && char <= '9') {
|
||||
return char.charCodeAt(0) - '0'.charCodeAt(0)
|
||||
}
|
||||
|
||||
throw new Error(`invalid byte for nibble "${char}"`)
|
||||
}
|
||||
}
|
||||
|
||||
const packHex = (char: string) => {
|
||||
if(char >= '0' && char <= '9') {
|
||||
return char.charCodeAt(0) - '0'.charCodeAt(0)
|
||||
}
|
||||
|
||||
if(char >= 'A' && char <= 'F') {
|
||||
return 10 + char.charCodeAt(0) - 'A'.charCodeAt(0)
|
||||
}
|
||||
|
||||
if(char >= 'a' && char <= 'f') {
|
||||
return 10 + char.charCodeAt(0) - 'a'.charCodeAt(0)
|
||||
}
|
||||
|
||||
if(char === '\0') {
|
||||
return 15
|
||||
}
|
||||
|
||||
throw new Error(`Invalid hex char "${char}"`)
|
||||
}
|
||||
|
||||
const writePackedBytes = (str: string, type: 'nibble' | 'hex') => {
|
||||
if(str.length > TAGS.PACKED_MAX) {
|
||||
throw new Error('Too many bytes to pack')
|
||||
}
|
||||
|
||||
pushByte(type === 'nibble' ? TAGS.NIBBLE_8 : TAGS.HEX_8)
|
||||
|
||||
let roundedLength = Math.ceil(str.length / 2.0)
|
||||
if(str.length % 2 !== 0) {
|
||||
roundedLength |= 128
|
||||
}
|
||||
|
||||
pushByte(roundedLength)
|
||||
const packFunction = type === 'nibble' ? packNibble : packHex
|
||||
|
||||
const packBytePair = (v1: string, v2: string) => {
|
||||
const result = (packFunction(v1) << 4) | packFunction(v2)
|
||||
return result
|
||||
}
|
||||
|
||||
const strLengthHalf = Math.floor(str.length / 2)
|
||||
for(let i = 0; i < strLengthHalf;i++) {
|
||||
pushByte(packBytePair(str[2 * i], str[2 * i + 1]))
|
||||
}
|
||||
|
||||
if(str.length % 2 !== 0) {
|
||||
pushByte(packBytePair(str[str.length - 1], '\x00'))
|
||||
}
|
||||
}
|
||||
|
||||
const isNibble = (str: string) => {
|
||||
if(str.length > TAGS.PACKED_MAX) {
|
||||
return false
|
||||
}
|
||||
|
||||
for(let i = 0;i < str.length;i++) {
|
||||
const char = str[i]
|
||||
const isInNibbleRange = char >= '0' && char <= '9'
|
||||
if(!isInNibbleRange && char !== '-' && char !== '.') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const isHex = (str: string) => {
|
||||
if(str.length > TAGS.PACKED_MAX) {
|
||||
return false
|
||||
}
|
||||
|
||||
for(let i = 0;i < str.length;i++) {
|
||||
const char = str[i]
|
||||
const isInNibbleRange = char >= '0' && char <= '9'
|
||||
if(!isInNibbleRange && !(char >= 'A' && char <= 'F') && !(char >= 'a' && char <= 'f')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const writeString = (str: string) => {
|
||||
// console.log('before write of ', str, ' ', Buffer.from(buffer).toString('hex'))
|
||||
const tokenIndex = TOKEN_MAP[str]
|
||||
if(tokenIndex) {
|
||||
if(typeof tokenIndex.dict === 'number') {
|
||||
pushByte(TAGS.DICTIONARY_0 + tokenIndex.dict)
|
||||
}
|
||||
|
||||
pushByte(tokenIndex.index)
|
||||
} else if(isNibble(str)) {
|
||||
writePackedBytes(str, 'nibble')
|
||||
} else if(isHex(str)) {
|
||||
writePackedBytes(str, 'hex')
|
||||
} else if(str) {
|
||||
const decodedJid = jidDecode(str)
|
||||
if(decodedJid) {
|
||||
writeJid(decodedJid)
|
||||
} else {
|
||||
writeStringRaw(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const writeListStart = (listSize: number) => {
|
||||
if(listSize === 0) {
|
||||
pushByte(TAGS.LIST_EMPTY)
|
||||
} else if(listSize < 256) {
|
||||
pushBytes([TAGS.LIST_8, listSize])
|
||||
} else {
|
||||
pushBytes([TAGS.LIST_16, listSize])
|
||||
}
|
||||
}
|
||||
|
||||
const validAttributes = Object.keys(attrs).filter(k => (
|
||||
typeof attrs[k] !== 'undefined' && attrs[k] !== null
|
||||
))
|
||||
|
||||
writeListStart(2 * validAttributes.length + 1 + (typeof content !== 'undefined' && content !== null ? 1 : 0))
|
||||
writeString(tag)
|
||||
|
||||
for(const key of validAttributes) {
|
||||
if(typeof attrs[key] === 'string') {
|
||||
writeString(key)
|
||||
writeString(attrs[key])
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof content === 'string') {
|
||||
writeString(content)
|
||||
} else if(Buffer.isBuffer(content) || content instanceof Uint8Array) {
|
||||
writeByteLength(content.length)
|
||||
pushBytes(content)
|
||||
} else if(Array.isArray(content)) {
|
||||
writeListStart(content.length)
|
||||
for(const item of content) {
|
||||
if(item) {
|
||||
encodeBinaryNode(item, buffer)
|
||||
}
|
||||
}
|
||||
} else if(typeof content === 'undefined' || content === null) {
|
||||
// do nothing
|
||||
} else {
|
||||
throw new Error(`invalid children for header "${tag}": ${content} (${typeof content})`)
|
||||
}
|
||||
|
||||
return Buffer.from(buffer)
|
||||
}
|
||||
@@ -1,326 +1,6 @@
|
||||
import { DICTIONARIES_MAP, SINGLE_BYTE_TOKEN, SINGLE_BYTE_TOKEN_MAP, DICTIONARIES } from '../../WABinary/Constants';
|
||||
import { jidDecode, jidEncode } from './jid-utils';
|
||||
import { Binary, numUtf8Bytes } from '../../WABinary/Binary';
|
||||
import { Boom } from '@hapi/boom';
|
||||
import { proto } from '../../WAProto';
|
||||
import { BinaryNode } from './types';
|
||||
|
||||
const LIST1 = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', '<27>', '<27>', '<27>', '<27>'];
|
||||
const LIST2 = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
|
||||
function k(data: Binary, uint: number) {
|
||||
let arr = [];
|
||||
for (let a = 0; a < uint; a++) {
|
||||
arr.push(decodeBinaryNode(data));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function x(data: Binary, t, r, a) {
|
||||
const arr = new Array(2 * a - r);
|
||||
for (let n = 0; n < arr.length - 1; n += 2) {
|
||||
var s = data.readUint8();
|
||||
(arr[n] = t[s >>> 4]), (arr[n + 1] = t[15 & s]);
|
||||
}
|
||||
|
||||
if (r) {
|
||||
arr[arr.length - 1] = t[data.readUint8() >>> 4];
|
||||
}
|
||||
|
||||
return arr.join('');
|
||||
}
|
||||
|
||||
function D(e, t, r) {
|
||||
var a = e.length % 2 == 1;
|
||||
r.writeUint8(t);
|
||||
var i = Math.ceil(e.length / 2);
|
||||
a && (i |= 128), r.writeUint8(i);
|
||||
for (var n = 0, s = 0; s < e.length; s++) {
|
||||
var o = e.charCodeAt(s),
|
||||
l = null;
|
||||
if ((48 <= o && o <= 57 ? (l = o - 48) : 255 === t ? (45 === o ? (l = 10) : 46 === o && (l = 11)) : 251 === t && 65 <= o && o <= 70 && (l = o - 55), null == l))
|
||||
throw new Error(`Cannot nibble encode ${o}`);
|
||||
s % 2 == 0 ? ((n = l << 4), s === e.length - 1 && ((n |= 15), r.writeUint8(n))) : ((n |= l), r.writeUint8(n));
|
||||
}
|
||||
}
|
||||
|
||||
function N(e, t) {
|
||||
if (e < 256) t.writeUint8(252), t.writeUint8(e);
|
||||
else if (e < 1048576) t.writeUint8(253), t.writeUint8((e >>> 16) & 255), t.writeUint8((e >>> 8) & 255), t.writeUint8(255 & e);
|
||||
else {
|
||||
if (!(e < 4294967296)) throw new Error(`Binary with length ${e} is too big for WAP protocol`);
|
||||
t.writeUint8(254), t.writeUint32(e);
|
||||
}
|
||||
}
|
||||
|
||||
function R(e: any, t: Binary) {
|
||||
var w = null;
|
||||
if ('' === e) return t.writeUint8(252), void t.writeUint8(0);
|
||||
var b = SINGLE_BYTE_TOKEN_MAP;
|
||||
var r = b.get(e);
|
||||
var c = [236, 237, 238, 239];
|
||||
if (null == r) {
|
||||
if (null == w) {
|
||||
w = [];
|
||||
for (var a = 0; a < DICTIONARIES_MAP.length; ++a) w.push(DICTIONARIES_MAP[a]);
|
||||
}
|
||||
for (var n = 0; n < w.length; ++n) {
|
||||
var s = w[n].get(e);
|
||||
if (null != s) return t.writeUint8(c[n]), void t.writeUint8(s);
|
||||
}
|
||||
var o = numUtf8Bytes(e);
|
||||
if (o < 128) {
|
||||
if (!/[^0-9.-]+?/.exec(e)) return void D(e, 255, t);
|
||||
if (!/[^0-9A-F]+?/.exec(e)) return void D(e, 251, t);
|
||||
}
|
||||
N(o, t), t.writeString(e);
|
||||
} else t.writeUint8(r + 1);
|
||||
}
|
||||
|
||||
function M(e: any, t: Binary) {
|
||||
var p = 248;
|
||||
var f = 249;
|
||||
if (void 0 === e.tag) return t.writeUint8(p), void t.writeUint8(0);
|
||||
var r = 1;
|
||||
e.attrs && (r += 2 * Object.keys(e.attrs).length),
|
||||
e.content && r++,
|
||||
r < 256 ? (t.writeUint8(p), t.writeUint8(r)) : r < 65536 && (t.writeUint8(f), t.writeUint16(r)),
|
||||
O(e.tag, t),
|
||||
e.attrs &&
|
||||
Object.keys(e.attrs).forEach((r) => {
|
||||
R(r, t), O(e.attrs[r], t);
|
||||
});
|
||||
var a = e.content;
|
||||
if (Array.isArray(a)) {
|
||||
a.length < 256 ? (t.writeUint8(p), t.writeUint8(a.length)) : a.length < 65536 && (t.writeUint8(f), t.writeUint16(a.length));
|
||||
for (var i = 0; i < a.length; i++) M(a[i], t);
|
||||
} else a && O(a, t);
|
||||
}
|
||||
|
||||
function L(data: Binary, t: boolean) {
|
||||
const n = data.readUint8();
|
||||
if (n === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (n === 248) {
|
||||
return k(data, data.readUint8());
|
||||
}
|
||||
|
||||
if (n === 249) {
|
||||
return k(data, data.readUint16());
|
||||
}
|
||||
|
||||
if (n === 252) {
|
||||
return t ? data.readString(data.readUint8()) : data.readByteArray(data.readUint8());
|
||||
}
|
||||
|
||||
if (n === 253) {
|
||||
const size = ((15 & data.readUint8()) << 16) + (data.readUint8() << 8) + data.readUint8();
|
||||
return t ? data.readString(size) : data.readByteArray(size);
|
||||
}
|
||||
|
||||
if (n === 254) {
|
||||
return t ? data.readString(data.readUint32()) : data.readByteArray(data.readUint32());
|
||||
}
|
||||
|
||||
if (n === 250) {
|
||||
const user = L(data, true);
|
||||
if (null != user && 'string' != typeof user) throw new Error(`Decode string got invalid value ${String(t)}, string expected`);
|
||||
const server = decodeStanzaString(data)
|
||||
return jidEncode(user, server)
|
||||
}
|
||||
|
||||
if (n === 247) {
|
||||
const agent = data.readUint8();
|
||||
const device = data.readUint8();
|
||||
const user = decodeStanzaString(data);
|
||||
|
||||
return jidEncode(user, 's.whatsapp.net', device, agent);
|
||||
}
|
||||
|
||||
if (n === 255) {
|
||||
const number = data.readUint8();
|
||||
return x(data, LIST1, number >>> 7, 127 & number);
|
||||
}
|
||||
|
||||
if (n === 251) {
|
||||
const number = data.readUint8();
|
||||
return x(data, LIST2, number >>> 7, 127 & number);
|
||||
}
|
||||
|
||||
if (n <= 0 || n >= 240) {
|
||||
throw new Error('Unable to decode WAP buffer');
|
||||
}
|
||||
|
||||
if (n >= 236 && n <= 239) {
|
||||
const dict = DICTIONARIES[n - 236];
|
||||
if (!dict) {
|
||||
throw new Error(`Missing WAP dictionary ${n - 236}`);
|
||||
}
|
||||
|
||||
const index = data.readUint8();
|
||||
const value = dict[index];
|
||||
if (!value) {
|
||||
throw new Error(`Invalid value index ${index} in dict ${n - 236}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const singleToken = SINGLE_BYTE_TOKEN[n - 1];
|
||||
if (!singleToken) throw new Error(`Undefined token with index ${n}`);
|
||||
|
||||
return singleToken;
|
||||
}
|
||||
|
||||
function O(e: any, t: Binary) {
|
||||
if (null == e) t.writeUint8(0);
|
||||
else if (typeof e === 'object' && !(e instanceof Uint8Array) && !Buffer.isBuffer(e) && !Array.isArray(e)) M(e, t);
|
||||
else if ('string' == typeof e) {
|
||||
const jid = jidDecode(e)
|
||||
if(jid) {
|
||||
if(typeof jid.agent !== 'undefined' || typeof jid.device !== 'undefined') {
|
||||
var { user: a, agent: i, device: n } = jid;
|
||||
t.writeUint8(247), t.writeUint8(i || 0), t.writeUint8(n || 0), O(a, t);
|
||||
} else {
|
||||
var { user: s, server: l } = jid;
|
||||
t.writeUint8(250), null != s ? O(s, t) : t.writeUint8(0), O(l, t);
|
||||
}
|
||||
} else {
|
||||
R(e, t);
|
||||
}
|
||||
} else {
|
||||
if (!(e instanceof Uint8Array)) throw new Error('Invalid payload type ' + typeof e);
|
||||
N(e.length, t), t.writeByteArray(e);
|
||||
}
|
||||
}
|
||||
|
||||
function decodeStanzaString(data: Binary) {
|
||||
// G
|
||||
const t = L(data, true);
|
||||
if (typeof t != 'string') {
|
||||
throw new Error(`Decode string got invalid value ${String(t)}, string expected`);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
function bufferToUInt(e: Uint8Array | Buffer, t: number) {
|
||||
let a = 0
|
||||
for (let i = 0; i < t; i++) a = 256 * a + e[i]
|
||||
return a
|
||||
}
|
||||
|
||||
export const decodeBinaryNode = (data: Binary): BinaryNode => {
|
||||
//U
|
||||
let r = data.readUint8();
|
||||
let t = r === 248 ? data.readUint8() : data.readUint16();
|
||||
|
||||
if (!t) {
|
||||
throw new Error('Failed to decode node, list cannot be empty');
|
||||
}
|
||||
|
||||
const a = {};
|
||||
|
||||
const n = decodeStanzaString(data);
|
||||
for (t -= 1; t > 1; ) {
|
||||
const s = decodeStanzaString(data);
|
||||
const l = L(data, true);
|
||||
a[s] = l;
|
||||
t -= 2;
|
||||
}
|
||||
|
||||
let i = null;
|
||||
1 === t && jidDecode(i = L(data, !1)) && (i = String(i));
|
||||
|
||||
return {
|
||||
tag: n,
|
||||
attrs: a,
|
||||
content: i
|
||||
}
|
||||
}
|
||||
|
||||
export const encodeBinaryNode = (node: BinaryNode) => {
|
||||
const data = new Binary();
|
||||
|
||||
O(node, data);
|
||||
|
||||
const dataArr = data.readByteArray();
|
||||
const result = new Uint8Array(1 + dataArr.length);
|
||||
|
||||
result[0] = 0;
|
||||
result.set(dataArr, 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// some extra useful utilities
|
||||
|
||||
export const getBinaryNodeChildren = ({ content }: BinaryNode, childTag: string) => {
|
||||
if(Array.isArray(content)) {
|
||||
return content.filter(item => item.tag == childTag)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export const getAllBinaryNodeChildren = ({ content }: BinaryNode) => {
|
||||
if(Array.isArray(content)) {
|
||||
return content
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export const getBinaryNodeChild = ({ content }: BinaryNode, childTag: string) => {
|
||||
if(Array.isArray(content)) {
|
||||
return content.find(item => item.tag == childTag)
|
||||
}
|
||||
}
|
||||
|
||||
export const getBinaryNodeChildBuffer = (node: BinaryNode, childTag: string) => {
|
||||
const child = getBinaryNodeChild(node, childTag)?.content
|
||||
if(Buffer.isBuffer(child) || child instanceof Uint8Array) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
|
||||
export const getBinaryNodeChildUInt = (node: BinaryNode, childTag: string, length: number) => {
|
||||
const buff = getBinaryNodeChildBuffer(node, childTag)
|
||||
if(buff) return bufferToUInt(buff, length)
|
||||
}
|
||||
|
||||
export const assertNodeErrorFree = (node: BinaryNode) => {
|
||||
const errNode = getBinaryNodeChild(node, 'error')
|
||||
if(errNode) {
|
||||
throw new Boom(errNode.attrs.text || 'Unknown error', { data: +errNode.attrs.code })
|
||||
}
|
||||
}
|
||||
|
||||
export const reduceBinaryNodeToDictionary = (node: BinaryNode, tag: string) => {
|
||||
const nodes = getBinaryNodeChildren(node, tag)
|
||||
const dict = nodes.reduce(
|
||||
(dict, { attrs }) => {
|
||||
dict[attrs.name || attrs.config_code] = attrs.value || attrs.config_value
|
||||
return dict
|
||||
}, { } as { [_: string]: string }
|
||||
)
|
||||
return dict
|
||||
}
|
||||
|
||||
export const getBinaryNodeMessages = ({ content }: BinaryNode) => {
|
||||
const msgs: proto.WebMessageInfo[] = []
|
||||
if(Array.isArray(content)) {
|
||||
for(const item of content) {
|
||||
if(item.tag === 'message') {
|
||||
msgs.push(proto.WebMessageInfo.decode(item.content as Buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
export * from './encode'
|
||||
export * from './decode'
|
||||
export * from './generic-utils'
|
||||
export * from './jid-utils'
|
||||
export { Binary } from '../../WABinary/Binary'
|
||||
export * from './types'
|
||||
export * from './Legacy'
|
||||
15
yarn.lock
15
yarn.lock
@@ -1845,11 +1845,6 @@ cssstyle@^2.3.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
curve25519-js@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/curve25519-js/-/curve25519-js-0.0.4.tgz#e6ad967e8cd284590d657bbfc90d8b50e49ba060"
|
||||
integrity sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==
|
||||
|
||||
data-urls@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
|
||||
@@ -2473,6 +2468,11 @@ functional-red-black-tree@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
futoin-hkdf@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.5.0.tgz#f10cc4d32f1e26568ded58d5a6535a97aa3a064c"
|
||||
integrity sha512-4CerDhtTgx4i5PKccQIpEp4T9wqmosPIP9Kep35SdCpYkQeriD3zddUVhrO1Fc4QvGhsAnd2rXyoOr5047mJEg==
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
|
||||
@@ -3500,11 +3500,10 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
"libsignal@git+https://github.com/adiwajshing/libsignal-node":
|
||||
"libsignal@git+https://github.com/adiwajshing/libsignal-node#proto-patch":
|
||||
version "2.0.1"
|
||||
resolved "git+https://github.com/adiwajshing/libsignal-node#4ffc1f3d6147ffdec88f62e70f41e7de2e08cd42"
|
||||
resolved "git+https://github.com/adiwajshing/libsignal-node#a623b3dc389cfbcf7b8559598b6c88986843d3dc"
|
||||
dependencies:
|
||||
curve25519-js "^0.0.4"
|
||||
protobufjs "6.8.8"
|
||||
|
||||
link-preview-js@^2.1.13:
|
||||
|
||||
Reference in New Issue
Block a user