Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347/** * SMB2 Read Command Handler * Handles SMB2_READ requests and responses for reading data from files */
import type { SMB2Connection, SMB2Session, SMB2TreeConnect, SMB2ReadRequest, SMB2ReadResponse, 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 SMB2ReadHandler { 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('SMB2ReadHandler'); }
public async handle( requestData: Buffer, connection: SMB2Connection, session?: SMB2Session, treeConnect?: SMB2TreeConnect, ): Promise<Buffer> { try { // Validate session and tree connect if (!session) { this.logger.warn('Read attempt without valid session'); throw new Error('No valid session'); }
if (!treeConnect) { this.logger.warn('Read attempt without valid tree connect'); throw new Error('No valid tree connect'); }
// Parse the read request const parseResult = this.parser.parseReadRequest(requestData); const request = parseResult.result;
const fileIdStr = fileIdToString(request.fileId); this.logger.debug(`Read 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(` Minimum count: ${request.minimumCount}`); this.logger.debug(` Flags: 0x${request.flags.toString(16)}`);
// Validate request this.validateReadRequest(request, connection);
// Find the open file const openFile = connection.openFiles.get(fileIdStr); if (!openFile) { this.logger.warn(`Attempted to read from 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 read access if (!(openFile.desiredAccess & ACCESS_MASK.FILE_READ_DATA)) { this.logger.warn(`Read access denied for file ${fileIdStr}`); throw new Error('Read access denied'); }
// Validate read parameters this.validateReadParameters(request, openFile);
// Calculate actual read length const maxReadSize = connection.maxReadSize || 65536; const actualLength = Math.min(request.length, maxReadSize);
try { // Read data from the file const data = await this.fileSystemProvider.readFile( request.fileId, request.offset, actualLength );
// Check if we read less than the minimum required if (data.length < request.minimumCount && data.length < actualLength) { this.logger.warn(`Read returned ${data.length} bytes, less than minimum ${request.minimumCount}`); // Continue anyway - some clients are flexible about this }
// Calculate remaining bytes (for sequential reads) const remainingBytes = Math.max(0, actualLength - data.length);
// Create response const response: SMB2ReadResponse = { structureSize: 17, dataOffset: SMB2_HEADER_SIZE + 17, dataLength: data.length, dataRemaining: remainingBytes, reserved: 0, data, };
// Serialize the response const responseBuffer = this.serializer.serializeReadResponse(response);
this.logger.debug(`Read successful: ${data.length} bytes from ${openFile.fileName} at offset ${request.offset}`); // Log performance metrics for large reads if (data.length > 1024 * 1024) { // 1MB this.logger.debug(`Large read completed: ${(data.length / 1024 / 1024).toFixed(2)}MB`); }
return responseBuffer;
} catch (error) { this.logger.error(`Error reading from file ${fileIdStr}:`, error); throw error; }
} catch (error) { this.logger.error('Error handling read request:', error); throw error; } }
/** * Validate read request parameters */ private validateReadRequest(request: SMB2ReadRequest, 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 read length if (request.length === 0) { throw new Error('Read length cannot be zero'); }
if (request.length > (connection.maxReadSize || 16 * 1024 * 1024)) { throw new Error('Read length exceeds maximum allowed size'); }
// Validate minimum count if (request.minimumCount > request.length) { throw new Error('Minimum count cannot exceed read length'); }
// Validate offset (check for reasonable bounds) if (request.offset < 0n) { throw new Error('Read offset cannot be negative'); }
// Check for potential overflow const maxOffset = BigInt(Number.MAX_SAFE_INTEGER); if (request.offset > maxOffset) { throw new Error('Read offset too large'); } }
/** * Validate read parameters against file properties */ private validateReadParameters(request: SMB2ReadRequest, openFile: any): void { // Additional validation can be added here based on file properties // For example, checking file size bounds, etc. // For now, just log the read operation this.logger.debug(`Reading from file: ${openFile.fileName}`); }
/** * Handle read with channel info (for multichannel scenarios) */ private async handleChannelRead( request: SMB2ReadRequest, connection: SMB2Connection ): Promise<Buffer> { // Stub implementation for multichannel reads // In a full implementation, this would handle: // - Channel selection // - Load balancing across channels // - Failover scenarios this.logger.debug('Multichannel read requested (not implemented)'); // Fall back to regular read return this.fileSystemProvider.readFile( request.fileId, request.offset, request.length ); }
/** * Get read statistics for monitoring */ public getReadStats(): { totalReads: number; totalBytesRead: bigint; averageReadSize: number; largeReads: number; } { // This would be implemented to track read statistics // For now, return default values return { totalReads: 0, totalBytesRead: 0n, averageReadSize: 0, largeReads: 0, }; }
/** * Check if read operation would benefit from async processing */ private shouldUseAsyncRead(request: SMB2ReadRequest, openFile: any): boolean { // Determine if this read should be handled asynchronously // Factors to consider: // - Read size // - File location (network vs local) // - Current server load // - Client capabilities const ASYNC_THRESHOLD = 1024 * 1024; // 1MB return request.length > ASYNC_THRESHOLD; }
/** * Perform async read operation */ private async performAsyncRead( request: SMB2ReadRequest, connection: SMB2Connection, session: SMB2Session ): Promise<Buffer> { // Stub implementation for async reads // In a full implementation, this would: // 1. Send interim response // 2. Perform read operation in background // 3. Send final response when complete this.logger.debug('Async read operation requested (not fully implemented)'); // For now, just perform synchronous read return this.fileSystemProvider.readFile( request.fileId, request.offset, request.length ); }
/** * Handle read-ahead optimization */ private async handleReadAhead( request: SMB2ReadRequest, openFile: any ): Promise<void> { // Implement read-ahead logic for sequential access patterns // This would cache subsequent blocks to improve performance this.logger.debug('Read-ahead optimization triggered (not implemented)'); }
/** * Validate read bounds against file size */ private async validateReadBounds( request: SMB2ReadRequest, fileId: SMB2FileId ): Promise<void> { try { const fileInfo = await this.fileSystemProvider.getFileInfo(fileId); const fileSize = fileInfo.standard.endOfFile; // Check if read starts beyond end of file if (request.offset >= fileSize) { this.logger.debug(`Read starts beyond EOF: offset ${request.offset}, size ${fileSize}`); // This is not necessarily an error - Windows allows it } // Log if read extends beyond file size if (request.offset + BigInt(request.length) > fileSize) { this.logger.debug(`Read extends beyond EOF: will be truncated`); } } catch (error) { // If we can't get file info, continue with read anyway this.logger.warn('Could not validate read bounds:', error); } }
/** * Handle compressed file reads */ private async handleCompressedRead( request: SMB2ReadRequest, openFile: any ): Promise<Buffer> { // Stub implementation for compressed file handling // In a full implementation, this would: // - Detect if file is compressed // - Decompress data on-the-fly // - Handle partial decompression for range reads this.logger.debug('Compressed file read (not implemented)'); // Fall back to regular read return this.fileSystemProvider.readFile( request.fileId, request.offset, request.length ); }}