UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

1,506 lines (1,501 loc) 53.3 kB
'use strict'; var index = require('../types/index.js'); var metadataManager = require('../metadata/metadata-manager.js'); var validation = require('../utils/validation.js'); var idGenerator = require('../utils/id-generator.js'); var secureMemory = require('../../secure-memory.js'); var index$1 = require('../../../utils/memory/index.js'); var eventManager = require('../events/event-manager.js'); var ArrayCryptoHandler = require('../crypto/ArrayCryptoHandler.js'); var ArraySerializationHandler = require('../serialization/ArraySerializationHandler.js'); require('../../../index.js'); var crypto = require('../../../core/crypto.js'); var types = require('../../../utils/memory/types.js'); /*************************************************************************** * FortifyJS - Enhanced Secure Array Core Implementation * * security features and array methods * * @author Nehonix & Community * * @license MIT ***************************************************************************** */ /** * A secure array that can store sensitive data with enhanced security features * T represents the type of elements stored in the array */ class SecureArray { /** * Creates a new secure array */ constructor(initialData, options) { // Core data storage this.elements = []; this.secureBuffers = new Map(); // State management this._isDestroyed = false; this._isReadOnly = false; this._isFrozen = false; this._version = 1; this._lastModified = Date.now(); this._createdAt = Date.now(); // Memory management this._memoryTracking = false; // Snapshot functionality this.snapshots = new Map(); this._id = idGenerator.ArrayIdGenerator.generate(); this.options = { ...index.DEFAULT_SECURE_ARRAY_OPTIONS, ...options }; this._isReadOnly = this.options.readOnly; this._maxSize = this.options.maxSize; // Initialize modular components this.metadataManager = new metadataManager.ArrayMetadataManager(); this.eventManager = new eventManager.ArrayEventManager(); this.cryptoHandler = new ArrayCryptoHandler.ArrayCryptoHandler(this._id); this.serializationHandler = new ArraySerializationHandler.ArraySerializationHandler(this.cryptoHandler, this.metadataManager); // Set encryption key if provided if (this.options.encryptionKey) { this.cryptoHandler.setEncryptionKey(this.options.encryptionKey); } // Enable memory tracking this._memoryTracking = this.options.enableMemoryTracking; // Register with advanced memory manager if tracking is enabled if (this._memoryTracking) { index$1.memoryManager.registerObject(this, this._id); // Listen to memory events for proactive management index$1.memoryManager.on(types.MemoryEventType.MEMORY_PRESSURE, (event) => { if (event.data?.pressure > 0.8) { this.handleMemoryPressure(); } }); index$1.memoryManager.on(types.MemoryEventType.LEAK_DETECTED, (event) => { if (event.data?.leaks?.includes(this._id)) { console.warn(`Potential memory leak detected in SecureArray ${this._id}`); } }); } // Initialize secure buffer pool this.initializeSecureBufferPool(); // Set initial data if (initialData) { this.pushAll(initialData); } this.eventManager.emit("created", -1, undefined, { id: this._id }); } // ===== MEMORY MANAGEMENT ===== /** * Initialize secure buffer pool for efficient memory reuse */ initializeSecureBufferPool() { if (!this.secureBufferPool) { try { this.secureBufferPool = index$1.memoryManager.getPool("secure-array-buffer-pool") || index$1.memoryManager.createPool({ name: "secure-array-buffer-pool", factory: () => new Uint8Array(1024), // 1KB buffers reset: (buffer) => { // Secure wipe before reuse this.secureWipe(buffer); }, capacity: 50, strategy: "LRU", validator: (buffer) => buffer instanceof Uint8Array, }); } catch (error) { // Pool might already exist, try to get it this.secureBufferPool = index$1.memoryManager.getPool("secure-array-buffer-pool"); } } } /** * Handle memory pressure by cleaning up unused resources */ handleMemoryPressure() { // Clean up unused secure buffers for (const [index, buffer] of this.secureBuffers.entries()) { if (index >= this.elements.length) { buffer.destroy(); this.secureBuffers.delete(index); } } // Clear old snapshots (keep only the latest 3) const sortedSnapshots = Array.from(this.snapshots.entries()).sort(([, a], [, b]) => b.timestamp - a.timestamp); if (sortedSnapshots.length > 3) { for (let i = 3; i < sortedSnapshots.length; i++) { this.snapshots.delete(sortedSnapshots[i][0]); } } // Trigger garbage collection if available if (global.gc) { global.gc(); } this.eventManager.emit("gc", -1, undefined, { operation: "memory_pressure_cleanup", buffersCleared: this.secureBuffers.size, }); } /** * Secure wipe of buffer contents */ secureWipe(buffer) { return crypto.FortifyJS.secureWipe(buffer); } // ===== UTILITY METHODS ===== /** * Ensures the array is not destroyed */ ensureNotDestroyed() { if (this._isDestroyed) { console.error("Array has been destroyed and cannot be used."); throw new Error("Cannot use destroyed fortified Array. "); } } /** * Ensures the array is not read-only */ ensureNotReadOnly() { if (this._isReadOnly) { throw new Error("SecureArray is read-only"); } } /** * Ensures the array is not frozen */ ensureNotFrozen() { if (this._isFrozen) { throw new Error("SecureArray is frozen"); } } /** * Check if adding elements would exceed max size */ checkSizeLimit(additionalElements = 1) { if (this._maxSize && this.elements.length + additionalElements > this._maxSize) { throw new Error(`Operation would exceed maximum size limit of ${this._maxSize}`); } } /** * Updates the last modified timestamp and version */ updateLastModified() { this._lastModified = Date.now(); this._version++; } /** * Validates an index */ validateIndex(index) { if (this.options.enableIndexValidation) { if (!Number.isInteger(index) || index < 0) { throw new Error(`Invalid index: ${index}`); } } } /** * Validates a value */ validateValue(value) { if (this.options.enableTypeValidation) { validation.ArrayValidationUtils.validateSecureArrayValue(value); } } // ===== CORE ARRAY OPERATIONS ===== /** * Gets the length of the array */ get length() { this.ensureNotDestroyed(); return this.elements.length; } /** * Gets the unique identifier of this array */ get id() { return this._id; } /** * Gets the version number (increments on each mutation) */ get version() { return this._version; } /** * Gets when the array was last modified */ get lastModified() { return this._lastModified; } /** * Gets when the array was created */ get createdAt() { return this._createdAt; } /** * Check if the array is empty */ get isEmpty() { return this.elements.length === 0; } /** * Check if the array is read-only */ get isReadOnly() { return this._isReadOnly; } /** * Check if the array is frozen */ get isFrozen() { return this._isFrozen; } /** * Check if the array is destroyed */ get isDestroyed() { return this._isDestroyed; } /** * Gets an element at the specified index with automatic decryption */ get(index) { this.ensureNotDestroyed(); this.validateIndex(index); if (index >= this.elements.length) { return undefined; } const value = this.elements[index]; // Update access metadata if (this.metadataManager.has(index)) { const metadata = this.metadataManager.get(index); this.metadataManager.update(index, metadata.type, metadata.isSecure, true); } // Check if value is encrypted (starts with [ENCRYPTED:) if (typeof value === "string" && this.cryptoHandler.isEncrypted(value)) { try { // Decrypt the value automatically const decryptedValue = this.cryptoHandler.decryptValue(value); this.eventManager.emit("get", index, decryptedValue); return decryptedValue; } catch (error) { console.error(`Failed to decrypt value at index ${index}:`, error); // Return the encrypted value if decryption fails this.eventManager.emit("get", index, value); return value; } } // Convert SecureBuffer back to original type based on metadata if (value instanceof secureMemory.SecureBuffer) { const metadata = this.metadataManager.get(index); if (metadata?.type === "string") { // Convert SecureBuffer back to string const buffer = value.getBuffer(); const result = new TextDecoder().decode(buffer); this.eventManager.emit("get", index, result); return result; } } this.eventManager.emit("get", index, value); return value; } /** * Gets element at index with bounds checking */ at(index) { this.ensureNotDestroyed(); // Handle negative indices if (index < 0) { index = this.elements.length + index; } return this.get(index); } /** * Sets an element at the specified index */ set(index, value) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); this.validateIndex(index); this.validateValue(value); // Check size limit for new indices if (index >= this.elements.length) { this.checkSizeLimit(index - this.elements.length + 1); } // Extend array if necessary while (index >= this.elements.length) { this.elements.push(undefined); } // Clean up any existing secure buffer for this index this.cleanupIndex(index); // Handle secure storage for strings if (typeof value === "string") { const secureBuffer = secureMemory.SecureBuffer.from(value); this.secureBuffers.set(index, secureBuffer); this.elements[index] = secureBuffer; this.metadataManager.update(index, "string", true); } else { this.elements[index] = value; this.metadataManager.update(index, typeof value, false); } this.updateLastModified(); this.eventManager.emit("set", index, value); return this; } /** * Cleans up resources associated with an index */ cleanupIndex(index) { if (this.secureBuffers.has(index)) { this.secureBuffers.get(index)?.destroy(); this.secureBuffers.delete(index); } } /** * Adds an element to the end of the array */ push(value) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); this.validateValue(value); this.checkSizeLimit(1); const index = this.elements.length; // Handle secure storage for strings if (typeof value === "string") { const secureBuffer = secureMemory.SecureBuffer.from(value); this.secureBuffers.set(index, secureBuffer); this.elements.push(secureBuffer); this.metadataManager.update(index, "string", true); } else { this.elements.push(value); this.metadataManager.update(index, typeof value, false); } this.updateLastModified(); this.eventManager.emit("push", index, value); return this.elements.length; } /** * Removes and returns the last element */ pop() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); if (this.elements.length === 0) { return undefined; } const index = this.elements.length - 1; const value = this.get(index); // Clean up resources this.cleanupIndex(index); this.elements.pop(); this.metadataManager.delete(index); this.updateLastModified(); this.eventManager.emit("pop", index, value); return value; } /** * Removes and returns the first element */ shift() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); if (this.elements.length === 0) { return undefined; } const value = this.get(0); // Clean up resources for index 0 this.cleanupIndex(0); this.elements.shift(); // Shift all metadata indices down by 1 this.metadataManager.shiftIndices(0, -1); // Shift all secure buffer indices down by 1 const newSecureBuffers = new Map(); for (const [index, buffer] of this.secureBuffers.entries()) { if (index > 0) { newSecureBuffers.set(index - 1, buffer); } } this.secureBuffers = newSecureBuffers; this.updateLastModified(); this.eventManager.emit("shift", 0, value); return value; } /** * Adds elements to the beginning of the array */ unshift(...values) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); this.checkSizeLimit(values.length); for (const value of values) { this.validateValue(value); } // Shift existing secure buffers and metadata const shiftAmount = values.length; this.metadataManager.shiftIndices(0, shiftAmount); const newSecureBuffers = new Map(); for (const [index, buffer] of this.secureBuffers.entries()) { newSecureBuffers.set(index + shiftAmount, buffer); } this.secureBuffers = newSecureBuffers; // Add new values at the beginning for (let i = 0; i < values.length; i++) { const value = values[i]; if (typeof value === "string") { const secureBuffer = secureMemory.SecureBuffer.from(value); this.secureBuffers.set(i, secureBuffer); this.metadataManager.update(i, "string", true); } else { this.metadataManager.update(i, typeof value, false); } } this.elements.unshift(...values); this.updateLastModified(); this.eventManager.emit("unshift", 0, values); return this.elements.length; } /** * Adds multiple elements to the array */ pushAll(values) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); this.checkSizeLimit(values.length); for (const value of values) { this.push(value); } return this.elements.length; } // ===== ENHANCED ARRAY METHODS ===== /** * Removes elements from array and optionally inserts new elements */ splice(start, deleteCount, ...items) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); if (start < 0) { start = Math.max(0, this.elements.length + start); } deleteCount = deleteCount === undefined ? this.elements.length - start : Math.max(0, deleteCount); if (items.length > 0) { this.checkSizeLimit(items.length - deleteCount); for (const item of items) { this.validateValue(item); } } // Create a new SecureArray for removed elements const removedElements = new SecureArray(); // Get elements to be removed for (let i = start; i < start + deleteCount && i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { removedElements.push(value); } } // Clean up secure buffers for removed elements for (let i = start; i < start + deleteCount; i++) { this.cleanupIndex(i); } // Perform the splice operation const removed = this.elements.splice(start, deleteCount, ...items); // Update metadata and secure buffers this.metadataManager.splice(start, deleteCount, items.length); // Handle secure storage for new items for (let i = 0; i < items.length; i++) { const value = items[i]; const index = start + i; if (typeof value === "string") { const secureBuffer = secureMemory.SecureBuffer.from(value); this.secureBuffers.set(index, secureBuffer); this.metadataManager.update(index, "string", true); } else { this.metadataManager.update(index, typeof value, false); } } this.updateLastModified(); this.eventManager.emit("splice", start, { removed, added: items }); return removedElements; } /** * Returns a shallow copy of a portion of the array */ slice(start, end) { this.ensureNotDestroyed(); const slicedElements = []; const actualStart = start === undefined ? 0 : start < 0 ? Math.max(0, this.elements.length + start) : start; const actualEnd = end === undefined ? this.elements.length : end < 0 ? this.elements.length + end : end; for (let i = actualStart; i < actualEnd && i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { slicedElements.push(value); } } const newArray = new SecureArray(slicedElements, { ...this.options, }); this.eventManager.emit("slice", actualStart, { start: actualStart, end: actualEnd, result: newArray, }); return newArray; } /** * Concatenates arrays and returns a new SecureArray */ concat(...arrays) { this.ensureNotDestroyed(); const newElements = []; // Add current array elements for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { newElements.push(value); } } // Add elements from other arrays for (const arr of arrays) { if (arr instanceof SecureArray) { for (let i = 0; i < arr.length; i++) { const value = arr.get(i); if (value !== undefined) { newElements.push(value); } } } else { newElements.push(...arr); } } return new SecureArray(newElements, { ...this.options }); } /** * Joins all elements into a string */ join(separator = ",") { this.ensureNotDestroyed(); const stringElements = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); stringElements.push(value?.toString() ?? ""); } return stringElements.join(separator); } /** * Reverses the array in place */ reverse() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); // Create new mappings for secure buffers const newSecureBuffers = new Map(); const length = this.elements.length; for (const [index, buffer] of this.secureBuffers.entries()) { newSecureBuffers.set(length - 1 - index, buffer); } this.secureBuffers = newSecureBuffers; this.elements.reverse(); this.metadataManager.reverse(length); this.updateLastModified(); this.eventManager.emit("reverse", -1, undefined); return this; } /** * Sorts the array in place */ sort(compareFn) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); // Get actual values for sorting const indexValuePairs = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { indexValuePairs.push({ index: i, value }); } } // Sort the pairs indexValuePairs.sort((a, b) => { if (compareFn) { return compareFn(a.value, b.value); } const aStr = String(a.value); const bStr = String(b.value); return aStr < bStr ? -1 : aStr > bStr ? 1 : 0; }); // Rebuild the array in sorted order const newElements = []; const newSecureBuffers = new Map(); for (let i = 0; i < indexValuePairs.length; i++) { const { index: oldIndex, value } = indexValuePairs[i]; newElements[i] = this.elements[oldIndex]; if (this.secureBuffers.has(oldIndex)) { newSecureBuffers.set(i, this.secureBuffers.get(oldIndex)); } } this.elements = newElements; this.secureBuffers = newSecureBuffers; this.metadataManager.reorder(indexValuePairs.map((p) => p.index)); this.updateLastModified(); this.eventManager.emit("sort", -1, undefined); return this; } // ===== FUNCTIONAL PROGRAMMING METHODS ===== /** * Calls a function for each element */ forEach(callback, thisArg) { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { callback.call(thisArg, value, i, this); } } } /** * Creates a new array with results of calling a function for every element */ map(callback, thisArg) { this.ensureNotDestroyed(); const mappedElements = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { mappedElements.push(callback.call(thisArg, value, i, this)); } } return new SecureArray(mappedElements, { ...this.options }); } /** * Creates a new array with elements that pass a test */ filter(predicate, thisArg) { this.ensureNotDestroyed(); const filteredElements = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined && predicate.call(thisArg, value, i, this)) { filteredElements.push(value); } } return new SecureArray(filteredElements, { ...this.options }); } /** * Reduces the array to a single value */ reduce(callback, initialValue) { this.ensureNotDestroyed(); let accumulator = initialValue; let startIndex = 0; if (accumulator === undefined) { if (this.elements.length === 0) { throw new TypeError("Reduce of empty array with no initial value"); } accumulator = this.get(0); startIndex = 1; } for (let i = startIndex; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { accumulator = callback(accumulator, value, i, this); } } return accumulator; } /** * Tests whether at least one element passes the test */ some(predicate, thisArg) { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined && predicate.call(thisArg, value, i, this)) { return true; } } return false; } /** * Tests whether all elements pass the test */ every(predicate, thisArg) { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined && !predicate.call(thisArg, value, i, this)) { return false; } } return true; } /** * Finds the first element that satisfies the predicate */ find(predicate, thisArg) { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined && predicate.call(thisArg, value, i, this)) { return value; } } return undefined; } /** * Finds the index of the first element that satisfies the predicate */ findIndex(predicate, thisArg) { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined && predicate.call(thisArg, value, i, this)) { return i; } } return -1; } /** * Finds the last element that satisfies the predicate */ findLast(predicate, thisArg) { this.ensureNotDestroyed(); for (let i = this.elements.length - 1; i >= 0; i--) { const value = this.get(i); if (value !== undefined && predicate.call(thisArg, value, i, this)) { return value; } } return undefined; } /** * Finds the index of the last element that satisfies the predicate */ findLastIndex(predicate, thisArg) { this.ensureNotDestroyed(); for (let i = this.elements.length - 1; i >= 0; i--) { const value = this.get(i); if (value !== undefined && predicate.call(thisArg, value, i, this)) { return i; } } return -1; } /** * Returns the first index of an element */ indexOf(searchElement, fromIndex = 0) { this.ensureNotDestroyed(); const startIndex = fromIndex < 0 ? Math.max(0, this.elements.length + fromIndex) : fromIndex; for (let i = startIndex; i < this.elements.length; i++) { const value = this.get(i); if (value === searchElement) { return i; } } return -1; } /** * Returns the last index of an element */ lastIndexOf(searchElement, fromIndex) { this.ensureNotDestroyed(); const startIndex = fromIndex === undefined ? this.elements.length - 1 : fromIndex < 0 ? this.elements.length + fromIndex : fromIndex; for (let i = Math.min(startIndex, this.elements.length - 1); i >= 0; i--) { const value = this.get(i); if (value === searchElement) { return i; } } return -1; } /** * Checks if an element exists in the array */ includes(searchElement, fromIndex = 0) { return this.indexOf(searchElement, fromIndex) !== -1; } // ===== ENHANCED SECURITY METHODS ===== /** * Creates a snapshot of the current array state */ createSnapshot(name) { this.ensureNotDestroyed(); const snapshotId = name || `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Deep copy elements const elementsCopy = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { elementsCopy.push(value); } } // Copy metadata const metadataCopy = new Map(this.metadataManager.getAllMetadata()); this.snapshots.set(snapshotId, { elements: elementsCopy, metadata: metadataCopy, timestamp: Date.now(), }); this.eventManager.emit("snapshot_created", -1, undefined, { snapshotId, }); return snapshotId; } /** * Restores the array from a snapshot */ restoreFromSnapshot(snapshotId) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); const snapshot = this.snapshots.get(snapshotId); if (!snapshot) { return false; } // Clear current state this.clear(); // Restore from snapshot for (const element of snapshot.elements) { this.push(element); } this.eventManager.emit("snapshot_restored", -1, undefined, { snapshotId, }); return true; } /** * Lists available snapshots */ listSnapshots() { return Array.from(this.snapshots.entries()).map(([id, snapshot]) => ({ id, timestamp: snapshot.timestamp, })); } /** * Deletes a snapshot */ deleteSnapshot(snapshotId) { return this.snapshots.delete(snapshotId); } /** * Clears all elements from the array */ clear() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); // Clean up all secure buffers for (const buffer of this.secureBuffers.values()) { buffer.destroy(); } this.secureBuffers.clear(); // Clear elements and metadata this.elements.length = 0; this.metadataManager.clear(); this.updateLastModified(); this.eventManager.emit("clear", -1, undefined); return this; } /** * Freezes the array to prevent modifications */ freeze() { this.ensureNotDestroyed(); this._isFrozen = true; this.eventManager.emit("freeze", -1, undefined); return this; } /** * Unfreezes the array to allow modifications */ unfreeze() { this.ensureNotDestroyed(); this._isFrozen = false; this.eventManager.emit("unfreeze", -1, undefined); return this; } /** * Makes the array read-only */ makeReadOnly() { this.ensureNotDestroyed(); this._isReadOnly = true; this.eventManager.emit("readonly", -1, undefined); return this; } /** * Removes read-only restriction */ makeWritable() { this.ensureNotDestroyed(); this._isReadOnly = false; this.eventManager.emit("writable", -1, undefined); return this; } /** * Securely wipes all data and destroys the array * @example * //===================== correct =========== const x = fArray([] as number[]); x.push(12); console.log(x._array); x.destroy(); //================ incorrect =============== const x = fArray([] as number[]); x.destroy(); // will destroy the array x.push(12); // x.push will throw an error console.log(x._array); // will throw an error */ destroy() { if (this._isDestroyed) { return; } // Clean up all secure buffers for (const buffer of this.secureBuffers.values()) { buffer.destroy(); } this.secureBuffers.clear(); // Clear snapshots this.snapshots.clear(); // Secure wipe of elements array if (this.elements.length > 0) { this.elements.fill(null); this.elements.length = 0; } // Clear metadata this.metadataManager.clear(); // Unregister from memory manager if (this._memoryTracking) { index$1.memoryManager.unregisterObject(this._id); } this._isDestroyed = true; this.eventManager.emit("destroyed", -1, undefined); // Clear event listeners this.eventManager.removeAllListeners(); } // ===== UTILITY AND STATISTICS METHODS ===== /** * Gets comprehensive statistics about the array */ getStats() { this.ensureNotDestroyed(); const typeCount = new Map(); let secureElementCount = 0; let totalMemoryUsage = 0; let totalAccesses = 0; for (let i = 0; i < this.elements.length; i++) { const metadata = this.metadataManager.get(i); if (metadata) { const type = metadata.type; typeCount.set(type, (typeCount.get(type) || 0) + 1); if (metadata.isSecure) { secureElementCount++; } totalAccesses += metadata.accessCount; } // Estimate memory usage const value = this.get(i); if (value !== undefined) { if (typeof value === "string") { totalMemoryUsage += value.length * 2; // UTF-16 } else if (typeof value === "number") { totalMemoryUsage += 8; // 64-bit number } else if (typeof value === "boolean") { totalMemoryUsage += 1; } else { totalMemoryUsage += 64; // Estimate for objects } } } return { length: this.elements.length, secureElements: secureElementCount, totalAccesses, memoryUsage: totalMemoryUsage, lastModified: this._lastModified, version: this._version, createdAt: this._createdAt, isReadOnly: this._isReadOnly, isFrozen: this._isFrozen, typeDistribution: Object.fromEntries(typeCount), secureElementCount, estimatedMemoryUsage: totalMemoryUsage, snapshotCount: this.snapshots.size, encryptionEnabled: this.cryptoHandler.getEncryptionStatus().hasEncryptionKey, }; } /** * Validates the integrity of the array */ validateIntegrity() { this.ensureNotDestroyed(); const errors = []; // Check if elements length matches metadata count const metadataCount = this.metadataManager.size(); if (this.elements.length !== metadataCount) { errors.push(`Element count (${this.elements.length}) doesn't match metadata count (${metadataCount})`); } // Check secure buffer consistency for (const [index, buffer] of this.secureBuffers.entries()) { if (index >= this.elements.length) { errors.push(`Secure buffer exists for invalid index: ${index}`); } const metadata = this.metadataManager.get(index); if (!metadata || !metadata.isSecure) { errors.push(`Secure buffer exists but metadata indicates non-secure element at index: ${index}`); } try { buffer.getBuffer(); // Test if buffer is still valid } catch (error) { errors.push(`Invalid secure buffer at index ${index}: ${error}`); } } // Check for memory leaks in metadata for (let i = 0; i < this.metadataManager.size(); i++) { if (!this.metadataManager.has(i) && i < this.elements.length) { errors.push(`Missing metadata for element at index: ${i}`); } } return { isValid: errors.length === 0, errors, }; } /** * Compacts the array by removing undefined/null elements */ compact() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); const compactedElements = []; const newSecureBuffers = new Map(); let newIndex = 0; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== null && value !== undefined) { compactedElements.push(this.elements[i]); if (this.secureBuffers.has(i)) { newSecureBuffers.set(newIndex, this.secureBuffers.get(i)); } const metadata = this.metadataManager.get(i); if (metadata) { this.metadataManager.update(newIndex, metadata.type, metadata.isSecure); } newIndex++; } else { // Clean up secure buffer for removed element this.cleanupIndex(i); } } // Clean up old metadata this.metadataManager.clear(); this.elements = compactedElements; this.secureBuffers = newSecureBuffers; this.updateLastModified(); this.eventManager.emit("compact", -1, undefined, { originalLength: this.elements.length + (this.elements.length - compactedElements.length), newLength: compactedElements.length, }); return this; } /** * Removes duplicate elements */ unique() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); const seen = new Set(); const uniqueElements = []; const newSecureBuffers = new Map(); let newIndex = 0; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined && !seen.has(value)) { seen.add(value); uniqueElements.push(this.elements[i]); if (this.secureBuffers.has(i)) { newSecureBuffers.set(newIndex, this.secureBuffers.get(i)); } const metadata = this.metadataManager.get(i); if (metadata) { this.metadataManager.update(newIndex, metadata.type, metadata.isSecure); } newIndex++; } else if (value !== undefined) { // Clean up duplicate's secure buffer this.cleanupIndex(i); } } // Clean up old metadata this.metadataManager.clear(); this.elements = uniqueElements; this.secureBuffers = newSecureBuffers; this.updateLastModified(); this.eventManager.emit("unique", -1, undefined, { originalLength: this.elements.length + (this.elements.length - uniqueElements.length), newLength: uniqueElements.length, }); return this; } /** * Groups elements by a key function */ groupBy(keyFn) { this.ensureNotDestroyed(); const groups = new Map(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { const key = keyFn(value, i); if (!groups.has(key)) { groups.set(key, new SecureArray([], { ...this.options })); } groups.get(key).push(value); } } return groups; } /** * Returns the minimum value */ min(compareFn) { this.ensureNotDestroyed(); if (this.elements.length === 0) { return undefined; } let min = this.get(0); if (min === undefined) { return undefined; } for (let i = 1; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { if (compareFn) { if (compareFn(value, min) < 0) { min = value; } } else { if (value < min) { min = value; } } } } return min; } /** * Returns the maximum value */ max(compareFn) { this.ensureNotDestroyed(); if (this.elements.length === 0) { return undefined; } let max = this.get(0); if (max === undefined) { return undefined; } for (let i = 1; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { if (compareFn) { if (compareFn(value, max) > 0) { max = value; } } else { if (value > max) { max = value; } } } } return max; } /** * Shuffles the array in place using Fisher-Yates algorithm */ shuffle() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); for (let i = this.elements.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); // Swap elements [this.elements[i], this.elements[j]] = [ this.elements[j], this.elements[i], ]; // Swap secure buffers if they exist const bufferI = this.secureBuffers.get(i); const bufferJ = this.secureBuffers.get(j); if (bufferI) { this.secureBuffers.set(j, bufferI); } else { this.secureBuffers.delete(j); } if (bufferJ) { this.secureBuffers.set(i, bufferJ); } else { this.secureBuffers.delete(i); } // Swap metadata const metaI = this.metadataManager.get(i); const metaJ = this.metadataManager.get(j); if (metaI && metaJ) { this.metadataManager.update(i, metaJ.type, metaJ.isSecure); this.metadataManager.update(j, metaI.type, metaI.isSecure); } } this.updateLastModified(); this.eventManager.emit("shuffle", -1, undefined); return this; } /** * Returns a random sample of elements */ sample(count = 1) { this.ensureNotDestroyed(); if (count <= 0 || this.elements.length === 0) { return new SecureArray([], { ...this.options }); } const sampleCount = Math.min(count, this.elements.length); const indices = new Set(); const sampledElements = []; while (indices.size < sampleCount) { const randomIndex = Math.floor(Math.random() * this.elements.length); if (!indices.has(randomIndex)) { indices.add(randomIndex); const value = this.get(randomIndex); if (value !== undefined) { sampledElements.push(value); } } } return new SecureArray(sampledElements, { ...this.options }); } // ===== ITERATOR IMPLEMENTATION ===== /** * Returns an iterator for the array */ *[Symbol.iterator]() { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { yield value; } } } /** * Returns an iterator for array entries [index, value] */ *entries() { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { yield [i, value]; } } } /** * Returns an iterator for array indices */ *keys() { this.ensureNotDestroyed(); for (let i = 0; i < this.elements.length; i++) { yield i; } } /** * Returns an iterator for array values */ *values() { return this[Symbol.iterator](); } // ===== EVENT MANAGEMENT ===== /** * Adds an event listener */ on(event, listener) { this.eventManager.on(event, listener); return this; } /** * Removes an event listener */ off(event, listener) { this.eventManager.off(event, listener); return this; } /** * Adds a one-time event listener */ once(event, listener) { this.eventManager.once(event, listener); return this; } // ===== SERIALIZATION AND EXPORT ===== /** * Serializes the SecureArray to a secure format */ serialize(options) { this.ensureNotDestroyed(); return this.serializationHandler.serialize(this.elements, options); } /** * Exports the SecureArray data in various formats */ exportData(format = "json") { this.ensureNotDestroyed(); // Get the actual values (not SecureBuffers) const actualElements = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { actualElements.push(value); } } return this.serializationHandler.exportData(actualElements, format); } /** * Converts to a regular JavaScript array (loses security features) */ toArray() { this.ensureNotDestroyed(); const regularArray = []; for (let i = 0; i < this.elements.length; i++) { const value = this.get(i); if (value !== undefined) { regularArray.push(value); } } return regularArray; } /** * Converts to a regular JavaScript array (same as toArray method) */ get _array() { return this.toArray(); } /** * Creates a SecureArray from a regular array */ static from(arrayLike, options) { const elements = Array.from(arrayLike); return new SecureArray(elements, options); } /** * Creates a SecureArray with specified length and fill value */ static of(...elements) { return new SecureArray(elements); } // ===== ENCRYPTION METHODS ===== /** * Gets encryption status from the crypto handler */ getEncryptionStatus() { return this.cryptoHandler.getEncryptionStatus(); } /** * Sets an encryption key for the array */ setEncryptionKey(key) { this.ensureNotDestroyed(); this.cryptoHandler.setEncryptionKey(key); } /** * Gets the raw encrypted data without decryption (for verification) */ getRawEncryptedData() { this.ensureNotDestroyed(); return [...this.elements]; } /** * Gets a specific element's raw encrypted form (for verification) */ getRawEncryptedElement(index) { this.ensureNotDestroyed(); this.validateIndex(index); if (index >= this.elements.length) { return undefined; } return this.elements[index]; } /** * Encrypts all elements in the array using AES-256-CTR-HMAC encryption * with proper memory management and atomic operations */ encryptAll() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.ensureNotFrozen(); // Check if encryption key is set const encryptionStatus = this.cryptoHandler.getEncryptionStatus(); if (!encryptionStatus.hasEncryptionKey) { throw new Error("Encryption key must be set before calling encryptAll()"); } // Prepare temporary storage for atomic operation const encryptedValues = []; const originalMetadata = new Map(); const indicesToProcess = []; try { // First pass: encrypt all values i