chunks v1
This commit is contained in:
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
import { Socket } from 'net';
|
||||
import { privateEncrypt, privateDecrypt, generateKeyPairSync, createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
||||
import { MessageHandler, ParsedMessage } from '../message_handler';
|
||||
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';
|
||||
import {constants} from "node:crypto";
|
||||
|
||||
const END_OF_MESSAGE = '<EOM>'; // Define a unique marker for end of message
|
||||
const CHUNK_SIZE = 1024; // Define chunk size
|
||||
|
||||
export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
private readonly socket: Socket;
|
||||
@@ -14,6 +14,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
private aesKey: Buffer | null;
|
||||
private aesIv: Buffer | null;
|
||||
private messageBuffer: string;
|
||||
private chunkBuffers: { [messageId: string]: string[] }; // Buffer for reassembling chunks
|
||||
|
||||
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
|
||||
super(ip, port, operationHandler);
|
||||
@@ -23,6 +24,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
this.aesKey = null;
|
||||
this.aesIv = null;
|
||||
this.messageBuffer = ''; // Initialize the message buffer
|
||||
this.chunkBuffers = {}; // Buffer for reassembling incoming messages
|
||||
this.generateKeyPair(); // Generate RSA key pair for encryption
|
||||
}
|
||||
|
||||
@@ -35,7 +37,6 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
});
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
console.log('RSA key pair generated.');
|
||||
}
|
||||
|
||||
// Send the server's public key to the client
|
||||
@@ -44,9 +45,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
throw new Error('Public key is not available. Please generate RSA key pair.');
|
||||
}
|
||||
|
||||
const message = MessageHandler.formatMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey });
|
||||
await this.writeToSocket(message + END_OF_MESSAGE);
|
||||
console.log('Public key sent to client.');
|
||||
await this.sendChunkedMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey });
|
||||
}
|
||||
|
||||
// Generate AES key and IV, then send them to the client
|
||||
@@ -57,16 +56,11 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
const aesKeyBase64 = this.aesKey.toString('base64');
|
||||
const aesIvBase64 = this.aesIv.toString('base64');
|
||||
|
||||
const formattedMessage = MessageHandler.formatMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 });
|
||||
const encryptedMessage = this.encryptWithRsa(Buffer.from(formattedMessage)).toString('base64');
|
||||
|
||||
await this.writeToSocket(encryptedMessage + END_OF_MESSAGE);
|
||||
console.log('AES key and IV sent to client.');
|
||||
await this.sendChunkedMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 });
|
||||
}
|
||||
|
||||
// Encrypt a message with the server's private key (RSA encryption)
|
||||
private encryptWithRsa(message: Buffer): Buffer {
|
||||
const bufferMessage = Buffer.isBuffer(message) ? message : Buffer.from(message);
|
||||
private encryptWithRsa(message: string): string {
|
||||
if (!this.privateKey) throw new Error('Server private key not set.');
|
||||
|
||||
return privateEncrypt(
|
||||
@@ -74,8 +68,8 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
key: this.privateKey,
|
||||
padding: constants.RSA_PKCS1_PADDING, // PKCS1 padding
|
||||
},
|
||||
bufferMessage
|
||||
);
|
||||
Buffer.from(message)
|
||||
).toString('base64');
|
||||
}
|
||||
|
||||
// Decrypt AES-encrypted messages
|
||||
@@ -106,17 +100,34 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
const incomingMessage = data.toString();
|
||||
this.messageBuffer += incomingMessage;
|
||||
|
||||
// Check if the message ends with END_OF_MESSAGE
|
||||
if (this.messageBuffer.endsWith(END_OF_MESSAGE)) {
|
||||
// Remove the END_OF_MESSAGE marker and process the message
|
||||
const completeMessage = this.messageBuffer.slice(0, -END_OF_MESSAGE.length);
|
||||
if (this.messageBuffer.includes(END_OF_MESSAGE)) {
|
||||
const messages = this.messageBuffer.split(END_OF_MESSAGE);
|
||||
|
||||
console.log(`\n\nComplete Message:\n${completeMessage}\n\n`);
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
this.handleIncomingMessage(completeMessage);
|
||||
this.messageBuffer = messages[messages.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the message buffer after processing
|
||||
this.messageBuffer = '';
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
if (!this.chunkBuffers[header.messageId]) {
|
||||
this.chunkBuffers[header.messageId] = new Array(header.totalChunks);
|
||||
}
|
||||
|
||||
this.chunkBuffers[header.messageId][header.sequenceNumber] = chunkContent;
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,14 +136,29 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
|
||||
let outgoingMessage: string;
|
||||
|
||||
if (this.aesKey && this.aesIv) {
|
||||
outgoingMessage = this.encryptWithAes(message);
|
||||
} else {
|
||||
outgoingMessage = message;
|
||||
}
|
||||
if (this.aesKey && this.aesIv) outgoingMessage = this.encryptWithAes(message);
|
||||
else if(this.privateKey) outgoingMessage = this.encryptWithRsa(message);
|
||||
else outgoingMessage = message;
|
||||
|
||||
outgoingMessage += END_OF_MESSAGE; // Append end-of-message marker
|
||||
await this.writeToSocket(outgoingMessage);
|
||||
const totalChunks = Math.ceil(outgoingMessage.length / CHUNK_SIZE);
|
||||
const messageId = Date.now().toString();
|
||||
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const chunk = outgoingMessage.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
|
||||
const chunkHeader = JSON.stringify({
|
||||
messageId,
|
||||
sequenceNumber: i,
|
||||
totalChunks,
|
||||
});
|
||||
|
||||
const chunkWithHeader = `${chunkHeader}|${chunk}`;
|
||||
|
||||
await this.writeToSocket(chunkWithHeader);
|
||||
|
||||
if (i === totalChunks - 1) {
|
||||
await this.writeToSocket(END_OF_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle incoming message (decrypt with AES if available)
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import { FileEncryptor } from '../../helpers/file_encryptor';
|
||||
|
||||
const LOCK_FILE_EXTENSION = '.lock';
|
||||
|
||||
@@ -101,6 +101,40 @@ export class UserToUserOperations extends OperationBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static hasEnoughDiskSpace(directory: string, requiredPercentage: number): boolean {
|
||||
try {
|
||||
let availableSpace = 0;
|
||||
let totalSpace = 0;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows
|
||||
const output = execSync(`wmic logicaldisk where "DeviceID='${directory[0]}:'" get FreeSpace,Size`).toString();
|
||||
const lines = output.trim().split('\n');
|
||||
const [freeSpaceStr, totalSpaceStr] = lines[1].trim().split(/\s+/);
|
||||
availableSpace = parseInt(freeSpaceStr, 10); // Available space in bytes
|
||||
totalSpace = parseInt(totalSpaceStr, 10); // Total space in bytes
|
||||
} else {
|
||||
// Unix-based (Linux/macOS)
|
||||
const output = execSync(`df -k "${directory}"`).toString();
|
||||
const lines = output.trim().split('\n');
|
||||
const parts = lines[lines.length - 1].split(/\s+/);
|
||||
const availableSpaceInKb = parseInt(parts[3], 10); // Available space in KB
|
||||
const totalSpaceInKb = parseInt(parts[1], 10); // Total space in KB
|
||||
availableSpace = availableSpaceInKb * 1024;
|
||||
totalSpace = totalSpaceInKb * 1024;
|
||||
}
|
||||
|
||||
// Calculate available space as a percentage of the total space
|
||||
const availablePercentage = (availableSpace / totalSpace) * 100;
|
||||
|
||||
// Return true if the available percentage is greater than or equal to the required percentage
|
||||
return availablePercentage >= requiredPercentage;
|
||||
} catch (error) {
|
||||
console.error(`Error checking disk space: ${error}`);
|
||||
return false; // Return false if there's an error
|
||||
}
|
||||
}
|
||||
|
||||
public static handleResetDatabase(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Path to the application.json file
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
@@ -217,6 +251,15 @@ export class UserToUserOperations extends OperationBase {
|
||||
const fullFilePath = path.join(baseBackupDir, userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Check if there is enough disk space
|
||||
const requiredPercentage = 25;
|
||||
if (!UserToUserOperations.hasEnoughDiskSpace(baseBackupDir, requiredPercentage)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Insufficient disk space for backup.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
@@ -370,7 +413,7 @@ export class UserToUserOperations extends OperationBase {
|
||||
|
||||
// Get the share directory path
|
||||
const baseDepartmentDir = appInfo.departmentDirectory.path;
|
||||
const userDepartmentDir = path.join(baseDepartmentDir, userName);
|
||||
const userDepartmentDir = path.join(baseDepartmentDir, '..', 'DEPARTMENT_FILES', userName);
|
||||
|
||||
try {
|
||||
// Check if the user's backup directory exists
|
||||
@@ -433,7 +476,7 @@ export class UserToUserOperations extends OperationBase {
|
||||
const departmentDirectory = appInfo.departmentDirectory.path;
|
||||
|
||||
// Full path where the file will be stored (under the user's directory in the shared folder)
|
||||
const fullFilePath = path.join(departmentDirectory, userName, relativeFilePath);
|
||||
const fullFilePath = path.join(departmentDirectory, '..', 'DEPARTMENT_FILES', userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Socket } from 'net';
|
||||
import { createCipheriv, createDecipheriv } from 'crypto';
|
||||
import { MessageHandler, ParsedMessage } from '../message_handler';
|
||||
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 {constants, publicDecrypt} from "node:crypto";
|
||||
import {operationCodes} from "../operation_codes";
|
||||
import { operationCodes } from '../operation_codes';
|
||||
|
||||
const END_OF_MESSAGE = '<EOM>'; // Define a unique marker for end of message
|
||||
const END_OF_MESSAGE = '<EOM>'; // Unique marker for the end of message
|
||||
const CHUNK_SIZE = 1024; // Define chunk size
|
||||
|
||||
export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
private readonly socket: Socket;
|
||||
@@ -15,30 +15,28 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
private messageBuffer: string;
|
||||
private serverPublicKey: string | null;
|
||||
private isAesKeySetFlag: boolean;
|
||||
private chunkBuffers: { [messageId: string]: string[] }; // Buffer for reassembling chunks
|
||||
|
||||
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
|
||||
super(ip, port, operationHandler);
|
||||
super(ip, port, operationHandler); // Call parent constructor
|
||||
this.socket = socket;
|
||||
this.aesKey = null;
|
||||
this.aesIv = null;
|
||||
this.serverPublicKey = null;
|
||||
this.messageBuffer = ''; // Buffer for message reassembly
|
||||
this.isAesKeySetFlag = false;
|
||||
this.chunkBuffers = {}; // Initialize chunk buffer for reassembling messages
|
||||
}
|
||||
|
||||
setServerPublicKey(publicKey: string): void {
|
||||
this.serverPublicKey = publicKey;
|
||||
console.log('Server public key set.');
|
||||
}
|
||||
|
||||
// Set the AES key when received
|
||||
setAesKey(aesKey: string, aesIv: string): void {
|
||||
this.aesKey = Buffer.from(aesKey, 'base64');
|
||||
this.aesIv = Buffer.from(aesIv, 'base64');
|
||||
console.log('AES key set.');
|
||||
}
|
||||
|
||||
// Encrypt a message with AES
|
||||
private encryptWithAes(message: string): string {
|
||||
if (!this.aesKey || !this.aesIv) {
|
||||
throw new Error('AES key or IV is not set.');
|
||||
@@ -65,62 +63,54 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
try {
|
||||
const encryptedMessage = Buffer.from(message.toString(), 'base64');
|
||||
// Decrypt the message using the server's public key
|
||||
const decrypted = publicDecrypt(
|
||||
{
|
||||
key: this.serverPublicKey,
|
||||
padding: constants.RSA_PKCS1_PADDING, // Matching padding for decryption
|
||||
padding: constants.RSA_PKCS1_PADDING,
|
||||
},
|
||||
encryptedMessage
|
||||
);
|
||||
return decrypted.toString('utf-8');
|
||||
} catch (error) {
|
||||
console.error('RSA decryption failed:', error);
|
||||
throw new Error('Failed to decrypt RSA message.');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle incoming chunks of data
|
||||
async handleIncomingChunk(data: Buffer): Promise<void> {
|
||||
const incomingMessage = data.toString();
|
||||
this.messageBuffer += incomingMessage;
|
||||
|
||||
// Check if the message ends with END_OF_MESSAGE
|
||||
if (this.messageBuffer.endsWith(END_OF_MESSAGE)) {
|
||||
// Remove the END_OF_MESSAGE marker and process the message
|
||||
const completeMessage = this.messageBuffer.slice(0, -END_OF_MESSAGE.length);
|
||||
|
||||
this.handleIncomingMessage(completeMessage);
|
||||
|
||||
// Clear the message buffer after processing
|
||||
this.messageBuffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Send a chunked message over the socket
|
||||
async sendChunkedMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> {
|
||||
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
|
||||
let outgoingMessage: string;
|
||||
|
||||
// Encrypt the message with AES if available
|
||||
if (this.aesKey && this.aesIv) {
|
||||
outgoingMessage = this.encryptWithAes(message);
|
||||
} else {
|
||||
outgoingMessage = message // Send plain text if AES is not set
|
||||
outgoingMessage = message;
|
||||
}
|
||||
|
||||
// Append the end marker to the message
|
||||
outgoingMessage += END_OF_MESSAGE;
|
||||
const totalChunks = Math.ceil(outgoingMessage.length / CHUNK_SIZE);
|
||||
const messageId = Date.now().toString();
|
||||
|
||||
await this.writeToSocket(outgoingMessage);
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const chunk = outgoingMessage.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
|
||||
const chunkHeader = JSON.stringify({
|
||||
messageId,
|
||||
sequenceNumber: i,
|
||||
totalChunks,
|
||||
});
|
||||
|
||||
const chunkWithHeader = `${chunkHeader}|${chunk}`;
|
||||
|
||||
await this.writeToSocket(chunkWithHeader);
|
||||
|
||||
if (i === totalChunks - 1) {
|
||||
await this.writeToSocket(END_OF_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write message to socket
|
||||
private writeToSocket(message: string): Promise<void> {
|
||||
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();
|
||||
@@ -128,37 +118,69 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
});
|
||||
}
|
||||
|
||||
// Handle incoming message (decrypted if AES is set)
|
||||
async handleIncomingChunk(data: Buffer): Promise<void> {
|
||||
const incomingMessage = data.toString();
|
||||
this.messageBuffer += incomingMessage;
|
||||
|
||||
if (this.messageBuffer.includes(END_OF_MESSAGE)) {
|
||||
const messages = this.messageBuffer.split(END_OF_MESSAGE);
|
||||
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
this.messageBuffer = messages[messages.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
if (!this.chunkBuffers[header.messageId]) {
|
||||
this.chunkBuffers[header.messageId] = new Array(header.totalChunks);
|
||||
}
|
||||
|
||||
this.chunkBuffers[header.messageId][header.sequenceNumber] = chunkContent;
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
let messageToProcess = incomingMessage;
|
||||
if (this.aesKey && this.aesIv) {
|
||||
messageToProcess = this.decryptWithAes(incomingMessage);
|
||||
}else if(this.serverPublicKey){
|
||||
} else if (this.serverPublicKey) {
|
||||
messageToProcess = this.decryptWithRsa(incomingMessage);
|
||||
}
|
||||
|
||||
const result = this.operationHandler.handleOperation(messageToProcess);
|
||||
|
||||
if(result.operationCode === operationCodes.SET_AES_KEY){
|
||||
if (result.operationCode === operationCodes.SET_AES_KEY) {
|
||||
this.isAesKeySetFlag = true;
|
||||
this.setAesKey(result.metaInfo?.aesKey, result.metaInfo?.aesIv);
|
||||
return;
|
||||
}
|
||||
|
||||
if(result.operationCode === operationCodes.SET_PUBLIC_KEY) {
|
||||
if (result.operationCode === operationCodes.SET_PUBLIC_KEY) {
|
||||
this.setServerPublicKey(result.metaInfo?.publicKey);
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlerResult = result
|
||||
this.handlerResult = result;
|
||||
}
|
||||
|
||||
// Check if AES key is set
|
||||
isAesKeySet(): boolean {
|
||||
return this.isAesKeySetFlag;
|
||||
}
|
||||
|
||||
// Get handler result for operation handling
|
||||
getHandlerResult(): ParsedMessage | null {
|
||||
return this.handlerResult;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Socket } from 'net';
|
||||
import { privateEncrypt, privateDecrypt, generateKeyPairSync, createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
||||
import { MessageHandler, ParsedMessage } from '../message_handler';
|
||||
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';
|
||||
import {constants} from "node:crypto";
|
||||
|
||||
const END_OF_MESSAGE = '<EOM>'; // Define a unique marker for end of message
|
||||
const CHUNK_SIZE = 1024; // Define chunk size
|
||||
|
||||
export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
private readonly socket: Socket;
|
||||
@@ -14,6 +14,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
private aesKey: Buffer | null;
|
||||
private aesIv: Buffer | null;
|
||||
private messageBuffer: string;
|
||||
private chunkBuffers: { [messageId: string]: string[] }; // Buffer for reassembling chunks
|
||||
|
||||
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
|
||||
super(ip, port, operationHandler);
|
||||
@@ -23,6 +24,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
this.aesKey = null;
|
||||
this.aesIv = null;
|
||||
this.messageBuffer = ''; // Initialize the message buffer
|
||||
this.chunkBuffers = {}; // Buffer for reassembling incoming messages
|
||||
this.generateKeyPair(); // Generate RSA key pair for encryption
|
||||
}
|
||||
|
||||
@@ -35,7 +37,6 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
});
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
console.log('RSA key pair generated.');
|
||||
}
|
||||
|
||||
// Send the server's public key to the client
|
||||
@@ -44,9 +45,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
throw new Error('Public key is not available. Please generate RSA key pair.');
|
||||
}
|
||||
|
||||
const message = MessageHandler.formatMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey });
|
||||
await this.writeToSocket(message + END_OF_MESSAGE);
|
||||
console.log('Public key sent to client.');
|
||||
await this.sendChunkedMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey });
|
||||
}
|
||||
|
||||
// Generate AES key and IV, then send them to the client
|
||||
@@ -57,16 +56,11 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
const aesKeyBase64 = this.aesKey.toString('base64');
|
||||
const aesIvBase64 = this.aesIv.toString('base64');
|
||||
|
||||
const formattedMessage = MessageHandler.formatMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 });
|
||||
const encryptedMessage = this.encryptWithRsa(Buffer.from(formattedMessage)).toString('base64');
|
||||
|
||||
await this.writeToSocket(encryptedMessage + END_OF_MESSAGE);
|
||||
console.log('AES key and IV sent to client.');
|
||||
await this.sendChunkedMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 });
|
||||
}
|
||||
|
||||
// Encrypt a message with the server's private key (RSA encryption)
|
||||
private encryptWithRsa(message: Buffer): Buffer {
|
||||
const bufferMessage = Buffer.isBuffer(message) ? message : Buffer.from(message);
|
||||
private encryptWithRsa(message: string): string {
|
||||
if (!this.privateKey) throw new Error('Server private key not set.');
|
||||
|
||||
return privateEncrypt(
|
||||
@@ -74,8 +68,8 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
key: this.privateKey,
|
||||
padding: constants.RSA_PKCS1_PADDING, // PKCS1 padding
|
||||
},
|
||||
bufferMessage
|
||||
);
|
||||
Buffer.from(message)
|
||||
).toString('base64');
|
||||
}
|
||||
|
||||
// Decrypt AES-encrypted messages
|
||||
@@ -106,17 +100,34 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
const incomingMessage = data.toString();
|
||||
this.messageBuffer += incomingMessage;
|
||||
|
||||
// Check if the message ends with END_OF_MESSAGE
|
||||
if (this.messageBuffer.endsWith(END_OF_MESSAGE)) {
|
||||
// Remove the END_OF_MESSAGE marker and process the message
|
||||
const completeMessage = this.messageBuffer.slice(0, -END_OF_MESSAGE.length);
|
||||
if (this.messageBuffer.includes(END_OF_MESSAGE)) {
|
||||
const messages = this.messageBuffer.split(END_OF_MESSAGE);
|
||||
|
||||
console.log(`\n\nComplete Message:\n${completeMessage}\n\n`);
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
this.handleIncomingMessage(completeMessage);
|
||||
this.messageBuffer = messages[messages.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the message buffer after processing
|
||||
this.messageBuffer = '';
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
if (!this.chunkBuffers[header.messageId]) {
|
||||
this.chunkBuffers[header.messageId] = new Array(header.totalChunks);
|
||||
}
|
||||
|
||||
this.chunkBuffers[header.messageId][header.sequenceNumber] = chunkContent;
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,14 +136,29 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
|
||||
let outgoingMessage: string;
|
||||
|
||||
if (this.aesKey && this.aesIv) {
|
||||
outgoingMessage = this.encryptWithAes(message);
|
||||
} else {
|
||||
outgoingMessage = message;
|
||||
}
|
||||
if (this.aesKey && this.aesIv) outgoingMessage = this.encryptWithAes(message);
|
||||
else if(this.privateKey) outgoingMessage = this.encryptWithRsa(message);
|
||||
else outgoingMessage = message;
|
||||
|
||||
outgoingMessage += END_OF_MESSAGE; // Append end-of-message marker
|
||||
await this.writeToSocket(outgoingMessage);
|
||||
const totalChunks = Math.ceil(outgoingMessage.length / CHUNK_SIZE);
|
||||
const messageId = Date.now().toString();
|
||||
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const chunk = outgoingMessage.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
|
||||
const chunkHeader = JSON.stringify({
|
||||
messageId,
|
||||
sequenceNumber: i,
|
||||
totalChunks,
|
||||
});
|
||||
|
||||
const chunkWithHeader = `${chunkHeader}|${chunk}`;
|
||||
|
||||
await this.writeToSocket(chunkWithHeader);
|
||||
|
||||
if (i === totalChunks - 1) {
|
||||
await this.writeToSocket(END_OF_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle incoming message (decrypt with AES if available)
|
||||
|
||||
@@ -29,12 +29,12 @@ export class TcpClient {
|
||||
this.socket = new net.Socket();
|
||||
|
||||
this.socket.connect(this.tcp_port, ip, () => {
|
||||
console.log(`Client connected to server at ${ip}:${this.tcp_port}`);
|
||||
//console.log(`Client connected to server at ${ip}:${this.tcp_port}`);
|
||||
this.communicator = new TcpClientCommunicator(this.socket as Socket, ip, this.tcp_port, this.operationHandler);
|
||||
});
|
||||
|
||||
this.socket.on('error', (err) => {
|
||||
console.error(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`);
|
||||
//console.error(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`);
|
||||
});
|
||||
|
||||
this.socket.on('data', async (data: Buffer) => {
|
||||
@@ -45,7 +45,7 @@ export class TcpClient {
|
||||
});
|
||||
|
||||
this.socket.on('close', () => {
|
||||
console.log(`Connection closed: ${ip}:${this.tcp_port}`);
|
||||
//console.log(`Connection closed: ${ip}:${this.tcp_port}`);
|
||||
this.lastResult = null; // Clear the last result on socket close
|
||||
});
|
||||
}
|
||||
@@ -57,14 +57,14 @@ export class TcpClient {
|
||||
this.socket = null;
|
||||
this.communicator = null;
|
||||
this.lastResult = null; // Clear the last result on close
|
||||
console.log('Client socket connection closed.');
|
||||
//console.log('Client socket connection closed.');
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message with operationCode, metaInfo, and fileContent in chunks
|
||||
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<boolean> {
|
||||
if (!this.communicator || !this.isAesKeySet()) {
|
||||
console.error('Communicator not initialized or AES key not set.');
|
||||
//console.error('Communicator not initialized or AES key not set.');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export class TcpServer {
|
||||
const port = socket.remotePort || 0;
|
||||
|
||||
const clientId = `${ip}:${port}`; // Use IP and port to identify the client
|
||||
console.log(`Client connected: ${clientId}`);
|
||||
//console.log(`Client connected: ${clientId}`);
|
||||
|
||||
const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler);
|
||||
this.connectionManager.addConnection(ip, port, tcpCommunicator);
|
||||
@@ -44,9 +44,8 @@ export class TcpServer {
|
||||
tcpCommunicator.generateKeyPair();
|
||||
tcpCommunicator.sendPublicKey()
|
||||
.then(() => tcpCommunicator.sendAesKey())
|
||||
.then(() => console.log('Public key and AES key sent successfully.'))
|
||||
.catch(err => {
|
||||
console.error(`Error during key exchange with client ${clientId}:`, err);
|
||||
//console.error(`Error during key exchange with client ${clientId}:`, err);
|
||||
socket.end(); // Close the connection in case of any error
|
||||
});
|
||||
|
||||
@@ -57,13 +56,13 @@ export class TcpServer {
|
||||
|
||||
// Handle client disconnect
|
||||
socket.on('end', () => {
|
||||
console.log(`Client disconnected: ${clientId}`);
|
||||
//console.log(`Client disconnected: ${clientId}`);
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
});
|
||||
|
||||
// Handle socket errors
|
||||
socket.on('error', (err: Error) => {
|
||||
console.error(`Error from client ${clientId}: ${err.message}`);
|
||||
//console.error(`Error from client ${clientId}: ${err.message}`);
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
});
|
||||
});
|
||||
@@ -86,7 +85,7 @@ export class TcpServer {
|
||||
// Retrieve the communicator associated with this connection
|
||||
const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator;
|
||||
if (!communicator) {
|
||||
console.error(`No communicator found for ${clientId}`);
|
||||
//console.error(`No communicator found for ${clientId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,9 +101,9 @@ export class TcpServer {
|
||||
handlerResult.metaInfo,
|
||||
handlerResult.fileContent
|
||||
);
|
||||
console.log(`Response sent to ${clientId}`);
|
||||
//console.log(`Response sent to ${clientId}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to send response to ${clientId}:`, err);
|
||||
//console.error(`Failed to send response to ${clientId}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class UdpServer {
|
||||
const ip = rinfo.address;
|
||||
const port = rinfo.port;
|
||||
|
||||
console.log(`Received message from ${ip}:${port}`);
|
||||
//console.log(`Received message from ${ip}:${port}`);
|
||||
|
||||
// Create a temporary communicator for the incoming message
|
||||
const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler);
|
||||
@@ -46,8 +46,6 @@ export class UdpServer {
|
||||
if (communicatorResult) {
|
||||
// Send response back to the client using the temporary communicator
|
||||
await communicator.sendMessage(communicatorResult.operationCode, communicatorResult.metaInfo);
|
||||
} else {
|
||||
console.error(`No handler result for ${ip}:${port}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user