diff --git a/UC/src/network/socket_communicator/socket_communicator_base.ts b/UC/src/network/socket_communicator/socket_communicator_base.ts index f23aa76..622cb30 100644 --- a/UC/src/network/socket_communicator/socket_communicator_base.ts +++ b/UC/src/network/socket_communicator/socket_communicator_base.ts @@ -1,24 +1,114 @@ import { ParsedMessage } from '../message_handler'; import { OperationHandler } from '../operations_base/operation_handler'; import ping from "ping"; +import { + constants, + createCipheriv, + createDecipheriv, + generateKeyPairSync, + privateEncrypt, + publicDecrypt, + randomBytes +} from "crypto"; export abstract class SocketCommunicatorBase { protected readonly ip: string; protected readonly port: number; protected readonly operationHandler: OperationHandler; protected handlerResult: ParsedMessage | null; - protected networkSpeed: number | null = null; // Estimated network speed in bytes/ms + protected networkSpeed: number | null = null; + + protected chunkBuffers: { [messageId: string]: string[] }; + protected readonly EOP = ''; + + protected privateKey: string | null; + protected publicKey: string | null; + protected aesKey: Buffer | null; + protected aesIv: Buffer | null; protected constructor(ip: string, port: number, operationHandler: OperationHandler) { this.ip = ip; this.port = port; this.operationHandler = operationHandler this.handlerResult = null; + this.chunkBuffers = {}; + + this.privateKey = null; + this.publicKey = null; + this.aesKey = null; + this.aesIv = null; } // Getter for the handler result getHandlerResult(): ParsedMessage | null { - return this.handlerResult; + const result = this.handlerResult; + this.handlerResult = null; + return result; + } + + protected generateKeyPair(): void { + const { privateKey, publicKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + }); + this.privateKey = privateKey; + this.publicKey = publicKey; + } + + protected generateAesKey(): void { + this.aesKey = randomBytes(32); + this.aesIv = randomBytes(16); + } + + protected encryptWithAes(message: string): string { + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not set.'); + } + const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); + let encrypted = cipher.update(message, 'utf-8'); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return encrypted.toString('base64'); + } + + protected decryptWithAes(encryptedMessage: string): string { + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not set.'); + } + const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); + let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString('utf-8'); + } + + protected decryptWithRsa(message: string): string { + if (!this.publicKey) { + throw new Error('Server public key not set.'); + } + try { + const encryptedMessage = Buffer.from(message, 'base64'); + const decrypted = publicDecrypt( + { + key: this.publicKey, + padding: constants.RSA_PKCS1_PADDING, + }, + encryptedMessage + ); + return decrypted.toString('utf-8'); + } catch (error) { + throw new Error('Failed to decrypt RSA message.'); + } + } + + protected encryptWithRsa(message: string): string { + if (!this.privateKey) throw new Error('Server private key not set.'); + return privateEncrypt( + { + key: this.privateKey, + padding: constants.RSA_PKCS1_PADDING, + }, + Buffer.from(message) + ).toString('base64'); } protected async scanNetworkLatency(): Promise { @@ -54,4 +144,41 @@ export abstract class SocketCommunicatorBase { return chunkSize || 1024; // Fallback to 1024 if alignment adjustment results in 0 } + + async handleIncomingChunk(data: Buffer): Promise { + const incomingData = data.toString().trim(); + + // Split the incoming data by to handle multiple chunks concatenated by TCP + const messages = incomingData.split(this.EOP).filter(Boolean); // Filter out any empty strings from split + + for (const incomingMessage of messages) { + const [headerJson, chunkContent] = incomingMessage.split('|'); + const header = JSON.parse(headerJson); + + // Initialize an array for chunks if it's the first chunk received for this messageId + if (!this.chunkBuffers[header.messageId]) { + this.chunkBuffers[header.messageId] = []; + } + + // Directly set the chunk at the correct index, adjusting for 1-based indexing + this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; + + console.log(`Received chunk: ${incomingMessage}`); + console.log(`Chunks received so far: ${this.chunkBuffers[header.messageId].filter(chunk => chunk !== undefined).length}`); + + // Check if all chunks have been received by confirming the length + if (this.chunkBuffers[header.messageId].filter(chunk => chunk !== undefined).length === header.totalChunks) { + const chunks = this.chunkBuffers[header.messageId]; + const fullMessage = chunks.join(''); + + await this.handleIncomingMessage(fullMessage); + + delete this.chunkBuffers[header.messageId]; + } + } + } + + abstract handleIncomingMessage(incomingMessage: string): Promise; + + abstract sendMessage(message: string): Promise; } diff --git a/UC/src/network/socket_communicator/tcp_server_communicator.ts b/UC/src/network/socket_communicator/tcp_server_communicator.ts index 0550dcc..b88a273 100644 --- a/UC/src/network/socket_communicator/tcp_server_communicator.ts +++ b/UC/src/network/socket_communicator/tcp_server_communicator.ts @@ -1,118 +1,48 @@ import { Socket } from 'net'; -import { privateEncrypt, generateKeyPairSync, createCipheriv, createDecipheriv, randomBytes, constants } from 'crypto'; import { MessageHandler } from '../message_handler'; import { SocketCommunicatorBase } from './socket_communicator_base'; import { OperationHandler } from '../operations_base/operation_handler'; export class TcpServerCommunicator extends SocketCommunicatorBase { private readonly socket: Socket; - private privateKey: string | null; - private publicKey: string | null; - private aesKey: Buffer | null; - private aesIv: Buffer | null; - private chunkBuffers: { [messageId: string]: string[] }; constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { super(ip, port, operationHandler); this.socket = socket; - this.privateKey = null; - this.publicKey = null; // Client public key will be set later - this.aesKey = null; - this.aesIv = null; - this.chunkBuffers = {}; - this.generateKeyPair(); - } - - generateKeyPair(): void { - const { privateKey, publicKey } = generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { type: 'spki', format: 'pem' }, - privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, - }); - this.privateKey = privateKey; - this.publicKey = publicKey; } async sendPublicKey(): Promise { - if (!this.publicKey) { - throw new Error('Public key is not available. Please generate RSA key pair.'); + this.generateKeyPair(); + if (!this.publicKey || !this.privateKey) { + throw new Error('RSA key pair is not available. Please generate RSA key pair.'); } - await this.sendChunkedMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey }); + + await this.sendMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey }); } async sendAesKey(): Promise { - this.aesKey = randomBytes(32); - this.aesIv = randomBytes(16); + this.generateAesKey(); + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not available. Please generate AES key.'); + } + const aesKeyBase64 = this.aesKey.toString('base64'); const aesIvBase64 = this.aesIv.toString('base64'); - await this.sendChunkedMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 }); + await this.sendMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 }); } - private encryptWithRsa(message: string): string { - if (!this.privateKey) throw new Error('Server private key not set.'); - return privateEncrypt( - { - key: this.privateKey, - padding: constants.RSA_PKCS1_PADDING, - }, - Buffer.from(message) - ).toString('base64'); + async handleIncomingMessage(incomingMessage: string): Promise { + const messageToProcess = this.decryptWithAes(incomingMessage); + this.handlerResult = await this.operationHandler.handleOperation(messageToProcess); } - private decryptWithAes(encryptedMessage: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key not set.'); - } - const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); - decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted.toString('utf-8'); - } - - private encryptWithAes(message: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not set.'); - } - const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let encrypted = cipher.update(message, 'utf-8'); - encrypted = Buffer.concat([encrypted, cipher.final()]); - return encrypted.toString('base64'); - } - - async handleIncomingChunk(data: Buffer): Promise { - const incomingMessage = data.toString().trim(); - const [headerJson, chunkContent] = incomingMessage.split('|'); - const header = JSON.parse(headerJson); - if (!this.chunkBuffers[header.messageId]) { - this.chunkBuffers[header.messageId] = new Array(header.totalChunks).fill(undefined); - } - this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; - if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { - await this.processCompleteMessage(header.messageId); - } - } - - private async processCompleteMessage(messageId: string): Promise { - const chunks = this.chunkBuffers[messageId]; - if (chunks && chunks.every((chunk) => chunk !== undefined)) { - const fullMessage = chunks.join(''); - await this.handleIncomingMessage(fullMessage); - delete this.chunkBuffers[messageId]; - } - } - - async sendChunkedMessage( - operationCode: string, - metaInfo?: { [key: string]: any }, - fileContent?: Buffer - ): Promise { - // Check if latency-based chunk size needs calculation + async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { if (!this.networkSpeed) { this.networkSpeed = await this.scanNetworkLatency(); } - // Format the message for sending const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); + let outgoingMessage: string; switch (operationCode) { case 'SET_PUBLIC_KEY': @@ -138,32 +68,8 @@ export class TcpServerCommunicator extends SocketCommunicatorBase { sequenceNumber: i + 1, totalChunks, }); - const chunkWithHeader = `${chunkHeader}|${chunk}`; - await this.writeToSocket(chunkWithHeader); - - // Set delay based on latency for smoother transmission - const delay = Math.max(100, Math.min(300, this.networkSpeed * 10)); - await new Promise((resolve) => setTimeout(resolve, delay)); + const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; + if(!this.socket.write(chunkWithHeader)) this.socket.end(); } } - - async handleIncomingMessage(incomingMessage: string): Promise { - let messageToProcess = incomingMessage; - if (this.aesKey && this.aesIv) { - messageToProcess = this.decryptWithAes(incomingMessage); - } - this.handlerResult = await this.operationHandler.handleOperation(messageToProcess); - } - - protected writeToSocket(message: string): Promise { - return new Promise((resolve, reject) => { - this.socket.write(message, (err: any) => { - if (err) { - console.error('Error sending message over TCP:', err); - return reject(err); - } - resolve(); - }); - }); - } } diff --git a/UC/src/network/socket_communicator/udp_socket_communicator.ts b/UC/src/network/socket_communicator/udp_socket_communicator.ts index 82b2b50..f9f4e48 100644 --- a/UC/src/network/socket_communicator/udp_socket_communicator.ts +++ b/UC/src/network/socket_communicator/udp_socket_communicator.ts @@ -11,20 +11,9 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase { this.socket = socket; } - // Handle incoming message (no decryption needed for UDP) - handleIncomingMessage(incomingMessage: string): void { - this.handlerResult = this.operationHandler.handleOperation(incomingMessage); - } - // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) - async sendMessage(operationCode: string, metaInfo?: any, fileContent?: Buffer): Promise { + async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); - - await this.sendUdpMessage(message); - } - - // Helper method to wrap socket.send in a Promise for async/await support - private sendUdpMessage(message: string): Promise { return new Promise((resolve, reject) => { this.socket.send(message, this.port, this.ip, (err: any) => { if (err) { @@ -36,4 +25,9 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase { }); }); } + + // Handle incoming message (no decryption needed for UDP) + async handleIncomingMessage(incomingMessage: string): Promise { + this.handlerResult = await this.operationHandler.handleOperation(incomingMessage); + } } diff --git a/UC/src/tcp_server.ts b/UC/src/tcp_server.ts index 1c60e3e..bc1dcb8 100644 --- a/UC/src/tcp_server.ts +++ b/UC/src/tcp_server.ts @@ -58,7 +58,6 @@ export class TcpServer { const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler); this.connectionManager.addConnection(ip, port, tcpCommunicator); - tcpCommunicator.generateKeyPair(); tcpCommunicator.sendPublicKey() .then(() => tcpCommunicator.sendAesKey()) .then(() => this.log('Public key and AES key sent successfully.')) @@ -106,7 +105,7 @@ export class TcpServer { const handlerResult = communicator.getHandlerResult(); if (handlerResult) { try { - await communicator.sendChunkedMessage( + await communicator.sendMessage( handlerResult.operationCode, handlerResult.metaInfo, handlerResult.fileContent diff --git a/User/src/network/socket_communicator/socket_communicator_base.ts b/User/src/network/socket_communicator/socket_communicator_base.ts index f23aa76..622cb30 100644 --- a/User/src/network/socket_communicator/socket_communicator_base.ts +++ b/User/src/network/socket_communicator/socket_communicator_base.ts @@ -1,24 +1,114 @@ import { ParsedMessage } from '../message_handler'; import { OperationHandler } from '../operations_base/operation_handler'; import ping from "ping"; +import { + constants, + createCipheriv, + createDecipheriv, + generateKeyPairSync, + privateEncrypt, + publicDecrypt, + randomBytes +} from "crypto"; export abstract class SocketCommunicatorBase { protected readonly ip: string; protected readonly port: number; protected readonly operationHandler: OperationHandler; protected handlerResult: ParsedMessage | null; - protected networkSpeed: number | null = null; // Estimated network speed in bytes/ms + protected networkSpeed: number | null = null; + + protected chunkBuffers: { [messageId: string]: string[] }; + protected readonly EOP = ''; + + protected privateKey: string | null; + protected publicKey: string | null; + protected aesKey: Buffer | null; + protected aesIv: Buffer | null; protected constructor(ip: string, port: number, operationHandler: OperationHandler) { this.ip = ip; this.port = port; this.operationHandler = operationHandler this.handlerResult = null; + this.chunkBuffers = {}; + + this.privateKey = null; + this.publicKey = null; + this.aesKey = null; + this.aesIv = null; } // Getter for the handler result getHandlerResult(): ParsedMessage | null { - return this.handlerResult; + const result = this.handlerResult; + this.handlerResult = null; + return result; + } + + protected generateKeyPair(): void { + const { privateKey, publicKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + }); + this.privateKey = privateKey; + this.publicKey = publicKey; + } + + protected generateAesKey(): void { + this.aesKey = randomBytes(32); + this.aesIv = randomBytes(16); + } + + protected encryptWithAes(message: string): string { + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not set.'); + } + const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); + let encrypted = cipher.update(message, 'utf-8'); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return encrypted.toString('base64'); + } + + protected decryptWithAes(encryptedMessage: string): string { + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not set.'); + } + const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); + let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString('utf-8'); + } + + protected decryptWithRsa(message: string): string { + if (!this.publicKey) { + throw new Error('Server public key not set.'); + } + try { + const encryptedMessage = Buffer.from(message, 'base64'); + const decrypted = publicDecrypt( + { + key: this.publicKey, + padding: constants.RSA_PKCS1_PADDING, + }, + encryptedMessage + ); + return decrypted.toString('utf-8'); + } catch (error) { + throw new Error('Failed to decrypt RSA message.'); + } + } + + protected encryptWithRsa(message: string): string { + if (!this.privateKey) throw new Error('Server private key not set.'); + return privateEncrypt( + { + key: this.privateKey, + padding: constants.RSA_PKCS1_PADDING, + }, + Buffer.from(message) + ).toString('base64'); } protected async scanNetworkLatency(): Promise { @@ -54,4 +144,41 @@ export abstract class SocketCommunicatorBase { return chunkSize || 1024; // Fallback to 1024 if alignment adjustment results in 0 } + + async handleIncomingChunk(data: Buffer): Promise { + const incomingData = data.toString().trim(); + + // Split the incoming data by to handle multiple chunks concatenated by TCP + const messages = incomingData.split(this.EOP).filter(Boolean); // Filter out any empty strings from split + + for (const incomingMessage of messages) { + const [headerJson, chunkContent] = incomingMessage.split('|'); + const header = JSON.parse(headerJson); + + // Initialize an array for chunks if it's the first chunk received for this messageId + if (!this.chunkBuffers[header.messageId]) { + this.chunkBuffers[header.messageId] = []; + } + + // Directly set the chunk at the correct index, adjusting for 1-based indexing + this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; + + console.log(`Received chunk: ${incomingMessage}`); + console.log(`Chunks received so far: ${this.chunkBuffers[header.messageId].filter(chunk => chunk !== undefined).length}`); + + // Check if all chunks have been received by confirming the length + if (this.chunkBuffers[header.messageId].filter(chunk => chunk !== undefined).length === header.totalChunks) { + const chunks = this.chunkBuffers[header.messageId]; + const fullMessage = chunks.join(''); + + await this.handleIncomingMessage(fullMessage); + + delete this.chunkBuffers[header.messageId]; + } + } + } + + abstract handleIncomingMessage(incomingMessage: string): Promise; + + abstract sendMessage(message: string): Promise; } diff --git a/User/src/network/socket_communicator/tcp_client_communicator.ts b/User/src/network/socket_communicator/tcp_client_communicator.ts index 41388c7..3b1a5a3 100644 --- a/User/src/network/socket_communicator/tcp_client_communicator.ts +++ b/User/src/network/socket_communicator/tcp_client_communicator.ts @@ -1,30 +1,28 @@ import { Socket } from 'net'; -import { createCipheriv, createDecipheriv, constants, publicDecrypt } from 'crypto'; -import { MessageHandler, ParsedMessage } from '../message_handler'; import { SocketCommunicatorBase } from './socket_communicator_base'; import { OperationHandler } from '../operations_base/operation_handler'; import { operationCodes } from '../operation_codes'; +import {MessageHandler} from "../message_handler"; export class TcpClientCommunicator extends SocketCommunicatorBase { private readonly socket: Socket; - private aesKey: Buffer | null; - private aesIv: Buffer | null; - private serverPublicKey: string | null; private isAesKeySetFlag: boolean; - private chunkBuffers: { [messageId: string]: string[] }; constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { super(ip, port, operationHandler); this.socket = socket; this.aesKey = null; this.aesIv = null; - this.serverPublicKey = null; this.isAesKeySetFlag = false; this.chunkBuffers = {}; } + isAesKeySet(): boolean { + return this.isAesKeySetFlag; + } + setServerPublicKey(publicKey: string): void { - this.serverPublicKey = publicKey; + this.publicKey = publicKey; } setAesKey(aesKey: string, aesIv: string): void { @@ -32,115 +30,11 @@ export class TcpClientCommunicator extends SocketCommunicatorBase { this.aesIv = Buffer.from(aesIv, 'base64'); } - private encryptWithAes(message: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not set.'); - } - const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let encrypted = cipher.update(message, 'utf-8'); - encrypted = Buffer.concat([encrypted, cipher.final()]); - return encrypted.toString('base64'); - } - - private decryptWithAes(encryptedMessage: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not set.'); - } - const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); - decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted.toString('utf-8'); - } - - private decryptWithRsa(message: string): string { - if (!this.serverPublicKey) { - throw new Error('Server public key not set.'); - } - try { - const encryptedMessage = Buffer.from(message, 'base64'); - const decrypted = publicDecrypt( - { - key: this.serverPublicKey, - padding: constants.RSA_PKCS1_PADDING, - }, - encryptedMessage - ); - return decrypted.toString('utf-8'); - } catch (error) { - throw new Error('Failed to decrypt RSA message.'); - } - } - - async sendChunkedMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { - if (!this.networkSpeed) { - this.networkSpeed = await this.scanNetworkLatency(); - } - - const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); - const outgoingMessage = this.encryptWithAes(message); - - // Calculate optimal chunk size based on network latency - const optimalChunkSize = await this.calculateOptimalChunkSize(outgoingMessage.length); - const totalChunks = Math.ceil(outgoingMessage.length / optimalChunkSize); - const messageId = Date.now().toString(); - - // Send each chunk with a delay between them - for (let i = 0; i < totalChunks; i++) { - const chunk = outgoingMessage.slice(i * optimalChunkSize, (i + 1) * optimalChunkSize); - const chunkHeader = JSON.stringify({ - messageId, - sequenceNumber: i + 1, - totalChunks, - }); - const chunkWithHeader = `${chunkHeader}|${chunk}`; - await this.writeToSocket(chunkWithHeader); - - // Set delay based on latency for smoother transmission - const delay = Math.max(100, Math.min(300, this.networkSpeed * 10)); - await new Promise((resolve) => setTimeout(resolve, delay)); - } - } - - private writeToSocket(message: string): Promise { - return new Promise((resolve, reject) => { - this.socket.write(message, (err: any) => { - if (err) { - return reject(err); - } - resolve(); - }); - }); - } - - async handleIncomingChunk(data: Buffer): Promise { - const incomingMessage = data.toString().trim(); - const [headerJson, chunkContent] = incomingMessage.split('|'); - const header = JSON.parse(headerJson); - - if (!this.chunkBuffers[header.messageId]) { - this.chunkBuffers[header.messageId] = new Array(header.totalChunks).fill(undefined); - } - - this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; - if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { - await this.processCompleteMessage(header.messageId); - } - } - - private async processCompleteMessage(messageId: string): Promise { - const chunks = this.chunkBuffers[messageId]; - if (chunks && chunks.every((chunk) => chunk !== undefined)) { - const fullMessage = chunks.join(''); - await this.handleIncomingMessage(fullMessage); - delete this.chunkBuffers[messageId]; - } - } - async handleIncomingMessage(incomingMessage: string): Promise { let messageToProcess; if (this.aesKey && this.aesIv) { messageToProcess = this.decryptWithAes(incomingMessage); - } else if (this.serverPublicKey) { + } else if (this.publicKey) { messageToProcess = this.decryptWithRsa(incomingMessage); } else { messageToProcess = Buffer.from(incomingMessage, 'base64').toString('utf-8'); @@ -162,13 +56,31 @@ export class TcpClientCommunicator extends SocketCommunicatorBase { this.handlerResult = result; } - isAesKeySet(): boolean { - return this.isAesKeySetFlag; + async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { + if (!this.networkSpeed) { + this.networkSpeed = await this.scanNetworkLatency(); + } + + const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); + + const outgoingMessage = this.encryptWithAes(message); + + // Calculate optimal chunk size based on network latency + const optimalChunkSize = await this.calculateOptimalChunkSize(outgoingMessage.length); + const totalChunks = Math.ceil(outgoingMessage.length / optimalChunkSize); + const messageId = Date.now().toString(); + + // Send each chunk with a delay between them + for (let i = 0; i < totalChunks; i++) { + const chunk = outgoingMessage.slice(i * optimalChunkSize, (i + 1) * optimalChunkSize); + const chunkHeader = JSON.stringify({ + messageId, + sequenceNumber: i + 1, + totalChunks, + }); + const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; + if(!this.socket.write(chunkWithHeader)) this.socket.end(); + } } - getHandlerResult(): ParsedMessage | null { - const message = this.handlerResult; - this.handlerResult = null; - return message; - } } diff --git a/User/src/network/socket_communicator/tcp_server_communicator.ts b/User/src/network/socket_communicator/tcp_server_communicator.ts index 0550dcc..b88a273 100644 --- a/User/src/network/socket_communicator/tcp_server_communicator.ts +++ b/User/src/network/socket_communicator/tcp_server_communicator.ts @@ -1,118 +1,48 @@ import { Socket } from 'net'; -import { privateEncrypt, generateKeyPairSync, createCipheriv, createDecipheriv, randomBytes, constants } from 'crypto'; import { MessageHandler } from '../message_handler'; import { SocketCommunicatorBase } from './socket_communicator_base'; import { OperationHandler } from '../operations_base/operation_handler'; export class TcpServerCommunicator extends SocketCommunicatorBase { private readonly socket: Socket; - private privateKey: string | null; - private publicKey: string | null; - private aesKey: Buffer | null; - private aesIv: Buffer | null; - private chunkBuffers: { [messageId: string]: string[] }; constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { super(ip, port, operationHandler); this.socket = socket; - this.privateKey = null; - this.publicKey = null; // Client public key will be set later - this.aesKey = null; - this.aesIv = null; - this.chunkBuffers = {}; - this.generateKeyPair(); - } - - generateKeyPair(): void { - const { privateKey, publicKey } = generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { type: 'spki', format: 'pem' }, - privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, - }); - this.privateKey = privateKey; - this.publicKey = publicKey; } async sendPublicKey(): Promise { - if (!this.publicKey) { - throw new Error('Public key is not available. Please generate RSA key pair.'); + this.generateKeyPair(); + if (!this.publicKey || !this.privateKey) { + throw new Error('RSA key pair is not available. Please generate RSA key pair.'); } - await this.sendChunkedMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey }); + + await this.sendMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey }); } async sendAesKey(): Promise { - this.aesKey = randomBytes(32); - this.aesIv = randomBytes(16); + this.generateAesKey(); + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not available. Please generate AES key.'); + } + const aesKeyBase64 = this.aesKey.toString('base64'); const aesIvBase64 = this.aesIv.toString('base64'); - await this.sendChunkedMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 }); + await this.sendMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 }); } - private encryptWithRsa(message: string): string { - if (!this.privateKey) throw new Error('Server private key not set.'); - return privateEncrypt( - { - key: this.privateKey, - padding: constants.RSA_PKCS1_PADDING, - }, - Buffer.from(message) - ).toString('base64'); + async handleIncomingMessage(incomingMessage: string): Promise { + const messageToProcess = this.decryptWithAes(incomingMessage); + this.handlerResult = await this.operationHandler.handleOperation(messageToProcess); } - private decryptWithAes(encryptedMessage: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key not set.'); - } - const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); - decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted.toString('utf-8'); - } - - private encryptWithAes(message: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not set.'); - } - const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let encrypted = cipher.update(message, 'utf-8'); - encrypted = Buffer.concat([encrypted, cipher.final()]); - return encrypted.toString('base64'); - } - - async handleIncomingChunk(data: Buffer): Promise { - const incomingMessage = data.toString().trim(); - const [headerJson, chunkContent] = incomingMessage.split('|'); - const header = JSON.parse(headerJson); - if (!this.chunkBuffers[header.messageId]) { - this.chunkBuffers[header.messageId] = new Array(header.totalChunks).fill(undefined); - } - this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; - if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { - await this.processCompleteMessage(header.messageId); - } - } - - private async processCompleteMessage(messageId: string): Promise { - const chunks = this.chunkBuffers[messageId]; - if (chunks && chunks.every((chunk) => chunk !== undefined)) { - const fullMessage = chunks.join(''); - await this.handleIncomingMessage(fullMessage); - delete this.chunkBuffers[messageId]; - } - } - - async sendChunkedMessage( - operationCode: string, - metaInfo?: { [key: string]: any }, - fileContent?: Buffer - ): Promise { - // Check if latency-based chunk size needs calculation + async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { if (!this.networkSpeed) { this.networkSpeed = await this.scanNetworkLatency(); } - // Format the message for sending const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); + let outgoingMessage: string; switch (operationCode) { case 'SET_PUBLIC_KEY': @@ -138,32 +68,8 @@ export class TcpServerCommunicator extends SocketCommunicatorBase { sequenceNumber: i + 1, totalChunks, }); - const chunkWithHeader = `${chunkHeader}|${chunk}`; - await this.writeToSocket(chunkWithHeader); - - // Set delay based on latency for smoother transmission - const delay = Math.max(100, Math.min(300, this.networkSpeed * 10)); - await new Promise((resolve) => setTimeout(resolve, delay)); + const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; + if(!this.socket.write(chunkWithHeader)) this.socket.end(); } } - - async handleIncomingMessage(incomingMessage: string): Promise { - let messageToProcess = incomingMessage; - if (this.aesKey && this.aesIv) { - messageToProcess = this.decryptWithAes(incomingMessage); - } - this.handlerResult = await this.operationHandler.handleOperation(messageToProcess); - } - - protected writeToSocket(message: string): Promise { - return new Promise((resolve, reject) => { - this.socket.write(message, (err: any) => { - if (err) { - console.error('Error sending message over TCP:', err); - return reject(err); - } - resolve(); - }); - }); - } } diff --git a/User/src/network/socket_communicator/udp_socket_communicator.ts b/User/src/network/socket_communicator/udp_socket_communicator.ts index 2e8de1a..f9f4e48 100644 --- a/User/src/network/socket_communicator/udp_socket_communicator.ts +++ b/User/src/network/socket_communicator/udp_socket_communicator.ts @@ -11,20 +11,9 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase { this.socket = socket; } - // Handle incoming message (no decryption needed for UDP) - async handleIncomingMessage(incomingMessage: string): Promise { - this.handlerResult = await this.operationHandler.handleOperation(incomingMessage); - } - // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); - - await this.sendUdpMessage(message); - } - - // Helper method to wrap socket.send in a Promise for async/await support - private sendUdpMessage(message: string): Promise { return new Promise((resolve, reject) => { this.socket.send(message, this.port, this.ip, (err: any) => { if (err) { @@ -36,4 +25,9 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase { }); }); } + + // Handle incoming message (no decryption needed for UDP) + async handleIncomingMessage(incomingMessage: string): Promise { + this.handlerResult = await this.operationHandler.handleOperation(incomingMessage); + } } diff --git a/User/src/network/tcp/tcp_client.ts b/User/src/network/tcp/tcp_client.ts index e4492b5..e87efae 100644 --- a/User/src/network/tcp/tcp_client.ts +++ b/User/src/network/tcp/tcp_client.ts @@ -80,7 +80,7 @@ export class TcpClient { } this.log(`Sending message with operationCode: ${operationCode}`); - await this.communicator.sendChunkedMessage(operationCode, metaInfo, fileContent); + await this.communicator.sendMessage(operationCode, metaInfo, fileContent); return true; } diff --git a/User/src/network/tcp/tcp_server.ts b/User/src/network/tcp/tcp_server.ts index 14256c0..6e3429f 100644 --- a/User/src/network/tcp/tcp_server.ts +++ b/User/src/network/tcp/tcp_server.ts @@ -43,7 +43,6 @@ export class TcpServer { const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler); this.connectionManager.addConnection(ip, port, tcpCommunicator); - tcpCommunicator.generateKeyPair(); tcpCommunicator.sendPublicKey() .then(() => tcpCommunicator.sendAesKey()) @@ -106,7 +105,7 @@ export class TcpServer { const handlerResult = communicator.getHandlerResult(); if (handlerResult) { try { - await communicator.sendChunkedMessage( + await communicator.sendMessage( handlerResult.operationCode, handlerResult.metaInfo, handlerResult.fileContent