diff --git a/app/providers/ProtocolProvider/protocol/packets/packet.message.ts b/app/providers/ProtocolProvider/protocol/packets/packet.message.ts index 323908b..52cad0d 100644 --- a/app/providers/ProtocolProvider/protocol/packets/packet.message.ts +++ b/app/providers/ProtocolProvider/protocol/packets/packet.message.ts @@ -14,6 +14,12 @@ export interface Attachment { blob: string; type: AttachmentType; preview: string; + transport_tag: string; + transport_server: string; + /** + * Обозначает, для кого закодировано это вложение, нужно для того, чтобы не кодировать и не загружать заново пересланные сообщения, если они уже были закодированы для этого диалога + */ + encoded_for: string; } export class PacketMessage extends Packet { @@ -42,7 +48,7 @@ export class PacketMessage extends Packet { this.toPublicKey = stream.readString(); this.content = stream.readString(); this.chachaKey = stream.readString(); - this.timestamp = stream.readInt64(); + this.timestamp = Number(stream.readInt64()); this.privateKey = stream.readString(); this.messageId = stream.readString(); let attachmentsCount = stream.readInt8(); @@ -51,7 +57,10 @@ export class PacketMessage extends Packet { let preview = stream.readString(); let blob = stream.readString(); let type = stream.readInt8() as AttachmentType; - this.attachments.push({id, preview, type, blob}); + let transport_tag = stream.readString(); + let transport_server = stream.readString(); + let encoded_for = stream.readString(); + this.attachments.push({id, preview, type, blob, transport_tag, transport_server, encoded_for}); } this.aesChachaKey = stream.readString(); } @@ -63,7 +72,7 @@ export class PacketMessage extends Packet { stream.writeString(this.toPublicKey); stream.writeString(this.content); stream.writeString(this.chachaKey); - stream.writeInt64(this.timestamp); + stream.writeInt64(BigInt(this.timestamp)); stream.writeString(this.privateKey); stream.writeString(this.messageId); stream.writeInt8(this.attachments.length); @@ -72,6 +81,9 @@ export class PacketMessage extends Packet { stream.writeString(this.attachments[i].preview); stream.writeString(this.attachments[i].blob); stream.writeInt8(this.attachments[i].type); + stream.writeString(this.attachments[i].transport_tag); + stream.writeString(this.attachments[i].transport_server); + stream.writeString(this.attachments[i].encoded_for); } stream.writeString(this.aesChachaKey); return stream; diff --git a/app/providers/ProtocolProvider/protocol/packets/packet.sync.ts b/app/providers/ProtocolProvider/protocol/packets/packet.sync.ts index b235991..889093b 100644 --- a/app/providers/ProtocolProvider/protocol/packets/packet.sync.ts +++ b/app/providers/ProtocolProvider/protocol/packets/packet.sync.ts @@ -18,14 +18,14 @@ export class PacketSync extends Packet { public _receive(stream: Stream): void { this.status = stream.readInt8() as SyncStatus; - this.timestamp = stream.readInt64(); + this.timestamp = Number(stream.readInt64()); } public _send(): Promise | Stream { let stream = new Stream(); stream.writeInt16(this.getPacketId()); stream.writeInt8(this.status); - stream.writeInt64(this.timestamp); + stream.writeInt64(BigInt(this.timestamp)); return stream; } diff --git a/app/providers/ProtocolProvider/protocol/stream.ts b/app/providers/ProtocolProvider/protocol/stream.ts index d82acfb..a38512b 100644 --- a/app/providers/ProtocolProvider/protocol/stream.ts +++ b/app/providers/ProtocolProvider/protocol/stream.ts @@ -1,151 +1,372 @@ export default class Stream { - - private _stream: number[]; - private _readPoiner: number = 0; - private _writePointer: number = 0; + private stream: Uint8Array; + private readPointer = 0; // bits + private writePointer = 0; // bits - constructor(stream : number[] = []) { - this._stream = stream; + constructor(stream?: Uint8Array | number[]) { + if (!stream) { + this.stream = new Uint8Array(0); + } else { + const src = stream instanceof Uint8Array ? stream : Uint8Array.from(stream); + this.stream = src; + this.writePointer = this.stream.length << 3; + } + } + + getStream(): Uint8Array { + return this.stream.slice(0, this.length()); + } + + setStream(stream?: Uint8Array | number[]) { + if (!stream) { + this.stream = new Uint8Array(0); + this.readPointer = 0; + this.writePointer = 0; + return; + } + const src = stream instanceof Uint8Array ? stream : Uint8Array.from(stream); + this.stream = src; + this.readPointer = 0; + this.writePointer = this.stream.length << 3; + } + + getBuffer(): Uint8Array { + return this.getStream(); + } + + isEmpty(): boolean { + return this.writePointer === 0; + } + + length(): number { + return (this.writePointer + 7) >> 3; + } + + // ---------- bit / boolean ---------- + + writeBit(value: number) { + this.writeBits(BigInt(value & 1), 1); + } + + readBit(): number { + return Number(this.readBits(1)); + } + + writeBoolean(value: boolean) { + this.writeBit(value ? 1 : 0); + } + + readBoolean(): boolean { + return this.readBit() === 1; + } + + // ---------- byte ---------- + + writeByte(b: number) { + this.writeUInt8(b & 0xff); + } + + readByte(): number { + const v = this.readUInt8(); + return (v << 24) >> 24; // signed byte + } + + // ---------- UInt / Int 8 ---------- + + writeUInt8(value: number) { + const v = value & 0xff; + + if ((this.writePointer & 7) === 0) { + this.reserveBits(8); + this.stream[this.writePointer >> 3] = v; + this.writePointer += 8; + return; } - public getStream(): number[] { - return this._stream; + this.writeBits(BigInt(v), 8); + } + + readUInt8(): number { + if (this.remainingBits() < 8n) { + throw new Error("Not enough bits to read UInt8"); } - public setStream(stream: number[]) { - this._stream = stream; + if ((this.readPointer & 7) === 0) { + const v = this.stream[this.readPointer >> 3] & 0xff; + this.readPointer += 8; + return v; } - public writeInt8(value: number) { - const negationBit = value < 0 ? 1 : 0; - const int8Value = Math.abs(value) & 0xFF; - this._stream[this._writePointer >> 3] |= negationBit << (7 - (this._writePointer & 7)); - this._writePointer++; - for (let i = 0; i < 8; i++) { - const bit = (int8Value >> (7 - i)) & 1; - this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7)); - this._writePointer++; - } + return Number(this.readBits(8)); + } + + writeInt8(value: number) { + this.writeUInt8(value); + } + + readInt8(): number { + const u = this.readUInt8(); + return (u << 24) >> 24; + } + + // ---------- UInt / Int 16 ---------- + + writeUInt16(value: number) { + const v = value & 0xffff; + this.writeUInt8((v >>> 8) & 0xff); + this.writeUInt8(v & 0xff); + } + + readUInt16(): number { + const hi = this.readUInt8(); + const lo = this.readUInt8(); + return (hi << 8) | lo; + } + + writeInt16(value: number) { + this.writeUInt16(value); + } + + readInt16(): number { + const u = this.readUInt16(); + return (u << 16) >> 16; + } + + // ---------- UInt / Int 32 ---------- + + writeUInt32(value: number) { + if (!Number.isFinite(value) || value < 0 || value > 0xffffffff) { + throw new Error(`UInt32 out of range: ${value}`); } - public readInt8(): number { - let value = 0; - const negationBit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1; - this._readPoiner++; - for (let i = 0; i < 8; i++) { - const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1; - value |= bit << (7 - i); - this._readPoiner++; - } - return negationBit ? -value : value; + const v = Math.floor(value); + this.writeUInt8((v >>> 24) & 0xff); + this.writeUInt8((v >>> 16) & 0xff); + this.writeUInt8((v >>> 8) & 0xff); + this.writeUInt8(v & 0xff); + } + + readUInt32(): number { + const b1 = this.readUInt8(); + const b2 = this.readUInt8(); + const b3 = this.readUInt8(); + const b4 = this.readUInt8(); + return (((b1 * 0x1000000) + (b2 << 16) + (b3 << 8) + b4) >>> 0); + } + + writeInt32(value: number) { + this.writeUInt32(value >>> 0); + } + + readInt32(): number { + return this.readUInt32() | 0; + } + + // ---------- UInt / Int 64 ---------- + + writeUInt64(value: bigint) { + if (value < 0n || value > 0xffff_ffff_ffff_ffffn) { + throw new Error(`UInt64 out of range: ${value.toString()}`); } - public writeBit(value: number) { - const bit = value & 1; - this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7)); - this._writePointer++; + this.writeUInt8(Number((value >> 56n) & 0xffn)); + this.writeUInt8(Number((value >> 48n) & 0xffn)); + this.writeUInt8(Number((value >> 40n) & 0xffn)); + this.writeUInt8(Number((value >> 32n) & 0xffn)); + this.writeUInt8(Number((value >> 24n) & 0xffn)); + this.writeUInt8(Number((value >> 16n) & 0xffn)); + this.writeUInt8(Number((value >> 8n) & 0xffn)); + this.writeUInt8(Number(value & 0xffn)); + } + + readUInt64(): bigint { + const high = BigInt(this.readUInt32() >>> 0); + const low = BigInt(this.readUInt32() >>> 0); + return (high << 32n) | low; + } + + writeInt64(value: bigint) { + const u = BigInt.asUintN(64, value); + this.writeUInt64(u); + } + + readInt64(): bigint { + return BigInt.asIntN(64, this.readUInt64()); + } + + // ---------- float ---------- + + writeFloat32(value: number) { + const ab = new ArrayBuffer(4); + const dv = new DataView(ab); + dv.setFloat32(0, value, false); // big-endian + this.writeUInt8(dv.getUint8(0)); + this.writeUInt8(dv.getUint8(1)); + this.writeUInt8(dv.getUint8(2)); + this.writeUInt8(dv.getUint8(3)); + } + + readFloat32(): number { + const ab = new ArrayBuffer(4); + const dv = new DataView(ab); + dv.setUint8(0, this.readUInt8()); + dv.setUint8(1, this.readUInt8()); + dv.setUint8(2, this.readUInt8()); + dv.setUint8(3, this.readUInt8()); + return dv.getFloat32(0, false); // big-endian + } + + // ---------- string / bytes ---------- + // String: length(UInt32) + chars(UInt16), как в Java charAt() + + writeString(value: string | null | undefined) { + const s = value ?? ""; + this.writeUInt32(s.length); + + if (s.length === 0) return; + + this.reserveBits(BigInt(s.length) * 16n); + for (let i = 0; i < s.length; i++) { + this.writeUInt16(s.charCodeAt(i) & 0xffff); + } + } + + readString(): string { + const len = this.readUInt32(); + if (len > 0x7fffffff) { + throw new Error(`String length too large: ${len}`); } - public readBit(): number { - const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1; - this._readPoiner++; - return bit; + const requiredBits = BigInt(len) * 16n; + if (requiredBits > this.remainingBits()) { + throw new Error("Not enough bits to read string"); } - public writeBoolean(value: boolean) { - this.writeBit(value ? 1 : 0); + const chars = new Array(len); + for (let i = 0; i < len; i++) { + chars[i] = this.readUInt16(); + } + return String.fromCharCode(...chars); + } + + // byte[]: length(UInt32) + payload + writeBytes(value: Uint8Array | number[] | null | undefined) { + const arr = value == null + ? new Uint8Array(0) + : (value instanceof Uint8Array ? value : Uint8Array.from(value)); + + this.writeUInt32(arr.length); + if (arr.length === 0) return; + + this.reserveBits(BigInt(arr.length) * 8n); + + if ((this.writePointer & 7) === 0) { + const byteIndex = this.writePointer >> 3; + this.ensureCapacity(byteIndex + arr.length - 1); + this.stream.set(arr, byteIndex); + this.writePointer += arr.length << 3; + return; } - public readBoolean(): boolean { - return this.readBit() === 1; + for (let i = 0; i < arr.length; i++) { + this.writeUInt8(arr[i]); } - - public writeInt16(value: number) { - this.writeInt8(value >> 8); - this.writeInt8(value & 0xFF); + } + + readBytes(): Uint8Array { + const len = this.readUInt32(); + if (len === 0) return new Uint8Array(0); + + const requiredBits = BigInt(len) * 8n; + if (requiredBits > this.remainingBits()) { + return new Uint8Array(0); } - public readInt16(): number { - const value = this.readInt8() << 8; - return value | this.readInt8(); + const out = new Uint8Array(len); + + if ((this.readPointer & 7) === 0) { + const byteIndex = this.readPointer >> 3; + out.set(this.stream.slice(byteIndex, byteIndex + len)); + this.readPointer += len << 3; + return out; } - public writeInt32(value: number) { - this.writeInt16(value >> 16); - this.writeInt16(value & 0xFFFF); + for (let i = 0; i < len; i++) { + out[i] = this.readUInt8(); + } + return out; + } + + // ---------- internals ---------- + + private remainingBits(): bigint { + return BigInt(this.writePointer - this.readPointer); + } + + private writeBits(value: bigint, bits: number) { + if (bits <= 0) return; + + this.reserveBits(bits); + + for (let i = bits - 1; i >= 0; i--) { + const bit = Number((value >> BigInt(i)) & 1n); + const byteIndex = this.writePointer >> 3; + const shift = 7 - (this.writePointer & 7); + + if (bit === 1) { + this.stream[byteIndex] = this.stream[byteIndex] | (1 << shift); + } else { + this.stream[byteIndex] = this.stream[byteIndex] & ~(1 << shift); + } + this.writePointer++; + } + } + + private readBits(bits: number): bigint { + if (bits <= 0) return 0n; + if (this.remainingBits() < BigInt(bits)) { + throw new Error("Not enough bits to read"); } - public readInt32(): number { - const value = this.readInt16() << 16; - return value | this.readInt16(); + let value = 0n; + for (let i = 0; i < bits; i++) { + const bit = (this.stream[this.readPointer >> 3] >> (7 - (this.readPointer & 7))) & 1; + value = (value << 1n) | BigInt(bit); + this.readPointer++; + } + return value; + } + + private reserveBits(bitsToWrite: number | bigint) { + const bits = typeof bitsToWrite === "number" ? BigInt(bitsToWrite) : bitsToWrite; + if (bits <= 0n) return; + + const lastBitIndex = BigInt(this.writePointer) + bits - 1n; + if (lastBitIndex < 0n) throw new Error("Bit index overflow"); + + const byteIndex = lastBitIndex >> 3n; + if (byteIndex > BigInt(Number.MAX_SAFE_INTEGER)) { + throw new Error("Stream too large"); } - public writeInt64(value: number) { - const high = Math.floor(value / 0x100000000); - const low = value >>> 0; - this.writeInt32(high); - this.writeInt32(low); - } - - public readInt64(): number { - const high = this.readInt32(); - const low = this.readInt32() >>> 0; - return high * 0x100000000 + low; - } - - public writeFloat32(value: number) { - const buffer = new ArrayBuffer(4); - new DataView(buffer).setFloat32(0, value, true); - const float32Value = new Uint32Array(buffer)[0]; - this.writeInt32(float32Value); - } - - public readFloat32(): number { - const float32Value = this.readInt32(); - const buffer = new ArrayBuffer(4); - new Uint32Array(buffer)[0] = float32Value; - return new DataView(buffer).getFloat32(0, true); - } - - public writeString(value: string) { - let length = value.length; - this.writeInt32(length); - for (let i = 0; i < value.length; i++) { - this.writeInt16(value.charCodeAt(i)); - } - } - - public readString(): string { - let length = this.readInt32(); - /** - * Фикс уязвимости с длинной строки, превышающей - * возможность для чтения _stream - */ - if (length < 0 || length > (this._stream.length - (this._readPoiner >> 3))) { - console.info("Stream readString length invalid", length, this._stream.length, this._readPoiner); - return ""; - } - let value = ""; - for (let i = 0; i < length; i++) { - value += String.fromCharCode(this.readInt16()); - } - return value; - } - - public writeBytes(value: number[]) { - this.writeInt32(value.length); - for (let i = 0; i < value.length; i++) { - this.writeInt8(value[i]); - } - } - - public readBytes(): number[] { - let length = this.readInt32(); - let value : any = []; - for (let i = 0; i < length; i++) { - value.push(this.readInt8()); - } - return value; + this.ensureCapacity(Number(byteIndex)); + } + + private ensureCapacity(byteIndex: number) { + const requiredSize = byteIndex + 1; + if (requiredSize <= this.stream.length) return; + + let newSize = this.stream.length === 0 ? 32 : this.stream.length; + while (newSize < requiredSize) { + if (newSize > (0x7fffffff >> 1)) { + newSize = requiredSize; + break; + } + newSize <<= 1; } + const next = new Uint8Array(newSize); + next.set(this.stream); + this.stream = next; + } } \ No newline at end of file