Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430/** * SMB2 Write Command Handler * Handles SMB2_WRITE requests and responses for writing data to files */
import type { SMB2Connection, SMB2Session, SMB2TreeConnect, SMB2WriteRequest, SMB2WriteResponse, SMB2CommandHandler, SMB2FileId,} from '../protocol/types.js';import { SMB2_STATUS, SMB2_HEADER_SIZE, SMB2_COMMANDS, ACCESS_MASK,} from '../protocol/constants.js';import { SMB2MessageParser } from '../protocol/parser.js';import { SMB2MessageSerializer } from '../protocol/serializer.js';import { SMB2FileSystemProvider } from '../filesystem/provider.js';import { Logger } from '../utils/logger.js';import { fileIdToString, clamp } from '../utils/helpers.js';
export class SMB2WriteHandler { private readonly fileSystemProvider: SMB2FileSystemProvider; private readonly parser: SMB2MessageParser; private readonly serializer: SMB2MessageSerializer; private readonly logger: Logger;
constructor(fileSystemProvider: SMB2FileSystemProvider) { this.fileSystemProvider = fileSystemProvider; this.parser = new SMB2MessageParser(); this.serializer = new SMB2MessageSerializer(); this.logger = new Logger('SMB2WriteHandler'); }
public async handle( requestData: Buffer, connection: SMB2Connection, session?: SMB2Session, treeConnect?: SMB2TreeConnect, ): Promise<Buffer> { try { // Validate session and tree connect if (!session) { this.logger.warn('Write attempt without valid session'); throw new Error('No valid session'); }
if (!treeConnect) { this.logger.warn('Write attempt without valid tree connect'); throw new Error('No valid tree connect'); }
// Parse the write request const parseResult = this.parser.parseWriteRequest(requestData); const request = parseResult.result;
const fileIdStr = fileIdToString(request.fileId); this.logger.debug(`Write request from ${connection.clientAddress}:`); this.logger.debug(` File ID: ${fileIdStr}`); this.logger.debug(` Length: ${request.length}`); this.logger.debug(` Offset: ${request.offset}`); this.logger.debug(` Flags: 0x${request.flags.toString(16)}`); this.logger.debug(` Channel: ${request.channel}`);
// Validate request this.validateWriteRequest(request, connection);
// Find the open file const openFile = connection.openFiles.get(fileIdStr); if (!openFile) { this.logger.warn(`Attempted to write to non-existent file: ${fileIdStr}`); throw new Error('File not open'); }
// Verify the file belongs to the correct session and tree if (openFile.sessionId !== session.sessionId) { this.logger.warn(`File ${fileIdStr} does not belong to session ${session.sessionId}`); throw new Error('Invalid session for file'); }
if (openFile.treeConnectId !== treeConnect.treeConnectId) { this.logger.warn(`File ${fileIdStr} does not belong to tree ${treeConnect.treeConnectId}`); throw new Error('Invalid tree connect for file'); }
// Check write access if (!(openFile.desiredAccess & (ACCESS_MASK.FILE_WRITE_DATA | ACCESS_MASK.FILE_APPEND_DATA))) { this.logger.warn(`Write access denied for file ${fileIdStr}`); throw new Error('Write access denied'); }
// Validate write parameters this.validateWriteParameters(request, openFile);
// Calculate actual write length const maxWriteSize = connection.maxWriteSize || 65536; const actualLength = Math.min(request.length, maxWriteSize); // Ensure we don't write more data than provided const dataToWrite = request.data.subarray(0, actualLength);
try { // Check for write-through flag const writeThrough = !!(request.flags & 0x00000001); // SMB2_WRITEFLAG_WRITE_THROUGH if (writeThrough) { this.logger.debug('Write-through requested'); }
// Write data to the file const bytesWritten = await this.fileSystemProvider.writeFile( request.fileId, request.offset, dataToWrite );
// Calculate remaining bytes for continuation const remainingBytes = Math.max(0, request.length - bytesWritten);
// Create response const response: SMB2WriteResponse = { structureSize: 17, reserved: 0, count: bytesWritten, remaining: remainingBytes, writeChannelInfoOffset: 0, writeChannelInfoLength: 0, writeChannelInfo: Buffer.alloc(0), };
// Serialize the response const responseBuffer = this.serializer.serializeWriteResponse(response);
this.logger.debug(`Write successful: ${bytesWritten} bytes to ${openFile.fileName} at offset ${request.offset}`); // Log performance metrics for large writes if (bytesWritten > 1024 * 1024) { // 1MB this.logger.debug(`Large write completed: ${(bytesWritten / 1024 / 1024).toFixed(2)}MB`); }
return responseBuffer;
} catch (error) { this.logger.error(`Error writing to file ${fileIdStr}:`, error); throw error; }
} catch (error) { this.logger.error('Error handling write request:', error); throw error; } }
/** * Validate write request parameters */ private validateWriteRequest(request: SMB2WriteRequest, connection: SMB2Connection): void { if (request.structureSize !== 49) { throw new Error(`Invalid structure size: ${request.structureSize}`); }
// Validate file ID is not null if (request.fileId.persistent === 0n && request.fileId.volatile === 0n) { throw new Error('Invalid file ID (null file ID)'); }
// Validate write length if (request.length === 0) { throw new Error('Write length cannot be zero'); }
if (request.length > (connection.maxWriteSize || 16 * 1024 * 1024)) { throw new Error('Write length exceeds maximum allowed size'); }
// Validate that we have data to write if (!request.data || request.data.length === 0) { throw new Error('No data provided for write'); }
if (request.data.length < request.length) { throw new Error('Provided data is shorter than specified length'); }
// Validate offset (check for reasonable bounds) if (request.offset < 0n) { throw new Error('Write offset cannot be negative'); }
// Check for potential overflow const maxOffset = BigInt(Number.MAX_SAFE_INTEGER); if (request.offset > maxOffset) { throw new Error('Write offset too large'); }
// Validate flags const validFlags = 0x00000001 | 0x00000002; // WRITE_THROUGH | WRITE_UNBUFFERED if ((request.flags & ~validFlags) !== 0) { this.logger.warn(`Unknown write flags: 0x${request.flags.toString(16)}`); } }
/** * Validate write parameters against file properties */ private validateWriteParameters(request: SMB2WriteRequest, openFile: any): void { // Additional validation can be added here based on file properties // For example, checking file permissions, disk space, etc. // Check if this is an append operation if (openFile.desiredAccess & ACCESS_MASK.FILE_APPEND_DATA) { this.logger.debug(`Append operation to file: ${openFile.fileName}`); }
// Log the write operation this.logger.debug(`Writing to file: ${openFile.fileName}`); }
/** * Handle write with channel info (for multichannel scenarios) */ private async handleChannelWrite( request: SMB2WriteRequest, connection: SMB2Connection ): Promise<number> { // Stub implementation for multichannel writes // In a full implementation, this would handle: // - Channel selection for optimal performance // - Load balancing across channels // - Failover scenarios // - Coordinated writes across multiple channels this.logger.debug('Multichannel write requested (not implemented)'); // Fall back to regular write return this.fileSystemProvider.writeFile( request.fileId, request.offset, request.data ); }
/** * Get write statistics for monitoring */ public getWriteStats(): { totalWrites: number; totalBytesWritten: bigint; averageWriteSize: number; largeWrites: number; } { // This would be implemented to track write statistics // For now, return default values return { totalWrites: 0, totalBytesWritten: 0n, averageWriteSize: 0, largeWrites: 0, }; }
/** * Check if write operation would benefit from async processing */ private shouldUseAsyncWrite(request: SMB2WriteRequest, openFile: any): boolean { // Determine if this write should be handled asynchronously // Factors to consider: // - Write size // - File location (network vs local) // - Current server load // - Client capabilities // - Write-through requirements const ASYNC_THRESHOLD = 1024 * 1024; // 1MB const hasWriteThrough = !!(request.flags & 0x00000001); // Don't use async for write-through operations return request.length > ASYNC_THRESHOLD && !hasWriteThrough; }
/** * Perform async write operation */ private async performAsyncWrite( request: SMB2WriteRequest, connection: SMB2Connection, session: SMB2Session ): Promise<number> { // Stub implementation for async writes // In a full implementation, this would: // 1. Send interim response // 2. Perform write operation in background // 3. Send final response when complete this.logger.debug('Async write operation requested (not fully implemented)'); // For now, just perform synchronous write return this.fileSystemProvider.writeFile( request.fileId, request.offset, request.data ); }
/** * Handle write caching and optimization */ private async handleWriteCaching( request: SMB2WriteRequest, openFile: any ): Promise<void> { // Implement write caching logic for performance optimization // This would buffer small writes and flush them periodically this.logger.debug('Write caching optimization triggered (not implemented)'); }
/** * Validate write bounds and handle file extension */ private async validateWriteBounds( request: SMB2WriteRequest, fileId: SMB2FileId ): Promise<void> { try { const fileInfo = await this.fileSystemProvider.getFileInfo(fileId); const currentSize = fileInfo.standard.endOfFile; const writeEnd = request.offset + BigInt(request.length); // Check if write will extend the file if (writeEnd > currentSize) { this.logger.debug(`Write will extend file: current ${currentSize}, new ${writeEnd}`); // Check available disk space (would be implemented) // await this.checkDiskSpace(writeEnd - currentSize); } } catch (error) { // If we can't get file info, continue with write anyway this.logger.warn('Could not validate write bounds:', error); } }
/** * Handle compressed file writes */ private async handleCompressedWrite( request: SMB2WriteRequest, openFile: any ): Promise<number> { // Stub implementation for compressed file handling // In a full implementation, this would: // - Detect if file is compressed // - Compress data before writing // - Handle partial writes to compressed files this.logger.debug('Compressed file write (not implemented)'); // Fall back to regular write return this.fileSystemProvider.writeFile( request.fileId, request.offset, request.data ); }
/** * Handle write conflicts and locking */ private async handleWriteConflicts( request: SMB2WriteRequest, openFile: any ): Promise<void> { // Implement write conflict detection and resolution // This would handle: // - Range locks // - Exclusive access // - Opportunistic locks // - Share mode conflicts this.logger.debug('Write conflict checking (not implemented)'); }
/** * Perform write-through operation */ private async performWriteThrough( request: SMB2WriteRequest, connection: SMB2Connection ): Promise<number> { // Implement write-through operation // This ensures data is written directly to storage without caching this.logger.debug('Write-through operation'); const bytesWritten = await this.fileSystemProvider.writeFile( request.fileId, request.offset, request.data ); // Force flush to storage (would be implemented at filesystem level) // await this.fileSystemProvider.flushFile(request.fileId); return bytesWritten; }
/** * Handle unbuffered writes */ private async performUnbufferedWrite( request: SMB2WriteRequest, connection: SMB2Connection ): Promise<number> { // Implement unbuffered write operation // This bypasses system caches for direct I/O this.logger.debug('Unbuffered write operation'); // For now, just perform regular write return this.fileSystemProvider.writeFile( request.fileId, request.offset, request.data ); }}