diff --git a/WABinary/Binary.js b/WABinary/Binary.js deleted file mode 100644 index c37e1eb..0000000 --- a/WABinary/Binary.js +++ /dev/null @@ -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 \ No newline at end of file diff --git a/WABinary/Constants.js b/WABinary/Constants.js deleted file mode 100644 index 8adfeed..0000000 --- a/WABinary/Constants.js +++ /dev/null @@ -1,22 +0,0 @@ - -const buildMap = (data) => { - const map = new Map(); - for (let r = 0; r < data.length; r++) { - map.set(data[r], r); - } - - return map; -} - -module.exports.DEVICE = { PRIMARY_DEVICE: 0, PRIMARY_VERSION: 0 } - -module.exports.SINGLE_BYTE_TOKEN = ["xmlstreamstart", "xmlstreamend", "s.whatsapp.net", "type", "participant", "from", "receipt", "id", "broadcast", "status", "message", "notification", "notify", "to", "jid", "user", "class", "offline", "g.us", "result", "mediatype", "enc", "skmsg", "off_cnt", "xmlns", "presence", "participants", "ack", "t", "iq", "device_hash", "read", "value", "media", "picture", "chatstate", "unavailable", "text", "urn:xmpp:whatsapp:push", "devices", "verified_name", "contact", "composing", "edge_routing", "routing_info", "item", "image", "verified_level", "get", "fallback_hostname", "2", "media_conn", "1", "v", "handshake", "fallback_class", "count", "config", "offline_preview", "download_buckets", "w:profile:picture", "set", "creation", "location", "fallback_ip4", "msg", "urn:xmpp:ping", "fallback_ip6", "call-creator", "relaylatency", "success", "subscribe", "video", "business_hours_config", "platform", "hostname", "version", "unknown", "0", "ping", "hash", "edit", "subject", "max_buckets", "download", "delivery", "props", "sticker", "name", "last", "contacts", "business", "primary", "preview", "w:p", "pkmsg", "call-id", "retry", "prop", "call", "auth_ttl", "available", "relay_id", "last_id", "day_of_week", "w", "host", "seen", "bits", "list", "atn", "upload", "is_new", "w:stats", "key", "paused", "specific_hours", "multicast", "stream:error", "mmg.whatsapp.net", "code", "deny", "played", "profile", "fna", "device-list", "close_time", "latency", "gcm", "pop", "audio", "26", "w:web", "open_time", "error", "auth", "ip4", "update", "profile_options", "config_value", "category", "catalog_not_created", "00", "config_code", "mode", "catalog_status", "ip6", "blocklist", "registration", "7", "web", "fail", "w:m", "cart_enabled", "ttl", "gif", "300", "device_orientation", "identity", "query", "401", "media-gig2-1.cdn.whatsapp.net", "in", "3", "te2", "add", "fallback", "categories", "ptt", "encrypt", "notice", "thumbnail-document", "item-not-found", "12", "thumbnail-image", "stage", "thumbnail-link", "usync", "out", "thumbnail-video", "8", "01", "context", "sidelist", "thumbnail-gif", "terminate", "not-authorized", "orientation", "dhash", "capability", "side_list", "md-app-state", "description", "serial", "readreceipts", "te", "business_hours", "md-msg-hist", "tag", "attribute_padding", "document", "open_24h", "delete", "expiration", "active", "prev_v_id", "true", "passive", "index", "4", "conflict", "remove", "w:gp2", "config_expo_key", "screen_height", "replaced", "02", "screen_width", "uploadfieldstat", "2:47DEQpj8", "media-bog1-1.cdn.whatsapp.net", "encopt", "url", "catalog_exists", "keygen", "rate", "offer", "opus", "media-mia3-1.cdn.whatsapp.net", "privacy", "media-mia3-2.cdn.whatsapp.net", "signature", "preaccept", "token_id", "media-eze1-1.cdn.whatsapp.net"]; -module.exports.DICTIONARY_0_TOKEN = ["media-for1-1.cdn.whatsapp.net", "relay", "media-gru2-2.cdn.whatsapp.net", "uncompressed", "medium", "voip_settings", "device", "reason", "media-lim1-1.cdn.whatsapp.net", "media-qro1-2.cdn.whatsapp.net", "media-gru1-2.cdn.whatsapp.net", "action", "features", "media-gru2-1.cdn.whatsapp.net", "media-gru1-1.cdn.whatsapp.net", "media-otp1-1.cdn.whatsapp.net", "kyc-id", "priority", "phash", "mute", "token", "100", "media-qro1-1.cdn.whatsapp.net", "none", "media-mrs2-2.cdn.whatsapp.net", "sign_credential", "03", "media-mrs2-1.cdn.whatsapp.net", "protocol", "timezone", "transport", "eph_setting", "1080", "original_dimensions", "media-frx5-1.cdn.whatsapp.net", "background", "disable", "original_image_url", "5", "transaction-id", "direct_path", "103", "appointment_only", "request_image_url", "peer_pid", "address", "105", "104", "102", "media-cdt1-1.cdn.whatsapp.net", "101", "109", "110", "106", "background_location", "v_id", "sync", "status-old", "111", "107", "ppic", "media-scl2-1.cdn.whatsapp.net", "business_profile", "108", "invite", "04", "audio_duration", "media-mct1-1.cdn.whatsapp.net", "media-cdg2-1.cdn.whatsapp.net", "media-los2-1.cdn.whatsapp.net", "invis", "net", "voip_payload_type", "status-revoke-delay", "404", "state", "use_correct_order_for_hmac_sha1", "ver", "media-mad1-1.cdn.whatsapp.net", "order", "540", "skey", "blinded_credential", "android", "contact_remove", "enable_downlink_relay_latency_only", "duration", "enable_vid_one_way_codec_nego", "6", "media-sof1-1.cdn.whatsapp.net", "accept", "all", "signed_credential", "media-atl3-1.cdn.whatsapp.net", "media-lhr8-1.cdn.whatsapp.net", "website", "05", "latitude", "media-dfw5-1.cdn.whatsapp.net", "forbidden", "enable_audio_piggyback_network_mtu_fix", "media-dfw5-2.cdn.whatsapp.net", "note.m4r", "media-atl3-2.cdn.whatsapp.net", "jb_nack_discard_count_fix", "longitude", "Opening.m4r", "media-arn2-1.cdn.whatsapp.net", "email", "timestamp", "admin", "media-pmo1-1.cdn.whatsapp.net", "America/Sao_Paulo", "contact_add", "media-sin6-1.cdn.whatsapp.net", "interactive", "8000", "acs_public_key", "sigquit_anr_detector_release_rollover_percent", "media.fmed1-2.fna.whatsapp.net", "groupadd", "enabled_for_video_upgrade", "latency_update_threshold", "media-frt3-2.cdn.whatsapp.net", "calls_row_constraint_layout", "media.fgbb2-1.fna.whatsapp.net", "mms4_media_retry_notification_encryption_enabled", "timeout", "media-sin6-3.cdn.whatsapp.net", "audio_nack_jitter_multiplier", "jb_discard_count_adjust_pct_rc", "audio_reserve_bps", "delta", "account_sync", "default", "media.fjed4-6.fna.whatsapp.net", "06", "lock_video_orientation", "media-frt3-1.cdn.whatsapp.net", "w:g2", "media-sin6-2.cdn.whatsapp.net", "audio_nack_algo_mask", "media.fgbb2-2.fna.whatsapp.net", "media.fmed1-1.fna.whatsapp.net", "cond_range_target_bitrate", "mms4_server_error_receipt_encryption_enabled", "vid_rc_dyn", "fri", "cart_v1_1_order_message_changes_enabled", "reg_push", "jb_hist_deposit_value", "privatestats", "media.fist7-2.fna.whatsapp.net", "thu", "jb_discard_count_adjust_pct", "mon", "group_call_video_maximization_enabled", "mms_cat_v1_forward_hot_override_enabled", "audio_nack_new_rtt", "media.fsub2-3.fna.whatsapp.net", "media_upload_aggressive_retry_exponential_backoff_enabled", "tue", "wed", "media.fruh4-2.fna.whatsapp.net", "audio_nack_max_seq_req", "max_rtp_audio_packet_resends", "jb_hist_max_cdf_value", "07", "audio_nack_max_jb_delay", "mms_forward_partially_downloaded_video", "media-lcy1-1.cdn.whatsapp.net", "resume", "jb_inband_fec_aware", "new_commerce_entry_point_enabled", "480", "payments_upi_generate_qr_amount_limit", "sigquit_anr_detector_rollover_percent", "media.fsdu2-1.fna.whatsapp.net", "fbns", "aud_pkt_reorder_pct", "dec", "stop_probing_before_accept_send", "media_upload_max_aggressive_retries", "edit_business_profile_new_mode_enabled", "media.fhex4-1.fna.whatsapp.net", "media.fjed4-3.fna.whatsapp.net", "sigquit_anr_detector_64bit_rollover_percent", "cond_range_ema_jb_last_delay", "watls_enable_early_data_http_get", "media.fsdu2-2.fna.whatsapp.net", "message_qr_disambiguation_enabled", "media-mxp1-1.cdn.whatsapp.net", "sat", "vertical", "media.fruh4-5.fna.whatsapp.net", "200", "media-sof1-2.cdn.whatsapp.net", "-1", "height", "product_catalog_hide_show_items_enabled", "deep_copy_frm_last", "tsoffline", "vp8/h.264", "media.fgye5-3.fna.whatsapp.net", "media.ftuc1-2.fna.whatsapp.net", "smb_upsell_chat_banner_enabled", "canonical", "08", "9", ".", "media.fgyd4-4.fna.whatsapp.net", "media.fsti4-1.fna.whatsapp.net", "mms_vcache_aggregation_enabled", "mms_hot_content_timespan_in_seconds", "nse_ver", "rte", "third_party_sticker_web_sync", "cond_range_target_total_bitrate", "media_upload_aggressive_retry_enabled", "instrument_spam_report_enabled", "disable_reconnect_tone", "move_media_folder_from_sister_app", "one_tap_calling_in_group_chat_size", "10", "storage_mgmt_banner_threshold_mb", "enable_backup_passive_mode", "sharechat_inline_player_enabled", "media.fcnq2-1.fna.whatsapp.net", "media.fhex4-2.fna.whatsapp.net", "media.fist6-3.fna.whatsapp.net", "ephemeral_drop_column_stage", "reconnecting_after_network_change_threshold_ms", "media-lhr8-2.cdn.whatsapp.net", "cond_jb_last_delay_ema_alpha", "entry_point_block_logging_enabled", "critical_event_upload_log_config", "respect_initial_bitrate_estimate", "smaller_image_thumbs_status_enabled", "media.fbtz1-4.fna.whatsapp.net", "media.fjed4-1.fna.whatsapp.net", "width", "720", "enable_frame_dropper", "enable_one_side_mode", "urn:xmpp:whatsapp:dirty", "new_sticker_animation_behavior_v2", "media.flim3-2.fna.whatsapp.net", "media.fuio6-2.fna.whatsapp.net", "skip_forced_signaling", "dleq_proof", "status_video_max_bitrate", "lazy_send_probing_req", "enhanced_storage_management", "android_privatestats_endpoint_dit_enabled", "media.fscl13-2.fna.whatsapp.net", "video_duration"]; -module.exports.DICTIONARY_1_TOKEN = ["group_call_discoverability_enabled", "media.faep9-2.fna.whatsapp.net", "msgr", "bloks_loggedin_access_app_id", "db_status_migration_step", "watls_prefer_ip6", "jabber:iq:privacy", "68", "media.fsaw1-11.fna.whatsapp.net", "mms4_media_conn_persist_enabled", "animated_stickers_thread_clean_up", "media.fcgk3-2.fna.whatsapp.net", "media.fcgk4-6.fna.whatsapp.net", "media.fgye5-2.fna.whatsapp.net", "media.flpb1-1.fna.whatsapp.net", "media.fsub2-1.fna.whatsapp.net", "media.fuio6-3.fna.whatsapp.net", "not-allowed", "partial_pjpeg_bw_threshold", "cap_estimated_bitrate", "mms_chatd_resume_check_over_thrift", "smb_upsell_business_profile_enabled", "product_catalog_webclient", "groups", "sigquit_anr_detector_release_updated_rollout", "syncd_key_rotation_enabled", "media.fdmm2-1.fna.whatsapp.net", "media-hou1-1.cdn.whatsapp.net", "remove_old_chat_notifications", "smb_biztools_deeplink_enabled", "use_downloadable_filters_int", "group_qr_codes_enabled", "max_receipt_processing_time", "optimistic_image_processing_enabled", "smaller_video_thumbs_status_enabled", "watls_early_data", "reconnecting_before_relay_failover_threshold_ms", "cond_range_packet_loss_pct", "groups_privacy_blacklist", "status-revoke-drop", "stickers_animated_thumbnail_download", "dedupe_transcode_shared_images", "dedupe_transcode_shared_videos", "media.fcnq2-2.fna.whatsapp.net", "media.fgyd4-1.fna.whatsapp.net", "media.fist7-1.fna.whatsapp.net", "media.flim3-3.fna.whatsapp.net", "add_contact_by_qr_enabled", "https://faq.whatsapp.com/payments", "multicast_limit_global", "sticker_notification_preview", "smb_better_catalog_list_adapters_enabled", "bloks_use_minscript_android", "pen_smoothing_enabled", "media.fcgk4-5.fna.whatsapp.net", "media.fevn1-3.fna.whatsapp.net", "media.fpoj7-1.fna.whatsapp.net", "media-arn2-2.cdn.whatsapp.net", "reconnecting_before_network_change_threshold_ms", "android_media_use_fresco_for_gifs", "cond_in_congestion", "status_image_max_edge", "sticker_search_enabled", "starred_stickers_web_sync", "db_blank_me_jid_migration_step", "media.fist6-2.fna.whatsapp.net", "media.ftuc1-1.fna.whatsapp.net", "09", "anr_fast_logs_upload_rollout", "camera_core_integration_enabled", "11", "third_party_sticker_caching", "thread_dump_contact_support", "wam_privatestats_enabled", "vcard_as_document_size_kb", "maxfpp", "fbip", "ephemeral_allow_group_members", "media-bom1-2.cdn.whatsapp.net", "media-xsp1-1.cdn.whatsapp.net", "disable_prewarm", "frequently_forwarded_max", "media.fbtz1-5.fna.whatsapp.net", "media.fevn7-1.fna.whatsapp.net", "media.fgyd4-2.fna.whatsapp.net", "sticker_tray_animation_fully_visible_items", "green_alert_banner_duration", "reconnecting_after_p2p_failover_threshold_ms", "connected", "share_biz_vcard_enabled", "stickers_animation", "0a", "1200", "WhatsApp", "group_description_length", "p_v_id", "payments_upi_intent_transaction_limit", "frequently_forwarded_messages", "media-xsp1-2.cdn.whatsapp.net", "media.faep8-1.fna.whatsapp.net", "media.faep8-2.fna.whatsapp.net", "media.faep9-1.fna.whatsapp.net", "media.fdmm2-2.fna.whatsapp.net", "media.fgzt3-1.fna.whatsapp.net", "media.flim4-2.fna.whatsapp.net", "media.frao1-1.fna.whatsapp.net", "media.fscl9-2.fna.whatsapp.net", "media.fsub2-2.fna.whatsapp.net", "superadmin", "media.fbog10-1.fna.whatsapp.net", "media.fcgh28-1.fna.whatsapp.net", "media.fjdo10-1.fna.whatsapp.net", "third_party_animated_sticker_import", "delay_fec", "attachment_picker_refresh", "android_linked_devices_re_auth_enabled", "rc_dyn", "green_alert_block_jitter", "add_contact_logging_enabled", "biz_message_logging_enabled", "conversation_media_preview_v2", "media-jnb1-1.cdn.whatsapp.net", "ab_key", "media.fcgk4-2.fna.whatsapp.net", "media.fevn1-1.fna.whatsapp.net", "media.fist6-1.fna.whatsapp.net", "media.fruh4-4.fna.whatsapp.net", "media.fsti4-2.fna.whatsapp.net", "mms_vcard_autodownload_size_kb", "watls_enabled", "notif_ch_override_off", "media.fsaw1-14.fna.whatsapp.net", "media.fscl13-1.fna.whatsapp.net", "db_group_participant_migration_step", "1020", "cond_range_sterm_rtt", "invites_logging_enabled", "triggered_block_enabled", "group_call_max_participants", "media-iad3-1.cdn.whatsapp.net", "product_catalog_open_deeplink", "shops_required_tos_version", "image_max_kbytes", "cond_low_quality_vid_mode", "db_receipt_migration_step", "jb_early_prob_hist_shrink", "media.fdmm2-3.fna.whatsapp.net", "media.fdmm2-4.fna.whatsapp.net", "media.fruh4-1.fna.whatsapp.net", "media.fsaw2-2.fna.whatsapp.net", "remove_geolocation_videos", "new_animation_behavior", "fieldstats_beacon_chance", "403", "authkey_reset_on_ban", "continuous_ptt_playback", "reconnecting_after_relay_failover_threshold_ms", "false", "group", "sun", "conversation_swipe_to_reply", "ephemeral_messages_setting", "smaller_video_thumbs_enabled", "md_device_sync_enabled", "bloks_shops_pdp_url_regex", "lasso_integration_enabled", "media-bom1-1.cdn.whatsapp.net", "new_backup_format_enabled", "256", "media.faep6-1.fna.whatsapp.net", "media.fasr1-1.fna.whatsapp.net", "media.fbtz1-7.fna.whatsapp.net", "media.fesb4-1.fna.whatsapp.net", "media.fjdo1-2.fna.whatsapp.net", "media.frba2-1.fna.whatsapp.net", "watls_no_dns", "600", "db_broadcast_me_jid_migration_step", "new_wam_runtime_enabled", "group_update", "enhanced_block_enabled", "sync_wifi_threshold_kb", "mms_download_nc_cat", "bloks_minification_enabled", "ephemeral_messages_enabled", "reject", "voip_outgoing_xml_signaling", "creator", "dl_bw", "payments_request_messages", "target_bitrate", "bloks_rendercore_enabled", "media-hbe1-1.cdn.whatsapp.net", "media-hel3-1.cdn.whatsapp.net", "media-kut2-2.cdn.whatsapp.net", "media-lax3-1.cdn.whatsapp.net", "media-lax3-2.cdn.whatsapp.net", "sticker_pack_deeplink_enabled", "hq_image_bw_threshold", "status_info", "voip", "dedupe_transcode_videos", "grp_uii_cleanup", "linked_device_max_count", "media.flim1-1.fna.whatsapp.net", "media.fsaw2-1.fna.whatsapp.net", "reconnecting_after_call_active_threshold_ms", "1140", "catalog_pdp_new_design", "media.fbtz1-10.fna.whatsapp.net", "media.fsaw1-15.fna.whatsapp.net", "0b", "consumer_rc_provider", "mms_async_fast_forward_ttl", "jb_eff_size_fix", "voip_incoming_xml_signaling", "media_provider_share_by_uuid", "suspicious_links", "dedupe_transcode_images", "green_alert_modal_start", "media-cgk1-1.cdn.whatsapp.net", "media-lga3-1.cdn.whatsapp.net", "template_doc_mime_types", "important_messages", "user_add", "vcard_max_size_kb", "media.fada2-1.fna.whatsapp.net", "media.fbog2-5.fna.whatsapp.net", "media.fbtz1-3.fna.whatsapp.net", "media.fcgk3-1.fna.whatsapp.net", "media.fcgk7-1.fna.whatsapp.net", "media.flim1-3.fna.whatsapp.net", "media.fscl9-1.fna.whatsapp.net", "ctwa_context_enterprise_enabled", "media.fsaw1-13.fna.whatsapp.net", "media.fuio11-2.fna.whatsapp.net", "status_collapse_muted", "db_migration_level_force", "recent_stickers_web_sync", "bloks_session_state", "bloks_shops_enabled", "green_alert_setting_deep_links_enabled", "restrict_groups", "battery", "green_alert_block_start", "refresh", "ctwa_context_enabled", "md_messaging_enabled", "status_image_quality", "md_blocklist_v2_server", "media-del1-1.cdn.whatsapp.net", "13", "userrate", "a_v_id", "cond_rtt_ema_alpha", "invalid"]; -module.exports.DICTIONARY_2_TOKEN = ["media.fada1-1.fna.whatsapp.net", "media.fadb3-2.fna.whatsapp.net", "media.fbhz2-1.fna.whatsapp.net", "media.fcor2-1.fna.whatsapp.net", "media.fjed4-2.fna.whatsapp.net", "media.flhe4-1.fna.whatsapp.net", "media.frak1-2.fna.whatsapp.net", "media.fsub6-3.fna.whatsapp.net", "media.fsub6-7.fna.whatsapp.net", "media.fvvi1-1.fna.whatsapp.net", "search_v5_eligible", "wam_real_time_enabled", "report_disk_event", "max_tx_rott_based_bitrate", "product", "media.fjdo10-2.fna.whatsapp.net", "video_frame_crc_sample_interval", "media_max_autodownload", "15", "h.264", "wam_privatestats_buffer_count", "md_phash_v2_enabled", "account_transfer_enabled", "business_product_catalog", "enable_non_dyn_codec_param_fix", "is_user_under_epd_jurisdiction", "media.fbog2-4.fna.whatsapp.net", "media.fbtz1-2.fna.whatsapp.net", "media.fcfc1-1.fna.whatsapp.net", "media.fjed4-5.fna.whatsapp.net", "media.flhe4-2.fna.whatsapp.net", "media.flim1-2.fna.whatsapp.net", "media.flos5-1.fna.whatsapp.net", "android_key_store_auth_ver", "010", "anr_process_monitor", "delete_old_auth_key", "media.fcor10-3.fna.whatsapp.net", "storage_usage_enabled", "android_camera2_support_level", "dirty", "consumer_content_provider", "status_video_max_duration", "0c", "bloks_cache_enabled", "media.fadb2-2.fna.whatsapp.net", "media.fbko1-1.fna.whatsapp.net", "media.fbtz1-9.fna.whatsapp.net", "media.fcgk4-4.fna.whatsapp.net", "media.fesb4-2.fna.whatsapp.net", "media.fevn1-2.fna.whatsapp.net", "media.fist2-4.fna.whatsapp.net", "media.fjdo1-1.fna.whatsapp.net", "media.fruh4-6.fna.whatsapp.net", "media.fsrg5-1.fna.whatsapp.net", "media.fsub6-6.fna.whatsapp.net", "minfpp", "5000", "locales", "video_max_bitrate", "use_new_auth_key", "bloks_http_enabled", "heartbeat_interval", "media.fbog11-1.fna.whatsapp.net", "ephemeral_group_query_ts", "fec_nack", "search_in_storage_usage", "c", "media-amt2-1.cdn.whatsapp.net", "linked_devices_ui_enabled", "14", "async_data_load_on_startup", "voip_incoming_xml_ack", "16", "db_migration_step", "init_bwe", "max_participants", "wam_buffer_count", "media.fada2-2.fna.whatsapp.net", "media.fadb3-1.fna.whatsapp.net", "media.fcor2-2.fna.whatsapp.net", "media.fdiy1-2.fna.whatsapp.net", "media.frba3-2.fna.whatsapp.net", "media.fsaw2-3.fna.whatsapp.net", "1280", "status_grid_enabled", "w:biz", "product_catalog_deeplink", "media.fgye10-2.fna.whatsapp.net", "media.fuio11-1.fna.whatsapp.net", "optimistic_upload", "work_manager_init", "lc", "catalog_message", "cond_net_medium", "enable_periodical_aud_rr_processing", "cond_range_ema_rtt", "media-tir2-1.cdn.whatsapp.net", "frame_ms", "group_invite_sending", "payments_web_enabled", "wallpapers_v2", "0d", "browser", "hq_image_max_edge", "image_edit_zoom", "linked_devices_re_auth_enabled", "media.faly3-2.fna.whatsapp.net", "media.fdoh5-3.fna.whatsapp.net", "media.fesb3-1.fna.whatsapp.net", "media.fknu1-1.fna.whatsapp.net", "media.fmex3-1.fna.whatsapp.net", "media.fruh4-3.fna.whatsapp.net", "255", "web_upgrade_to_md_modal", "audio_piggyback_timeout_msec", "enable_audio_oob_fec_feature", "from_ip", "image_max_edge", "message_qr_enabled", "powersave", "receipt_pre_acking", "video_max_edge", "full", "011", "012", "enable_audio_oob_fec_for_sender", "md_voip_enabled", "enable_privatestats", "max_fec_ratio", "payments_cs_faq_url", "media-xsp1-3.cdn.whatsapp.net", "hq_image_quality", "media.fasr1-2.fna.whatsapp.net", "media.fbog3-1.fna.whatsapp.net", "media.ffjr1-6.fna.whatsapp.net", "media.fist2-3.fna.whatsapp.net", "media.flim4-3.fna.whatsapp.net", "media.fpbc2-4.fna.whatsapp.net", "media.fpku1-1.fna.whatsapp.net", "media.frba1-1.fna.whatsapp.net", "media.fudi1-1.fna.whatsapp.net", "media.fvvi1-2.fna.whatsapp.net", "gcm_fg_service", "enable_dec_ltr_size_check", "clear", "lg", "media.fgru11-1.fna.whatsapp.net", "18", "media-lga3-2.cdn.whatsapp.net", "pkey", "0e", "max_subject", "cond_range_lterm_rtt", "announcement_groups", "biz_profile_options", "s_t", "media.fabv2-1.fna.whatsapp.net", "media.fcai3-1.fna.whatsapp.net", "media.fcgh1-1.fna.whatsapp.net", "media.fctg1-4.fna.whatsapp.net", "media.fdiy1-1.fna.whatsapp.net", "media.fisb4-1.fna.whatsapp.net", "media.fpku1-2.fna.whatsapp.net", "media.fros9-1.fna.whatsapp.net", "status_v3_text", "usync_sidelist", "17", "announcement", "...", "md_group_notification", "0f", "animated_pack_in_store", "013", "America/Mexico_City", "1260", "media-ams4-1.cdn.whatsapp.net", "media-cgk1-2.cdn.whatsapp.net", "media-cpt1-1.cdn.whatsapp.net", "media-maa2-1.cdn.whatsapp.net", "media.fgye10-1.fna.whatsapp.net", "e", "catalog_cart", "hfm_string_changes", "init_bitrate", "packless_hsm", "group_info", "America/Belem", "50", "960", "cond_range_bwe", "decode", "encode", "media.fada1-8.fna.whatsapp.net", "media.fadb1-2.fna.whatsapp.net", "media.fasu6-1.fna.whatsapp.net", "media.fbog4-1.fna.whatsapp.net", "media.fcgk9-2.fna.whatsapp.net", "media.fdoh5-2.fna.whatsapp.net", "media.ffjr1-2.fna.whatsapp.net", "media.fgua1-1.fna.whatsapp.net", "media.fgye1-1.fna.whatsapp.net", "media.fist1-4.fna.whatsapp.net", "media.fpbc2-2.fna.whatsapp.net", "media.fres2-1.fna.whatsapp.net", "media.fsdq1-2.fna.whatsapp.net", "media.fsub6-5.fna.whatsapp.net", "profilo_enabled", "template_hsm", "use_disorder_prefetching_timer", "video_codec_priority", "vpx_max_qp", "ptt_reduce_recording_delay", "25", "iphone", "Windows", "s_o", "Africa/Lagos", "abt", "media-kut2-1.cdn.whatsapp.net", "media-mba1-1.cdn.whatsapp.net", "media-mxp1-2.cdn.whatsapp.net", "md_blocklist_v2", "url_text", "enable_short_offset", "group_join_permissions", "enable_audio_piggyback_feature", "image_quality", "media.fcgk7-2.fna.whatsapp.net", "media.fcgk8-2.fna.whatsapp.net", "media.fclo7-1.fna.whatsapp.net", "media.fcmn1-1.fna.whatsapp.net", "media.feoh1-1.fna.whatsapp.net", "media.fgyd4-3.fna.whatsapp.net", "media.fjed4-4.fna.whatsapp.net", "media.flim1-4.fna.whatsapp.net", "media.flim2-4.fna.whatsapp.net", "media.fplu6-1.fna.whatsapp.net", "media.frak1-1.fna.whatsapp.net", "media.fsdq1-1.fna.whatsapp.net", "to_ip", "015", "vp8", "19", "21", "1320", "auth_key_ver", "message_processing_dedup", "server-error", "wap4_enabled", "420", "014", "cond_range_rtt", "ptt_fast_lock_enabled", "media-ort2-1.cdn.whatsapp.net", "fwd_ui_start_ts"]; - -module.exports.DICTIONARY_3_TOKEN = ["contact_blacklist", "Asia/Jakarta", "media.fepa10-1.fna.whatsapp.net", "media.fmex10-3.fna.whatsapp.net", "disorder_prefetching_start_when_empty", "America/Bogota", "use_local_probing_rx_bitrate", "America/Argentina/Buenos_Aires", "cross_post", "media.fabb1-1.fna.whatsapp.net", "media.fbog4-2.fna.whatsapp.net", "media.fcgk9-1.fna.whatsapp.net", "media.fcmn2-1.fna.whatsapp.net", "media.fdel3-1.fna.whatsapp.net", "media.ffjr1-1.fna.whatsapp.net", "media.fgdl5-1.fna.whatsapp.net", "media.flpb1-2.fna.whatsapp.net", "media.fmex2-1.fna.whatsapp.net", "media.frba2-2.fna.whatsapp.net", "media.fros2-2.fna.whatsapp.net", "media.fruh2-1.fna.whatsapp.net", "media.fybz2-2.fna.whatsapp.net", "options", "20", "a", "017", "018", "mute_always", "user_notice", "Asia/Kolkata", "gif_provider", "locked", "media-gua1-1.cdn.whatsapp.net", "piggyback_exclude_force_flush", "24", "media.frec39-1.fna.whatsapp.net", "user_remove", "file_max_size", "cond_packet_loss_pct_ema_alpha", "media.facc1-1.fna.whatsapp.net", "media.fadb2-1.fna.whatsapp.net", "media.faly3-1.fna.whatsapp.net", "media.fbdo6-2.fna.whatsapp.net", "media.fcmn2-2.fna.whatsapp.net", "media.fctg1-3.fna.whatsapp.net", "media.ffez1-2.fna.whatsapp.net", "media.fist1-3.fna.whatsapp.net", "media.fist2-2.fna.whatsapp.net", "media.flim2-2.fna.whatsapp.net", "media.fmct2-3.fna.whatsapp.net", "media.fpei3-1.fna.whatsapp.net", "media.frba3-1.fna.whatsapp.net", "media.fsdu8-2.fna.whatsapp.net", "media.fstu2-1.fna.whatsapp.net", "media_type", "receipt_agg", "016", "enable_pli_for_crc_mismatch", "live", "enc_rekey", "frskmsg", "d", "media.fdel11-2.fna.whatsapp.net", "proto", "2250", "audio_piggyback_enable_cache", "skip_nack_if_ltrp_sent", "mark_dtx_jb_frames", "web_service_delay", "7282", "catalog_send_all", "outgoing", "360", "30", "LIMITED", "019", "audio_picker", "bpv2_phase", "media.fada1-7.fna.whatsapp.net", "media.faep7-1.fna.whatsapp.net", "media.fbko1-2.fna.whatsapp.net", "media.fbni1-2.fna.whatsapp.net", "media.fbtz1-1.fna.whatsapp.net", "media.fbtz1-8.fna.whatsapp.net", "media.fcjs3-1.fna.whatsapp.net", "media.fesb3-2.fna.whatsapp.net", "media.fgdl5-4.fna.whatsapp.net", "media.fist2-1.fna.whatsapp.net", "media.flhe2-2.fna.whatsapp.net", "media.flim2-1.fna.whatsapp.net", "media.fmex1-1.fna.whatsapp.net", "media.fpat3-2.fna.whatsapp.net", "media.fpat3-3.fna.whatsapp.net", "media.fros2-1.fna.whatsapp.net", "media.fsdu8-1.fna.whatsapp.net", "media.fsub3-2.fna.whatsapp.net", "payments_chat_plugin", "cond_congestion_no_rtcp_thr", "green_alert", "not-a-biz", "..", "shops_pdp_urls_config", "source", "media-dus1-1.cdn.whatsapp.net", "mute_video", "01b", "currency", "max_keys", "resume_check", "contact_array", "qr_scanning", "23", "b", "media.fbfh15-1.fna.whatsapp.net", "media.flim22-1.fna.whatsapp.net", "media.fsdu11-1.fna.whatsapp.net", "media.fsdu15-1.fna.whatsapp.net", "Chrome", "fts_version", "60", "media.fada1-6.fna.whatsapp.net", "media.faep4-2.fna.whatsapp.net", "media.fbaq5-1.fna.whatsapp.net", "media.fbni1-1.fna.whatsapp.net", "media.fcai3-2.fna.whatsapp.net", "media.fdel3-2.fna.whatsapp.net", "media.fdmm3-2.fna.whatsapp.net", "media.fhex3-1.fna.whatsapp.net", "media.fisb4-2.fna.whatsapp.net", "media.fkhi5-2.fna.whatsapp.net", "media.flos2-1.fna.whatsapp.net", "media.fmct2-1.fna.whatsapp.net", "media.fntr7-1.fna.whatsapp.net", "media.frak3-1.fna.whatsapp.net", "media.fruh5-2.fna.whatsapp.net", "media.fsub6-1.fna.whatsapp.net", "media.fuab1-2.fna.whatsapp.net", "media.fuio1-1.fna.whatsapp.net", "media.fver1-1.fna.whatsapp.net", "media.fymy1-1.fna.whatsapp.net", "product_catalog", "1380", "audio_oob_fec_max_pkts", "22", "254", "media-ort2-2.cdn.whatsapp.net", "media-sjc3-1.cdn.whatsapp.net", "1600", "01a", "01c", "405", "key_frame_interval", "body", "media.fcgh20-1.fna.whatsapp.net", "media.fesb10-2.fna.whatsapp.net", "125", "2000", "media.fbsb1-1.fna.whatsapp.net", "media.fcmn3-2.fna.whatsapp.net", "media.fcpq1-1.fna.whatsapp.net", "media.fdel1-2.fna.whatsapp.net", "media.ffor2-1.fna.whatsapp.net", "media.fgdl1-4.fna.whatsapp.net", "media.fhex2-1.fna.whatsapp.net", "media.fist1-2.fna.whatsapp.net", "media.fjed5-2.fna.whatsapp.net", "media.flim6-4.fna.whatsapp.net", "media.flos2-2.fna.whatsapp.net", "media.fntr6-2.fna.whatsapp.net", "media.fpku3-2.fna.whatsapp.net", "media.fros8-1.fna.whatsapp.net", "media.fymy1-2.fna.whatsapp.net", "ul_bw", "ltrp_qp_offset", "request", "nack", "dtx_delay_state_reset", "timeoffline", "28", "01f", "32", "enable_ltr_pool", "wa_msys_crypto", "01d", "58", "dtx_freeze_hg_update", "nack_if_rpsi_throttled", "253", "840", "media.famd15-1.fna.whatsapp.net", "media.fbog17-2.fna.whatsapp.net", "media.fcai19-2.fna.whatsapp.net", "media.fcai21-4.fna.whatsapp.net", "media.fesb10-4.fna.whatsapp.net", "media.fesb10-5.fna.whatsapp.net", "media.fmaa12-1.fna.whatsapp.net", "media.fmex11-3.fna.whatsapp.net", "media.fpoa33-1.fna.whatsapp.net", "1050", "021", "clean", "cond_range_ema_packet_loss_pct", "media.fadb6-5.fna.whatsapp.net", "media.faqp4-1.fna.whatsapp.net", "media.fbaq3-1.fna.whatsapp.net", "media.fbel2-1.fna.whatsapp.net", "media.fblr4-2.fna.whatsapp.net", "media.fclo8-1.fna.whatsapp.net", "media.fcoo1-2.fna.whatsapp.net", "media.ffjr1-4.fna.whatsapp.net", "media.ffor9-1.fna.whatsapp.net", "media.fisb3-1.fna.whatsapp.net", "media.fkhi2-2.fna.whatsapp.net", "media.fkhi4-1.fna.whatsapp.net", "media.fpbc1-2.fna.whatsapp.net", "media.fruh2-2.fna.whatsapp.net", "media.fruh5-1.fna.whatsapp.net", "media.fsub3-1.fna.whatsapp.net", "payments_transaction_limit", "252", "27", "29", "tintagel", "01e", "237", "780", "callee_updated_payload", "020", "257", "price", "025", "239", "payments_cs_phone_number", "mediaretry", "w:auth:backup:token", "Glass.caf", "max_bitrate", "240", "251", "660", "media.fbog16-1.fna.whatsapp.net", "media.fcgh21-1.fna.whatsapp.net", "media.fkul19-2.fna.whatsapp.net", "media.flim21-2.fna.whatsapp.net", "media.fmex10-4.fna.whatsapp.net", "64", "33", "34", "35", "interruption", "media.fabv3-1.fna.whatsapp.net", "media.fadb6-1.fna.whatsapp.net", "media.fagr1-1.fna.whatsapp.net", "media.famd1-1.fna.whatsapp.net", "media.famm6-1.fna.whatsapp.net", "media.faqp2-3.fna.whatsapp.net"]; -module.exports.DICTIONARIES = [module.exports.DICTIONARY_0_TOKEN, module.exports.DICTIONARY_1_TOKEN, module.exports.DICTIONARY_2_TOKEN, module.exports.DICTIONARY_3_TOKEN]; - -module.exports.SINGLE_BYTE_TOKEN_MAP = buildMap(module.exports.SINGLE_BYTE_TOKEN); -module.exports.DICTIONARIES_MAP = module.exports.DICTIONARIES.map((dict) => buildMap(dict)) \ No newline at end of file diff --git a/WABinary/HexHelper.js b/WABinary/HexHelper.js deleted file mode 100644 index e274291..0000000 --- a/WABinary/HexHelper.js +++ /dev/null @@ -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; -}; diff --git a/WABinary/readme.md b/WABinary/readme.md deleted file mode 100644 index 267c69d..0000000 --- a/WABinary/readme.md +++ /dev/null @@ -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) \ No newline at end of file diff --git a/package.json b/package.json index fa1356d..65dfc05 100644 --- a/package.json +++ b/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", diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index fac4634..6890743 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -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 = { 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 \ No newline at end of file diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index 75f3a53..e70169d 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -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) => { diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index 808b497..18a155e 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -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) => { diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index edac1ed..6fe8f55 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -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(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) => { - const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState.creds, range) - - const update: Partial = { - 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, diff --git a/src/Types/Auth.ts b/src/Types/Auth.ts index 7e1c9c7..8392461 100644 --- a/src/Types/Auth.ts +++ b/src/Types/Auth.ts @@ -41,10 +41,10 @@ export type AuthenticationCreds = SignalCreds & { signalIdentities?: SignalIdentity[] myAppStateKeyId?: string firstUnuploadedPreKeyId: number - serverHasPreKeys: boolean nextPreKeyId: number lastAccountSyncTimestamp?: number + platform?: string accountSettings: AccountSettings } diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index 664c1f4..5ac031a 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -108,7 +108,6 @@ export const initAuthCreds = (): AuthenticationCreds => { nextPreKeyId: 1, firstUnuploadedPreKeyId: 1, - serverHasPreKeys: false, accountSettings: { unarchiveChats: false } diff --git a/src/Utils/chat-utils.ts b/src/Utils/chat-utils.ts index 19f4718..3521222 100644 --- a/src/Utils/chat-utils.ts +++ b/src/Utils/chat-utils.ts @@ -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) { diff --git a/src/Utils/crypto.ts b/src/Utils/crypto.ts index 85b8bd1..fa08e22 100644 --- a/src/Utils/crypto.ts +++ b/src/Utils/crypto.ts @@ -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) } \ No newline at end of file diff --git a/src/Utils/generics.ts b/src/Utils/generics.ts index 2dc34c5..82622c7 100644 --- a/src/Utils/generics.ts +++ b/src/Utils/generics.ts @@ -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, diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 6a238e6..baaa1b4 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -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) { diff --git a/src/Utils/noise-handler.ts b/src/Utils/noise-handler.ts index 96a65d4..079b2aa 100644 --- a/src/Utils/noise-handler.ts +++ b/src/Utils/noise-handler.ts @@ -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() + } } } } \ No newline at end of file diff --git a/src/Utils/signal.ts b/src/Utils/signal.ts index 6b87319..10ca261 100644 --- a/src/Utils/signal.ts +++ b/src/Utils/signal.ts @@ -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 = { + 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 } } \ No newline at end of file diff --git a/src/Utils/validate-connection.ts b/src/Utils/validate-connection.ts index 9e25f92..67c4c0a 100644 --- a/src/Utils/validate-connection.ts +++ b/src/Utils/validate-connection.ts @@ -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 -const getUserAgent = ({ version }: Pick): proto.IUserAgent => ({ +const getUserAgent = ({ version, browser }: ClientPayloadConfig): proto.IUserAgent => ({ appVersion: { primary: version[0], secondary: version[1], @@ -19,12 +20,12 @@ const getUserAgent = ({ version }: Pick): 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 ) => { - 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 = { account, - me: { id: jid, verifiedName }, - signalIdentities: [...(signalIdentities || []), identity] + me: { id: jid, name: bizName }, + signalIdentities: [ + ...(signalIdentities || []), + identity + ], + platform: platformNode?.attrs.name } + return { creds: authUpdate, reply diff --git a/src/WABinary/Legacy/constants.ts b/src/WABinary/Legacy/constants.ts deleted file mode 100644 index a9be6d6..0000000 --- a/src/WABinary/Legacy/constants.ts +++ /dev/null @@ -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', -] \ No newline at end of file diff --git a/src/WABinary/Legacy/index.ts b/src/WABinary/Legacy/index.ts index 79dcaf7..5c4c478 100644 --- a/src/WABinary/Legacy/index.ts +++ b/src/WABinary/Legacy/index.ts @@ -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]) } } diff --git a/src/WABinary/constants.ts b/src/WABinary/constants.ts new file mode 100644 index 0000000..d5806f5 --- /dev/null +++ b/src/WABinary/constants.ts @@ -0,0 +1,43 @@ + +export const TAGS = { + LIST_EMPTY: 0, + DICTIONARY_0: 236, + DICTIONARY_1: 237, + DICTIONARY_2: 238, + DICTIONARY_3: 239, + AD_JID: 247, + LIST_8: 248, + LIST_16: 249, + JID_PAIR: 250, + HEX_8: 251, + BINARY_8: 252, + BINARY_20: 253, + BINARY_32: 254, + NIBBLE_8: 255, + PACKED_MAX: 127, + SINGLE_BYTE_MAX: 256, + STREAM_END: 2 +} +export const DOUBLE_BYTE_TOKENS = [ + ['media-for1-1.cdn.whatsapp.net', 'relay', 'media-gru2-2.cdn.whatsapp.net', 'uncompressed', 'medium', 'voip_settings', 'device', 'reason', 'media-lim1-1.cdn.whatsapp.net', 'media-qro1-2.cdn.whatsapp.net', 'media-gru1-2.cdn.whatsapp.net', 'action', 'features', 'media-gru2-1.cdn.whatsapp.net', 'media-gru1-1.cdn.whatsapp.net', 'media-otp1-1.cdn.whatsapp.net', 'kyc-id', 'priority', 'phash', 'mute', 'token', '100', 'media-qro1-1.cdn.whatsapp.net', 'none', 'media-mrs2-2.cdn.whatsapp.net', 'sign_credential', '03', 'media-mrs2-1.cdn.whatsapp.net', 'protocol', 'timezone', 'transport', 'eph_setting', '1080', 'original_dimensions', 'media-frx5-1.cdn.whatsapp.net', 'background', 'disable', 'original_image_url', '5', 'transaction-id', 'direct_path', '103', 'appointment_only', 'request_image_url', 'peer_pid', 'address', '105', '104', '102', 'media-cdt1-1.cdn.whatsapp.net', '101', '109', '110', '106', 'background_location', 'v_id', 'sync', 'status-old', '111', '107', 'ppic', 'media-scl2-1.cdn.whatsapp.net', 'business_profile', '108', 'invite', '04', 'audio_duration', 'media-mct1-1.cdn.whatsapp.net', 'media-cdg2-1.cdn.whatsapp.net', 'media-los2-1.cdn.whatsapp.net', 'invis', 'net', 'voip_payload_type', 'status-revoke-delay', '404', 'state', 'use_correct_order_for_hmac_sha1', 'ver', 'media-mad1-1.cdn.whatsapp.net', 'order', '540', 'skey', 'blinded_credential', 'android', 'contact_remove', 'enable_downlink_relay_latency_only', 'duration', 'enable_vid_one_way_codec_nego', '6', 'media-sof1-1.cdn.whatsapp.net', 'accept', 'all', 'signed_credential', 'media-atl3-1.cdn.whatsapp.net', 'media-lhr8-1.cdn.whatsapp.net', 'website', '05', 'latitude', 'media-dfw5-1.cdn.whatsapp.net', 'forbidden', 'enable_audio_piggyback_network_mtu_fix', 'media-dfw5-2.cdn.whatsapp.net', 'note.m4r', 'media-atl3-2.cdn.whatsapp.net', 'jb_nack_discard_count_fix', 'longitude', 'Opening.m4r', 'media-arn2-1.cdn.whatsapp.net', 'email', 'timestamp', 'admin', 'media-pmo1-1.cdn.whatsapp.net', 'America/Sao_Paulo', 'contact_add', 'media-sin6-1.cdn.whatsapp.net', 'interactive', '8000', 'acs_public_key', 'sigquit_anr_detector_release_rollover_percent', 'media.fmed1-2.fna.whatsapp.net', 'groupadd', 'enabled_for_video_upgrade', 'latency_update_threshold', 'media-frt3-2.cdn.whatsapp.net', 'calls_row_constraint_layout', 'media.fgbb2-1.fna.whatsapp.net', 'mms4_media_retry_notification_encryption_enabled', 'timeout', 'media-sin6-3.cdn.whatsapp.net', 'audio_nack_jitter_multiplier', 'jb_discard_count_adjust_pct_rc', 'audio_reserve_bps', 'delta', 'account_sync', 'default', 'media.fjed4-6.fna.whatsapp.net', '06', 'lock_video_orientation', 'media-frt3-1.cdn.whatsapp.net', 'w:g2', 'media-sin6-2.cdn.whatsapp.net', 'audio_nack_algo_mask', 'media.fgbb2-2.fna.whatsapp.net', 'media.fmed1-1.fna.whatsapp.net', 'cond_range_target_bitrate', 'mms4_server_error_receipt_encryption_enabled', 'vid_rc_dyn', 'fri', 'cart_v1_1_order_message_changes_enabled', 'reg_push', 'jb_hist_deposit_value', 'privatestats', 'media.fist7-2.fna.whatsapp.net', 'thu', 'jb_discard_count_adjust_pct', 'mon', 'group_call_video_maximization_enabled', 'mms_cat_v1_forward_hot_override_enabled', 'audio_nack_new_rtt', 'media.fsub2-3.fna.whatsapp.net', 'media_upload_aggressive_retry_exponential_backoff_enabled', 'tue', 'wed', 'media.fruh4-2.fna.whatsapp.net', 'audio_nack_max_seq_req', 'max_rtp_audio_packet_resends', 'jb_hist_max_cdf_value', '07', 'audio_nack_max_jb_delay', 'mms_forward_partially_downloaded_video', 'media-lcy1-1.cdn.whatsapp.net', 'resume', 'jb_inband_fec_aware', 'new_commerce_entry_point_enabled', '480', 'payments_upi_generate_qr_amount_limit', 'sigquit_anr_detector_rollover_percent', 'media.fsdu2-1.fna.whatsapp.net', 'fbns', 'aud_pkt_reorder_pct', 'dec', 'stop_probing_before_accept_send', 'media_upload_max_aggressive_retries', 'edit_business_profile_new_mode_enabled', 'media.fhex4-1.fna.whatsapp.net', 'media.fjed4-3.fna.whatsapp.net', 'sigquit_anr_detector_64bit_rollover_percent', 'cond_range_ema_jb_last_delay', 'watls_enable_early_data_http_get', 'media.fsdu2-2.fna.whatsapp.net', 'message_qr_disambiguation_enabled', 'media-mxp1-1.cdn.whatsapp.net', 'sat', 'vertical', 'media.fruh4-5.fna.whatsapp.net', '200', 'media-sof1-2.cdn.whatsapp.net', '-1', 'height', 'product_catalog_hide_show_items_enabled', 'deep_copy_frm_last', 'tsoffline', 'vp8/h.264', 'media.fgye5-3.fna.whatsapp.net', 'media.ftuc1-2.fna.whatsapp.net', 'smb_upsell_chat_banner_enabled', 'canonical', '08', '9', '.', 'media.fgyd4-4.fna.whatsapp.net', 'media.fsti4-1.fna.whatsapp.net', 'mms_vcache_aggregation_enabled', 'mms_hot_content_timespan_in_seconds', 'nse_ver', 'rte', 'third_party_sticker_web_sync', 'cond_range_target_total_bitrate', 'media_upload_aggressive_retry_enabled', 'instrument_spam_report_enabled', 'disable_reconnect_tone', 'move_media_folder_from_sister_app', 'one_tap_calling_in_group_chat_size', '10', 'storage_mgmt_banner_threshold_mb', 'enable_backup_passive_mode', 'sharechat_inline_player_enabled', 'media.fcnq2-1.fna.whatsapp.net', 'media.fhex4-2.fna.whatsapp.net', 'media.fist6-3.fna.whatsapp.net', 'ephemeral_drop_column_stage', 'reconnecting_after_network_change_threshold_ms', 'media-lhr8-2.cdn.whatsapp.net', 'cond_jb_last_delay_ema_alpha', 'entry_point_block_logging_enabled', 'critical_event_upload_log_config', 'respect_initial_bitrate_estimate', 'smaller_image_thumbs_status_enabled', 'media.fbtz1-4.fna.whatsapp.net', 'media.fjed4-1.fna.whatsapp.net', 'width', '720', 'enable_frame_dropper', 'enable_one_side_mode', 'urn:xmpp:whatsapp:dirty', 'new_sticker_animation_behavior_v2', 'media.flim3-2.fna.whatsapp.net', 'media.fuio6-2.fna.whatsapp.net', 'skip_forced_signaling', 'dleq_proof', 'status_video_max_bitrate', 'lazy_send_probing_req', 'enhanced_storage_management', 'android_privatestats_endpoint_dit_enabled', 'media.fscl13-2.fna.whatsapp.net', 'video_duration'], + ['group_call_discoverability_enabled', 'media.faep9-2.fna.whatsapp.net', 'msgr', 'bloks_loggedin_access_app_id', 'db_status_migration_step', 'watls_prefer_ip6', 'jabber:iq:privacy', '68', 'media.fsaw1-11.fna.whatsapp.net', 'mms4_media_conn_persist_enabled', 'animated_stickers_thread_clean_up', 'media.fcgk3-2.fna.whatsapp.net', 'media.fcgk4-6.fna.whatsapp.net', 'media.fgye5-2.fna.whatsapp.net', 'media.flpb1-1.fna.whatsapp.net', 'media.fsub2-1.fna.whatsapp.net', 'media.fuio6-3.fna.whatsapp.net', 'not-allowed', 'partial_pjpeg_bw_threshold', 'cap_estimated_bitrate', 'mms_chatd_resume_check_over_thrift', 'smb_upsell_business_profile_enabled', 'product_catalog_webclient', 'groups', 'sigquit_anr_detector_release_updated_rollout', 'syncd_key_rotation_enabled', 'media.fdmm2-1.fna.whatsapp.net', 'media-hou1-1.cdn.whatsapp.net', 'remove_old_chat_notifications', 'smb_biztools_deeplink_enabled', 'use_downloadable_filters_int', 'group_qr_codes_enabled', 'max_receipt_processing_time', 'optimistic_image_processing_enabled', 'smaller_video_thumbs_status_enabled', 'watls_early_data', 'reconnecting_before_relay_failover_threshold_ms', 'cond_range_packet_loss_pct', 'groups_privacy_blacklist', 'status-revoke-drop', 'stickers_animated_thumbnail_download', 'dedupe_transcode_shared_images', 'dedupe_transcode_shared_videos', 'media.fcnq2-2.fna.whatsapp.net', 'media.fgyd4-1.fna.whatsapp.net', 'media.fist7-1.fna.whatsapp.net', 'media.flim3-3.fna.whatsapp.net', 'add_contact_by_qr_enabled', 'https://faq.whatsapp.com/payments', 'multicast_limit_global', 'sticker_notification_preview', 'smb_better_catalog_list_adapters_enabled', 'bloks_use_minscript_android', 'pen_smoothing_enabled', 'media.fcgk4-5.fna.whatsapp.net', 'media.fevn1-3.fna.whatsapp.net', 'media.fpoj7-1.fna.whatsapp.net', 'media-arn2-2.cdn.whatsapp.net', 'reconnecting_before_network_change_threshold_ms', 'android_media_use_fresco_for_gifs', 'cond_in_congestion', 'status_image_max_edge', 'sticker_search_enabled', 'starred_stickers_web_sync', 'db_blank_me_jid_migration_step', 'media.fist6-2.fna.whatsapp.net', 'media.ftuc1-1.fna.whatsapp.net', '09', 'anr_fast_logs_upload_rollout', 'camera_core_integration_enabled', '11', 'third_party_sticker_caching', 'thread_dump_contact_support', 'wam_privatestats_enabled', 'vcard_as_document_size_kb', 'maxfpp', 'fbip', 'ephemeral_allow_group_members', 'media-bom1-2.cdn.whatsapp.net', 'media-xsp1-1.cdn.whatsapp.net', 'disable_prewarm', 'frequently_forwarded_max', 'media.fbtz1-5.fna.whatsapp.net', 'media.fevn7-1.fna.whatsapp.net', 'media.fgyd4-2.fna.whatsapp.net', 'sticker_tray_animation_fully_visible_items', 'green_alert_banner_duration', 'reconnecting_after_p2p_failover_threshold_ms', 'connected', 'share_biz_vcard_enabled', 'stickers_animation', '0a', '1200', 'WhatsApp', 'group_description_length', 'p_v_id', 'payments_upi_intent_transaction_limit', 'frequently_forwarded_messages', 'media-xsp1-2.cdn.whatsapp.net', 'media.faep8-1.fna.whatsapp.net', 'media.faep8-2.fna.whatsapp.net', 'media.faep9-1.fna.whatsapp.net', 'media.fdmm2-2.fna.whatsapp.net', 'media.fgzt3-1.fna.whatsapp.net', 'media.flim4-2.fna.whatsapp.net', 'media.frao1-1.fna.whatsapp.net', 'media.fscl9-2.fna.whatsapp.net', 'media.fsub2-2.fna.whatsapp.net', 'superadmin', 'media.fbog10-1.fna.whatsapp.net', 'media.fcgh28-1.fna.whatsapp.net', 'media.fjdo10-1.fna.whatsapp.net', 'third_party_animated_sticker_import', 'delay_fec', 'attachment_picker_refresh', 'android_linked_devices_re_auth_enabled', 'rc_dyn', 'green_alert_block_jitter', 'add_contact_logging_enabled', 'biz_message_logging_enabled', 'conversation_media_preview_v2', 'media-jnb1-1.cdn.whatsapp.net', 'ab_key', 'media.fcgk4-2.fna.whatsapp.net', 'media.fevn1-1.fna.whatsapp.net', 'media.fist6-1.fna.whatsapp.net', 'media.fruh4-4.fna.whatsapp.net', 'media.fsti4-2.fna.whatsapp.net', 'mms_vcard_autodownload_size_kb', 'watls_enabled', 'notif_ch_override_off', 'media.fsaw1-14.fna.whatsapp.net', 'media.fscl13-1.fna.whatsapp.net', 'db_group_participant_migration_step', '1020', 'cond_range_sterm_rtt', 'invites_logging_enabled', 'triggered_block_enabled', 'group_call_max_participants', 'media-iad3-1.cdn.whatsapp.net', 'product_catalog_open_deeplink', 'shops_required_tos_version', 'image_max_kbytes', 'cond_low_quality_vid_mode', 'db_receipt_migration_step', 'jb_early_prob_hist_shrink', 'media.fdmm2-3.fna.whatsapp.net', 'media.fdmm2-4.fna.whatsapp.net', 'media.fruh4-1.fna.whatsapp.net', 'media.fsaw2-2.fna.whatsapp.net', 'remove_geolocation_videos', 'new_animation_behavior', 'fieldstats_beacon_chance', '403', 'authkey_reset_on_ban', 'continuous_ptt_playback', 'reconnecting_after_relay_failover_threshold_ms', 'false', 'group', 'sun', 'conversation_swipe_to_reply', 'ephemeral_messages_setting', 'smaller_video_thumbs_enabled', 'md_device_sync_enabled', 'bloks_shops_pdp_url_regex', 'lasso_integration_enabled', 'media-bom1-1.cdn.whatsapp.net', 'new_backup_format_enabled', '256', 'media.faep6-1.fna.whatsapp.net', 'media.fasr1-1.fna.whatsapp.net', 'media.fbtz1-7.fna.whatsapp.net', 'media.fesb4-1.fna.whatsapp.net', 'media.fjdo1-2.fna.whatsapp.net', 'media.frba2-1.fna.whatsapp.net', 'watls_no_dns', '600', 'db_broadcast_me_jid_migration_step', 'new_wam_runtime_enabled', 'group_update', 'enhanced_block_enabled', 'sync_wifi_threshold_kb', 'mms_download_nc_cat', 'bloks_minification_enabled', 'ephemeral_messages_enabled', 'reject', 'voip_outgoing_xml_signaling', 'creator', 'dl_bw', 'payments_request_messages', 'target_bitrate', 'bloks_rendercore_enabled', 'media-hbe1-1.cdn.whatsapp.net', 'media-hel3-1.cdn.whatsapp.net', 'media-kut2-2.cdn.whatsapp.net', 'media-lax3-1.cdn.whatsapp.net', 'media-lax3-2.cdn.whatsapp.net', 'sticker_pack_deeplink_enabled', 'hq_image_bw_threshold', 'status_info', 'voip', 'dedupe_transcode_videos', 'grp_uii_cleanup', 'linked_device_max_count', 'media.flim1-1.fna.whatsapp.net', 'media.fsaw2-1.fna.whatsapp.net', 'reconnecting_after_call_active_threshold_ms', '1140', 'catalog_pdp_new_design', 'media.fbtz1-10.fna.whatsapp.net', 'media.fsaw1-15.fna.whatsapp.net', '0b', 'consumer_rc_provider', 'mms_async_fast_forward_ttl', 'jb_eff_size_fix', 'voip_incoming_xml_signaling', 'media_provider_share_by_uuid', 'suspicious_links', 'dedupe_transcode_images', 'green_alert_modal_start', 'media-cgk1-1.cdn.whatsapp.net', 'media-lga3-1.cdn.whatsapp.net', 'template_doc_mime_types', 'important_messages', 'user_add', 'vcard_max_size_kb', 'media.fada2-1.fna.whatsapp.net', 'media.fbog2-5.fna.whatsapp.net', 'media.fbtz1-3.fna.whatsapp.net', 'media.fcgk3-1.fna.whatsapp.net', 'media.fcgk7-1.fna.whatsapp.net', 'media.flim1-3.fna.whatsapp.net', 'media.fscl9-1.fna.whatsapp.net', 'ctwa_context_enterprise_enabled', 'media.fsaw1-13.fna.whatsapp.net', 'media.fuio11-2.fna.whatsapp.net', 'status_collapse_muted', 'db_migration_level_force', 'recent_stickers_web_sync', 'bloks_session_state', 'bloks_shops_enabled', 'green_alert_setting_deep_links_enabled', 'restrict_groups', 'battery', 'green_alert_block_start', 'refresh', 'ctwa_context_enabled', 'md_messaging_enabled', 'status_image_quality', 'md_blocklist_v2_server', 'media-del1-1.cdn.whatsapp.net', '13', 'userrate', 'a_v_id', 'cond_rtt_ema_alpha', 'invalid'], + ['media.fada1-1.fna.whatsapp.net', 'media.fadb3-2.fna.whatsapp.net', 'media.fbhz2-1.fna.whatsapp.net', 'media.fcor2-1.fna.whatsapp.net', 'media.fjed4-2.fna.whatsapp.net', 'media.flhe4-1.fna.whatsapp.net', 'media.frak1-2.fna.whatsapp.net', 'media.fsub6-3.fna.whatsapp.net', 'media.fsub6-7.fna.whatsapp.net', 'media.fvvi1-1.fna.whatsapp.net', 'search_v5_eligible', 'wam_real_time_enabled', 'report_disk_event', 'max_tx_rott_based_bitrate', 'product', 'media.fjdo10-2.fna.whatsapp.net', 'video_frame_crc_sample_interval', 'media_max_autodownload', '15', 'h.264', 'wam_privatestats_buffer_count', 'md_phash_v2_enabled', 'account_transfer_enabled', 'business_product_catalog', 'enable_non_dyn_codec_param_fix', 'is_user_under_epd_jurisdiction', 'media.fbog2-4.fna.whatsapp.net', 'media.fbtz1-2.fna.whatsapp.net', 'media.fcfc1-1.fna.whatsapp.net', 'media.fjed4-5.fna.whatsapp.net', 'media.flhe4-2.fna.whatsapp.net', 'media.flim1-2.fna.whatsapp.net', 'media.flos5-1.fna.whatsapp.net', 'android_key_store_auth_ver', '010', 'anr_process_monitor', 'delete_old_auth_key', 'media.fcor10-3.fna.whatsapp.net', 'storage_usage_enabled', 'android_camera2_support_level', 'dirty', 'consumer_content_provider', 'status_video_max_duration', '0c', 'bloks_cache_enabled', 'media.fadb2-2.fna.whatsapp.net', 'media.fbko1-1.fna.whatsapp.net', 'media.fbtz1-9.fna.whatsapp.net', 'media.fcgk4-4.fna.whatsapp.net', 'media.fesb4-2.fna.whatsapp.net', 'media.fevn1-2.fna.whatsapp.net', 'media.fist2-4.fna.whatsapp.net', 'media.fjdo1-1.fna.whatsapp.net', 'media.fruh4-6.fna.whatsapp.net', 'media.fsrg5-1.fna.whatsapp.net', 'media.fsub6-6.fna.whatsapp.net', 'minfpp', '5000', 'locales', 'video_max_bitrate', 'use_new_auth_key', 'bloks_http_enabled', 'heartbeat_interval', 'media.fbog11-1.fna.whatsapp.net', 'ephemeral_group_query_ts', 'fec_nack', 'search_in_storage_usage', 'c', 'media-amt2-1.cdn.whatsapp.net', 'linked_devices_ui_enabled', '14', 'async_data_load_on_startup', 'voip_incoming_xml_ack', '16', 'db_migration_step', 'init_bwe', 'max_participants', 'wam_buffer_count', 'media.fada2-2.fna.whatsapp.net', 'media.fadb3-1.fna.whatsapp.net', 'media.fcor2-2.fna.whatsapp.net', 'media.fdiy1-2.fna.whatsapp.net', 'media.frba3-2.fna.whatsapp.net', 'media.fsaw2-3.fna.whatsapp.net', '1280', 'status_grid_enabled', 'w:biz', 'product_catalog_deeplink', 'media.fgye10-2.fna.whatsapp.net', 'media.fuio11-1.fna.whatsapp.net', 'optimistic_upload', 'work_manager_init', 'lc', 'catalog_message', 'cond_net_medium', 'enable_periodical_aud_rr_processing', 'cond_range_ema_rtt', 'media-tir2-1.cdn.whatsapp.net', 'frame_ms', 'group_invite_sending', 'payments_web_enabled', 'wallpapers_v2', '0d', 'browser', 'hq_image_max_edge', 'image_edit_zoom', 'linked_devices_re_auth_enabled', 'media.faly3-2.fna.whatsapp.net', 'media.fdoh5-3.fna.whatsapp.net', 'media.fesb3-1.fna.whatsapp.net', 'media.fknu1-1.fna.whatsapp.net', 'media.fmex3-1.fna.whatsapp.net', 'media.fruh4-3.fna.whatsapp.net', '255', 'web_upgrade_to_md_modal', 'audio_piggyback_timeout_msec', 'enable_audio_oob_fec_feature', 'from_ip', 'image_max_edge', 'message_qr_enabled', 'powersave', 'receipt_pre_acking', 'video_max_edge', 'full', '011', '012', 'enable_audio_oob_fec_for_sender', 'md_voip_enabled', 'enable_privatestats', 'max_fec_ratio', 'payments_cs_faq_url', 'media-xsp1-3.cdn.whatsapp.net', 'hq_image_quality', 'media.fasr1-2.fna.whatsapp.net', 'media.fbog3-1.fna.whatsapp.net', 'media.ffjr1-6.fna.whatsapp.net', 'media.fist2-3.fna.whatsapp.net', 'media.flim4-3.fna.whatsapp.net', 'media.fpbc2-4.fna.whatsapp.net', 'media.fpku1-1.fna.whatsapp.net', 'media.frba1-1.fna.whatsapp.net', 'media.fudi1-1.fna.whatsapp.net', 'media.fvvi1-2.fna.whatsapp.net', 'gcm_fg_service', 'enable_dec_ltr_size_check', 'clear', 'lg', 'media.fgru11-1.fna.whatsapp.net', '18', 'media-lga3-2.cdn.whatsapp.net', 'pkey', '0e', 'max_subject', 'cond_range_lterm_rtt', 'announcement_groups', 'biz_profile_options', 's_t', 'media.fabv2-1.fna.whatsapp.net', 'media.fcai3-1.fna.whatsapp.net', 'media.fcgh1-1.fna.whatsapp.net', 'media.fctg1-4.fna.whatsapp.net', 'media.fdiy1-1.fna.whatsapp.net', 'media.fisb4-1.fna.whatsapp.net', 'media.fpku1-2.fna.whatsapp.net', 'media.fros9-1.fna.whatsapp.net', 'status_v3_text', 'usync_sidelist', '17', 'announcement', '...', 'md_group_notification', '0f', 'animated_pack_in_store', '013', 'America/Mexico_City', '1260', 'media-ams4-1.cdn.whatsapp.net', 'media-cgk1-2.cdn.whatsapp.net', 'media-cpt1-1.cdn.whatsapp.net', 'media-maa2-1.cdn.whatsapp.net', 'media.fgye10-1.fna.whatsapp.net', 'e', 'catalog_cart', 'hfm_string_changes', 'init_bitrate', 'packless_hsm', 'group_info', 'America/Belem', '50', '960', 'cond_range_bwe', 'decode', 'encode', 'media.fada1-8.fna.whatsapp.net', 'media.fadb1-2.fna.whatsapp.net', 'media.fasu6-1.fna.whatsapp.net', 'media.fbog4-1.fna.whatsapp.net', 'media.fcgk9-2.fna.whatsapp.net', 'media.fdoh5-2.fna.whatsapp.net', 'media.ffjr1-2.fna.whatsapp.net', 'media.fgua1-1.fna.whatsapp.net', 'media.fgye1-1.fna.whatsapp.net', 'media.fist1-4.fna.whatsapp.net', 'media.fpbc2-2.fna.whatsapp.net', 'media.fres2-1.fna.whatsapp.net', 'media.fsdq1-2.fna.whatsapp.net', 'media.fsub6-5.fna.whatsapp.net', 'profilo_enabled', 'template_hsm', 'use_disorder_prefetching_timer', 'video_codec_priority', 'vpx_max_qp', 'ptt_reduce_recording_delay', '25', 'iphone', 'Windows', 's_o', 'Africa/Lagos', 'abt', 'media-kut2-1.cdn.whatsapp.net', 'media-mba1-1.cdn.whatsapp.net', 'media-mxp1-2.cdn.whatsapp.net', 'md_blocklist_v2', 'url_text', 'enable_short_offset', 'group_join_permissions', 'enable_audio_piggyback_feature', 'image_quality', 'media.fcgk7-2.fna.whatsapp.net', 'media.fcgk8-2.fna.whatsapp.net', 'media.fclo7-1.fna.whatsapp.net', 'media.fcmn1-1.fna.whatsapp.net', 'media.feoh1-1.fna.whatsapp.net', 'media.fgyd4-3.fna.whatsapp.net', 'media.fjed4-4.fna.whatsapp.net', 'media.flim1-4.fna.whatsapp.net', 'media.flim2-4.fna.whatsapp.net', 'media.fplu6-1.fna.whatsapp.net', 'media.frak1-1.fna.whatsapp.net', 'media.fsdq1-1.fna.whatsapp.net', 'to_ip', '015', 'vp8', '19', '21', '1320', 'auth_key_ver', 'message_processing_dedup', 'server-error', 'wap4_enabled', '420', '014', 'cond_range_rtt', 'ptt_fast_lock_enabled', 'media-ort2-1.cdn.whatsapp.net', 'fwd_ui_start_ts'], + ['contact_blacklist', 'Asia/Jakarta', 'media.fepa10-1.fna.whatsapp.net', 'media.fmex10-3.fna.whatsapp.net', 'disorder_prefetching_start_when_empty', 'America/Bogota', 'use_local_probing_rx_bitrate', 'America/Argentina/Buenos_Aires', 'cross_post', 'media.fabb1-1.fna.whatsapp.net', 'media.fbog4-2.fna.whatsapp.net', 'media.fcgk9-1.fna.whatsapp.net', 'media.fcmn2-1.fna.whatsapp.net', 'media.fdel3-1.fna.whatsapp.net', 'media.ffjr1-1.fna.whatsapp.net', 'media.fgdl5-1.fna.whatsapp.net', 'media.flpb1-2.fna.whatsapp.net', 'media.fmex2-1.fna.whatsapp.net', 'media.frba2-2.fna.whatsapp.net', 'media.fros2-2.fna.whatsapp.net', 'media.fruh2-1.fna.whatsapp.net', 'media.fybz2-2.fna.whatsapp.net', 'options', '20', 'a', '017', '018', 'mute_always', 'user_notice', 'Asia/Kolkata', 'gif_provider', 'locked', 'media-gua1-1.cdn.whatsapp.net', 'piggyback_exclude_force_flush', '24', 'media.frec39-1.fna.whatsapp.net', 'user_remove', 'file_max_size', 'cond_packet_loss_pct_ema_alpha', 'media.facc1-1.fna.whatsapp.net', 'media.fadb2-1.fna.whatsapp.net', 'media.faly3-1.fna.whatsapp.net', 'media.fbdo6-2.fna.whatsapp.net', 'media.fcmn2-2.fna.whatsapp.net', 'media.fctg1-3.fna.whatsapp.net', 'media.ffez1-2.fna.whatsapp.net', 'media.fist1-3.fna.whatsapp.net', 'media.fist2-2.fna.whatsapp.net', 'media.flim2-2.fna.whatsapp.net', 'media.fmct2-3.fna.whatsapp.net', 'media.fpei3-1.fna.whatsapp.net', 'media.frba3-1.fna.whatsapp.net', 'media.fsdu8-2.fna.whatsapp.net', 'media.fstu2-1.fna.whatsapp.net', 'media_type', 'receipt_agg', '016', 'enable_pli_for_crc_mismatch', 'live', 'enc_rekey', 'frskmsg', 'd', 'media.fdel11-2.fna.whatsapp.net', 'proto', '2250', 'audio_piggyback_enable_cache', 'skip_nack_if_ltrp_sent', 'mark_dtx_jb_frames', 'web_service_delay', '7282', 'catalog_send_all', 'outgoing', '360', '30', 'LIMITED', '019', 'audio_picker', 'bpv2_phase', 'media.fada1-7.fna.whatsapp.net', 'media.faep7-1.fna.whatsapp.net', 'media.fbko1-2.fna.whatsapp.net', 'media.fbni1-2.fna.whatsapp.net', 'media.fbtz1-1.fna.whatsapp.net', 'media.fbtz1-8.fna.whatsapp.net', 'media.fcjs3-1.fna.whatsapp.net', 'media.fesb3-2.fna.whatsapp.net', 'media.fgdl5-4.fna.whatsapp.net', 'media.fist2-1.fna.whatsapp.net', 'media.flhe2-2.fna.whatsapp.net', 'media.flim2-1.fna.whatsapp.net', 'media.fmex1-1.fna.whatsapp.net', 'media.fpat3-2.fna.whatsapp.net', 'media.fpat3-3.fna.whatsapp.net', 'media.fros2-1.fna.whatsapp.net', 'media.fsdu8-1.fna.whatsapp.net', 'media.fsub3-2.fna.whatsapp.net', 'payments_chat_plugin', 'cond_congestion_no_rtcp_thr', 'green_alert', 'not-a-biz', '..', 'shops_pdp_urls_config', 'source', 'media-dus1-1.cdn.whatsapp.net', 'mute_video', '01b', 'currency', 'max_keys', 'resume_check', 'contact_array', 'qr_scanning', '23', 'b', 'media.fbfh15-1.fna.whatsapp.net', 'media.flim22-1.fna.whatsapp.net', 'media.fsdu11-1.fna.whatsapp.net', 'media.fsdu15-1.fna.whatsapp.net', 'Chrome', 'fts_version', '60', 'media.fada1-6.fna.whatsapp.net', 'media.faep4-2.fna.whatsapp.net', 'media.fbaq5-1.fna.whatsapp.net', 'media.fbni1-1.fna.whatsapp.net', 'media.fcai3-2.fna.whatsapp.net', 'media.fdel3-2.fna.whatsapp.net', 'media.fdmm3-2.fna.whatsapp.net', 'media.fhex3-1.fna.whatsapp.net', 'media.fisb4-2.fna.whatsapp.net', 'media.fkhi5-2.fna.whatsapp.net', 'media.flos2-1.fna.whatsapp.net', 'media.fmct2-1.fna.whatsapp.net', 'media.fntr7-1.fna.whatsapp.net', 'media.frak3-1.fna.whatsapp.net', 'media.fruh5-2.fna.whatsapp.net', 'media.fsub6-1.fna.whatsapp.net', 'media.fuab1-2.fna.whatsapp.net', 'media.fuio1-1.fna.whatsapp.net', 'media.fver1-1.fna.whatsapp.net', 'media.fymy1-1.fna.whatsapp.net', 'product_catalog', '1380', 'audio_oob_fec_max_pkts', '22', '254', 'media-ort2-2.cdn.whatsapp.net', 'media-sjc3-1.cdn.whatsapp.net', '1600', '01a', '01c', '405', 'key_frame_interval', 'body', 'media.fcgh20-1.fna.whatsapp.net', 'media.fesb10-2.fna.whatsapp.net', '125', '2000', 'media.fbsb1-1.fna.whatsapp.net', 'media.fcmn3-2.fna.whatsapp.net', 'media.fcpq1-1.fna.whatsapp.net', 'media.fdel1-2.fna.whatsapp.net', 'media.ffor2-1.fna.whatsapp.net', 'media.fgdl1-4.fna.whatsapp.net', 'media.fhex2-1.fna.whatsapp.net', 'media.fist1-2.fna.whatsapp.net', 'media.fjed5-2.fna.whatsapp.net', 'media.flim6-4.fna.whatsapp.net', 'media.flos2-2.fna.whatsapp.net', 'media.fntr6-2.fna.whatsapp.net', 'media.fpku3-2.fna.whatsapp.net', 'media.fros8-1.fna.whatsapp.net', 'media.fymy1-2.fna.whatsapp.net', 'ul_bw', 'ltrp_qp_offset', 'request', 'nack', 'dtx_delay_state_reset', 'timeoffline', '28', '01f', '32', 'enable_ltr_pool', 'wa_msys_crypto', '01d', '58', 'dtx_freeze_hg_update', 'nack_if_rpsi_throttled', '253', '840', 'media.famd15-1.fna.whatsapp.net', 'media.fbog17-2.fna.whatsapp.net', 'media.fcai19-2.fna.whatsapp.net', 'media.fcai21-4.fna.whatsapp.net', 'media.fesb10-4.fna.whatsapp.net', 'media.fesb10-5.fna.whatsapp.net', 'media.fmaa12-1.fna.whatsapp.net', 'media.fmex11-3.fna.whatsapp.net', 'media.fpoa33-1.fna.whatsapp.net', '1050', '021', 'clean', 'cond_range_ema_packet_loss_pct', 'media.fadb6-5.fna.whatsapp.net', 'media.faqp4-1.fna.whatsapp.net', 'media.fbaq3-1.fna.whatsapp.net', 'media.fbel2-1.fna.whatsapp.net', 'media.fblr4-2.fna.whatsapp.net', 'media.fclo8-1.fna.whatsapp.net', 'media.fcoo1-2.fna.whatsapp.net', 'media.ffjr1-4.fna.whatsapp.net', 'media.ffor9-1.fna.whatsapp.net', 'media.fisb3-1.fna.whatsapp.net', 'media.fkhi2-2.fna.whatsapp.net', 'media.fkhi4-1.fna.whatsapp.net', 'media.fpbc1-2.fna.whatsapp.net', 'media.fruh2-2.fna.whatsapp.net', 'media.fruh5-1.fna.whatsapp.net', 'media.fsub3-1.fna.whatsapp.net', 'payments_transaction_limit', '252', '27', '29', 'tintagel', '01e', '237', '780', 'callee_updated_payload', '020', '257', 'price', '025', '239', 'payments_cs_phone_number', 'mediaretry', 'w:auth:backup:token', 'Glass.caf', 'max_bitrate', '240', '251', '660', 'media.fbog16-1.fna.whatsapp.net', 'media.fcgh21-1.fna.whatsapp.net', 'media.fkul19-2.fna.whatsapp.net', 'media.flim21-2.fna.whatsapp.net', 'media.fmex10-4.fna.whatsapp.net', '64', '33', '34', '35', 'interruption', 'media.fabv3-1.fna.whatsapp.net', 'media.fadb6-1.fna.whatsapp.net', 'media.fagr1-1.fna.whatsapp.net', 'media.famd1-1.fna.whatsapp.net', 'media.famm6-1.fna.whatsapp.net', 'media.faqp2-3.fna.whatsapp.net'], + +] +export const SINGLE_BYTE_TOKENS = [ + '', 'xmlstreamstart', 'xmlstreamend', 's.whatsapp.net', 'type', 'participant', 'from', 'receipt', 'id', 'broadcast', 'status', 'message', 'notification', 'notify', 'to', 'jid', 'user', 'class', 'offline', 'g.us', 'result', 'mediatype', 'enc', 'skmsg', 'off_cnt', 'xmlns', 'presence', 'participants', 'ack', 't', 'iq', 'device_hash', 'read', 'value', 'media', 'picture', 'chatstate', 'unavailable', 'text', 'urn:xmpp:whatsapp:push', 'devices', 'verified_name', 'contact', 'composing', 'edge_routing', 'routing_info', 'item', 'image', 'verified_level', 'get', 'fallback_hostname', '2', 'media_conn', '1', 'v', 'handshake', 'fallback_class', 'count', 'config', 'offline_preview', 'download_buckets', 'w:profile:picture', 'set', 'creation', 'location', 'fallback_ip4', 'msg', 'urn:xmpp:ping', 'fallback_ip6', 'call-creator', 'relaylatency', 'success', 'subscribe', 'video', 'business_hours_config', 'platform', 'hostname', 'version', 'unknown', '0', 'ping', 'hash', 'edit', 'subject', 'max_buckets', 'download', 'delivery', 'props', 'sticker', 'name', 'last', 'contacts', 'business', 'primary', 'preview', 'w:p', 'pkmsg', 'call-id', 'retry', 'prop', 'call', 'auth_ttl', 'available', 'relay_id', 'last_id', 'day_of_week', 'w', 'host', 'seen', 'bits', 'list', 'atn', 'upload', 'is_new', 'w:stats', 'key', 'paused', 'specific_hours', 'multicast', 'stream:error', 'mmg.whatsapp.net', 'code', 'deny', 'played', 'profile', 'fna', 'device-list', 'close_time', 'latency', 'gcm', 'pop', 'audio', '26', 'w:web', 'open_time', 'error', 'auth', 'ip4', 'update', 'profile_options', 'config_value', 'category', 'catalog_not_created', '00', 'config_code', 'mode', 'catalog_status', 'ip6', 'blocklist', 'registration', '7', 'web', 'fail', 'w:m', 'cart_enabled', 'ttl', 'gif', '300', 'device_orientation', 'identity', 'query', '401', 'media-gig2-1.cdn.whatsapp.net', 'in', '3', 'te2', 'add', 'fallback', 'categories', 'ptt', 'encrypt', 'notice', 'thumbnail-document', 'item-not-found', '12', 'thumbnail-image', 'stage', 'thumbnail-link', 'usync', 'out', 'thumbnail-video', '8', '01', 'context', 'sidelist', 'thumbnail-gif', 'terminate', 'not-authorized', 'orientation', 'dhash', 'capability', 'side_list', 'md-app-state', 'description', 'serial', 'readreceipts', 'te', 'business_hours', 'md-msg-hist', 'tag', 'attribute_padding', 'document', 'open_24h', 'delete', 'expiration', 'active', 'prev_v_id', 'true', 'passive', 'index', '4', 'conflict', 'remove', 'w:gp2', 'config_expo_key', 'screen_height', 'replaced', '02', 'screen_width', 'uploadfieldstat', '2:47DEQpj8', 'media-bog1-1.cdn.whatsapp.net', 'encopt', 'url', 'catalog_exists', 'keygen', 'rate', 'offer', 'opus', 'media-mia3-1.cdn.whatsapp.net', 'privacy', 'media-mia3-2.cdn.whatsapp.net', 'signature', 'preaccept', 'token_id', 'media-eze1-1.cdn.whatsapp.net' +] + +const TOKEN_MAP: { [token: string]: { dict?: number, index: number } } = { } +for(let i = 0;i < SINGLE_BYTE_TOKENS.length;i++) { + TOKEN_MAP[SINGLE_BYTE_TOKENS[i]] = { index: i } +} + +for(let i = 0;i < DOUBLE_BYTE_TOKENS.length;i++) { + for(let j = 0;j < DOUBLE_BYTE_TOKENS[i].length;j++) { + TOKEN_MAP[DOUBLE_BYTE_TOKENS[i][j]] = { dict: i, index: j } + } +} + +export { TOKEN_MAP } \ No newline at end of file diff --git a/src/WABinary/decode.ts b/src/WABinary/decode.ts new file mode 100644 index 0000000..888641e --- /dev/null +++ b/src/WABinary/decode.ts @@ -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) +} \ No newline at end of file diff --git a/src/WABinary/encode.ts b/src/WABinary/encode.ts new file mode 100644 index 0000000..9dfb912 --- /dev/null +++ b/src/WABinary/encode.ts @@ -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) => { + 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) +} \ No newline at end of file diff --git a/src/WABinary/index.ts b/src/WABinary/index.ts index 3595df2..f22933f 100644 --- a/src/WABinary/index.ts +++ b/src/WABinary/index.ts @@ -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', '-', '.', '�', '�', '�', '�']; -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' \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2274ac2..3b8ddd6 100644 --- a/yarn.lock +++ b/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: