import { createSocket, Socket } from 'dgram';
import { v4 as uuidv4 } from 'uuid';
import { createHash, randomBytes, createCipher, createDecipher } from 'crypto';
import { ChildProcess, spawn } from 'child_process';
import { BaseProtocol } from '../core/BaseProtocol.js';
import {
ConsoleSession,
ConsoleOutput,
ConsoleEvent,
ConsoleType,
SessionOptions,
} from '../types/index.js';
import { SessionState } from '../core/IProtocol.js';
import {
ProtocolCapabilities,
ProtocolHealthStatus,
} from '../core/ProtocolFactory.js';
// IPMI Connection Options Interface
export interface IPMIConnectionOptions {
host: string;
port?: number; // Default: 623
username: string;
password: string;
// IPMI Version Support
ipmiVersion?: '1.5' | '2.0'; // Default: 2.0
// Authentication and Privilege Levels
privilegeLevel?: 'user' | 'operator' | 'admin'; // Default: admin
authenticationType?: 'none' | 'md2' | 'md5' | 'password' | 'oem';
// Cipher Suite Selection for IPMI 2.0
cipherSuite?: number; // 0-17, Default: 3 (AES-128-CBC, SHA1-HMAC)
// Session Settings
sessionTimeout?: number; // Default: 30000ms
maxRetries?: number; // Default: 3
retryDelay?: number; // Default: 1000ms
keepAliveInterval?: number; // Default: 10000ms
// Interface Type
interface?: 'lan' | 'lanplus' | 'serial' | 'open'; // Default: lanplus
// Vendor Specific Settings
vendor?: 'dell' | 'hp' | 'ibm' | 'supermicro' | 'generic'; // Default: generic
// Serial over LAN Settings
sol?: {
enabled: boolean;
payloadType?: number; // Default: 1
port?: number; // Default: 623
privilege?: string;
encryption?: boolean;
authentication?: boolean;
};
// DCMI Settings
dcmi?: {
enabled: boolean;
powerCapping?: boolean;
thermalManagement?: boolean;
assetTag?: boolean;
};
// Advanced Settings
bridging?: {
enabled: boolean;
targetChannel?: number;
targetAddress?: number;
transitChannel?: number;
transitAddress?: number;
};
// Security Settings
kg?: string; // BMC Key for enhanced security
confidentialityAlgorithm?: 'none' | 'aes-cbc-128';
integrityAlgorithm?: 'none' | 'hmac-sha1-96' | 'hmac-md5-128' | 'md5-128';
// Protocol Timeouts
timeouts?: {
connection: number;
response: number;
session: number;
sol: number;
};
}
// IPMI Message Structure
interface IPMIMessage {
netFn: number;
cmd: number;
data: Buffer;
sessionId?: number;
sequence?: number;
authType?: number;
sessionSeq?: number;
}
// IPMI Response Structure
interface IPMIResponse {
completionCode: number;
data: Buffer;
netFn: number;
cmd: number;
sessionId?: number;
sequence?: number;
}
// Session State
interface IPMISessionState {
sessionId: number;
managedSystemSessionId: number;
consoleSessionId: number;
remoteConsoleSessionId: number;
kg: Buffer;
sik: Buffer; // Session Integrity Key
k1: Buffer; // Additional Key Generation Key 1
k2: Buffer; // Additional Key Generation Key 2
rakpAuthCode: Buffer;
privilegeLevel: number;
cipherSuite: number;
sequenceNumber: number;
authType: number;
isActive: boolean;
lastActivity: Date;
keepAliveTimer?: NodeJS.Timeout;
}
// Sensor Data Structure
export interface SensorReading {
sensorNumber: number;
sensorName: string;
sensorType: string;
entityId: number;
entityInstance: number;
reading: number;
unit: string;
status: 'ok' | 'warning' | 'critical' | 'not-readable';
thresholds: {
lowerNonRecoverable?: number;
lowerCritical?: number;
lowerWarning?: number;
upperWarning?: number;
upperCritical?: number;
upperNonRecoverable?: number;
};
eventData?: Buffer;
timestamp: Date;
}
// Power Control Operations
export type PowerControlOperation =
| 'power-down'
| 'power-up'
| 'power-cycle'
| 'hard-reset'
| 'pulse-diagnostic'
| 'soft-shutdown';
// Virtual Media Operations
export interface VirtualMediaInfo {
deviceId: number;
deviceName: string;
mediaType: 'floppy' | 'cdrom' | 'hdd' | 'usb';
connected: boolean;
writeProtected: boolean;
imagePath?: string;
imageSize?: number;
}
// Hardware Monitoring Data
export interface HardwareMonitoringData {
sensors: SensorReading[];
powerState: string;
bootProgress: string;
systemHealth: 'ok' | 'warning' | 'critical';
fans: Array<{
name: string;
rpm: number;
status: string;
}>;
temperatures: Array<{
name: string;
value: number;
unit: string;
status: string;
}>;
voltages: Array<{
name: string;
value: number;
unit: string;
status: string;
}>;
powerSupplies: Array<{
name: string;
status: string;
inputVoltage: number;
outputWattage: number;
}>;
memory: Array<{
slot: string;
size: string;
status: string;
type: string;
}>;
processors: Array<{
socket: string;
model: string;
speed: string;
status: string;
temperature?: number;
}>;
}
/**
* Production-ready IPMI/BMC Protocol Implementation
*
* Features:
* - Full IPMI 2.0 protocol support with RMCP+
* - Serial over LAN (SOL) console
* - Comprehensive sensor monitoring
* - Power management operations
* - Virtual media mounting
* - Vendor-specific extensions (iDRAC, iLO, IMM)
* - DCMI extensions
* - Firmware update capabilities
* - Hardware monitoring and alerting
*/
export class IPMIProtocol extends BaseProtocol {
public readonly type: ConsoleType = 'ipmi';
public readonly capabilities: ProtocolCapabilities;
public readonly healthStatus: ProtocolHealthStatus;
// IPMI-specific properties
private connections: Map<string, IPMIConnectionState> = new Map();
private ipmiSessions: Map<string, IPMISessionState> = new Map();
private sequenceCounter: number = 1;
private ipmiProcesses: Map<string, ChildProcess> = new Map();
// IPMI Constants
private static readonly IPMI_PORT = 623;
private static readonly RMCP_VERSION_1 = 0x06;
private static readonly RMCP_SEQUENCE = 0xff;
private static readonly RMCP_CLASS_IPMI = 0x07;
private static readonly RMCP_CLASS_ASF = 0x06;
// Authentication Types
private static readonly AUTH_TYPE_NONE = 0x00;
private static readonly AUTH_TYPE_MD2 = 0x01;
private static readonly AUTH_TYPE_MD5 = 0x02;
private static readonly AUTH_TYPE_PASSWORD = 0x04;
private static readonly AUTH_TYPE_OEM = 0x05;
private static readonly AUTH_TYPE_RMCP_PLUS = 0x06;
// NetFn Constants
private static readonly NETFN_CHASSIS = 0x00;
private static readonly NETFN_BRIDGE = 0x02;
private static readonly NETFN_SENSOR_EVENT = 0x04;
private static readonly NETFN_APPLICATION = 0x06;
private static readonly NETFN_FIRMWARE = 0x08;
private static readonly NETFN_STORAGE = 0x0a;
private static readonly NETFN_TRANSPORT = 0x0c;
private static readonly NETFN_PICMG = 0x2c;
private static readonly NETFN_DCMI = 0x2e;
private static readonly NETFN_OEM = 0x30;
// Command Constants
private static readonly CMD_GET_DEVICE_ID = 0x01;
private static readonly CMD_GET_DEVICE_GUID = 0x08;
private static readonly CMD_GET_SYSTEM_INFO = 0x59;
private static readonly CMD_GET_CHANNEL_AUTH_CAP = 0x38;
private static readonly CMD_GET_SESSION_CHALLENGE = 0x39;
private static readonly CMD_ACTIVATE_SESSION = 0x3a;
private static readonly CMD_SET_SESSION_PRIVILEGE = 0x3b;
private static readonly CMD_CLOSE_SESSION = 0x3c;
private static readonly CMD_GET_CHASSIS_STATUS = 0x01;
private static readonly CMD_CHASSIS_CONTROL = 0x02;
private static readonly CMD_GET_SDR_REPOSITORY_INFO = 0x20;
private static readonly CMD_GET_SDR = 0x23;
private static readonly CMD_GET_SENSOR_READING = 0x2d;
private static readonly CMD_SET_SOL_CONFIGURATION = 0x21;
private static readonly CMD_GET_SOL_CONFIGURATION = 0x22;
private static readonly CMD_ACTIVATE_PAYLOAD = 0x48;
private static readonly CMD_DEACTIVATE_PAYLOAD = 0x49;
// RMCP+ Session Setup Commands
private static readonly CMD_OPEN_SESSION = 0x40;
private static readonly CMD_RAKP_MESSAGE_1 = 0x41;
private static readonly CMD_RAKP_MESSAGE_2 = 0x42;
private static readonly CMD_RAKP_MESSAGE_3 = 0x43;
private static readonly CMD_RAKP_MESSAGE_4 = 0x44;
// Cipher Suite Algorithms
private static readonly CIPHER_SUITES = {
0: { auth: 'none', integrity: 'none', confidentiality: 'none' },
1: { auth: 'hmac-sha1', integrity: 'none', confidentiality: 'none' },
2: {
auth: 'hmac-sha1',
integrity: 'hmac-sha1-96',
confidentiality: 'none',
},
3: {
auth: 'hmac-sha1',
integrity: 'hmac-sha1-96',
confidentiality: 'aes-cbc-128',
},
4: {
auth: 'hmac-sha1',
integrity: 'hmac-sha1-96',
confidentiality: 'xrc4-128',
},
5: {
auth: 'hmac-sha1',
integrity: 'hmac-sha1-96',
confidentiality: 'xrc4-40',
},
6: { auth: 'hmac-md5', integrity: 'none', confidentiality: 'none' },
7: { auth: 'hmac-md5', integrity: 'hmac-md5-128', confidentiality: 'none' },
8: {
auth: 'hmac-md5',
integrity: 'hmac-md5-128',
confidentiality: 'aes-cbc-128',
},
9: {
auth: 'hmac-md5',
integrity: 'hmac-md5-128',
confidentiality: 'xrc4-128',
},
10: {
auth: 'hmac-md5',
integrity: 'hmac-md5-128',
confidentiality: 'xrc4-40',
},
11: { auth: 'hmac-md5', integrity: 'md5-128', confidentiality: 'none' },
12: {
auth: 'hmac-md5',
integrity: 'md5-128',
confidentiality: 'aes-cbc-128',
},
13: { auth: 'hmac-md5', integrity: 'md5-128', confidentiality: 'xrc4-128' },
14: { auth: 'hmac-md5', integrity: 'md5-128', confidentiality: 'xrc4-40' },
15: { auth: 'hmac-sha256', integrity: 'none', confidentiality: 'none' },
16: {
auth: 'hmac-sha256',
integrity: 'hmac-sha256-128',
confidentiality: 'none',
},
17: {
auth: 'hmac-sha256',
integrity: 'hmac-sha256-128',
confidentiality: 'aes-cbc-128',
},
};
// Vendor-specific OEM codes
private static readonly VENDOR_OEM_CODES = {
DELL: 0x0002a2,
HP: 0x00000b,
IBM: 0x0002f3,
SUPERMICRO: 0x00a015,
INTEL: 0x000157,
FUJITSU: 0x002880,
};
constructor() {
super('IPMIProtocol');
this.capabilities = {
supportsStreaming: true,
supportsFileTransfer: false,
supportsX11Forwarding: false,
supportsPortForwarding: false,
supportsAuthentication: true,
supportsEncryption: true,
supportsCompression: false,
supportsMultiplexing: false,
supportsKeepAlive: true,
supportsReconnection: true,
supportsBinaryData: true,
supportsCustomEnvironment: false,
supportsWorkingDirectory: false,
supportsSignals: false,
supportsResizing: false,
supportsPTY: false,
maxConcurrentSessions: 5,
defaultTimeout: 30000,
supportedEncodings: ['binary'],
supportedAuthMethods: ['password', 'md5'],
platformSupport: {
windows: true,
linux: true,
macos: true,
freebsd: true,
},
};
this.healthStatus = {
isHealthy: true,
lastChecked: new Date(),
errors: [],
warnings: [],
metrics: {
activeSessions: 0,
totalSessions: 0,
averageLatency: 0,
successRate: 1.0,
uptime: 0,
},
dependencies: {
ipmi: {
available: true,
version: '2.0',
},
},
};
}
/**
* Create IPMI session with comprehensive authentication
*/
async createIPMISession(
sessionId: string,
options: IPMIConnectionOptions
): Promise<ConsoleSession> {
try {
this.logger.info(`Creating IPMI session ${sessionId}`, {
host: options.host,
port: options.port || IPMIProtocol.IPMI_PORT,
username: options.username,
ipmiVersion: options.ipmiVersion || '2.0',
cipherSuite: options.cipherSuite || 3,
interface: options.interface || 'lanplus',
});
const connectionState: IPMIConnectionState = {
sessionId,
socket: null,
connectionOptions: { ...options },
isConnected: false,
lastActivity: new Date(),
sequenceNumber: 1,
outputBuffer: '',
solSession: null,
sensorCache: new Map(),
monitoringInterval: null,
powerState: 'unknown',
hardwareHealth: {
sensors: [],
powerState: 'unknown',
bootProgress: 'unknown',
systemHealth: 'ok',
fans: [],
temperatures: [],
voltages: [],
powerSupplies: [],
memory: [],
processors: [],
},
};
this.connections.set(sessionId, connectionState);
// Create UDP socket for IPMI communication
const socket = createSocket('udp4');
connectionState.socket = socket;
// Setup socket event handlers
socket.on('message', (msg, rinfo) => {
this.handleIncomingMessage(sessionId, msg, rinfo);
});
socket.on('error', (error) => {
this.logger.error(`IPMI socket error for session ${sessionId}:`, error);
this.emit('error', { sessionId, error });
});
socket.on('close', () => {
this.logger.info(`IPMI socket closed for session ${sessionId}`);
connectionState.isConnected = false;
});
// Connect to BMC
await this.connectToBMC(sessionId, options);
// Establish IPMI session
if (options.ipmiVersion === '1.5') {
await this.establishIPMI15Session(sessionId, options);
} else {
await this.establishIPMI20Session(sessionId, options);
}
// Initialize monitoring if enabled
if (options.dcmi?.enabled) {
this.startHardwareMonitoring(sessionId);
}
// Setup Serial over LAN if enabled
if (options.sol?.enabled) {
await this.setupSerialOverLAN(sessionId, options);
}
const session: ConsoleSession = {
id: sessionId,
command: `ipmi://${options.host}:${options.port || IPMIProtocol.IPMI_PORT}`,
args: [],
cwd: '/',
env: {},
createdAt: new Date(),
status: 'running',
type: (options.vendor === 'dell' ? 'idrac' : 'ipmi') as ConsoleType,
exitCode: null,
pid: null,
executionState: 'idle',
activeCommands: new Map(),
ipmiOptions: options,
ipmiState: {
sessionId,
connectionState: 'connecting',
ipmiVersion: options.ipmiVersion || '2.0',
cipherSuite: options.cipherSuite || 3,
authType: 0,
privilegeLevel: options.privilegeLevel || 'admin',
solState: options.sol?.enabled
? {
active: false,
payloadInstance: 0,
sequenceNumber: 0,
acknowledgmentNumber: 0,
characterCount: 0,
status: 0,
}
: undefined,
monitoringState: options.dcmi?.enabled
? {
enabled: true,
sensorCount: 0,
lastSensorUpdate: new Date(),
hardwareHealth: 'ok',
activeSensors: [],
}
: undefined,
statistics: {
connectTime: new Date(),
lastActivity: new Date(),
commandsExecuted: 0,
solCharactersSent: 0,
solCharactersReceived: 0,
sensorsRead: 0,
powerOperations: 0,
errors: 0,
timeouts: 0,
reconnections: 0,
},
vendorState: {
vendor: options.vendor || 'generic',
vendorExtensions: {},
customCommands: [],
},
},
};
connectionState.isConnected = true;
this.logger.info(`IPMI session ${sessionId} established successfully`);
this.emit('sessionCreated', session);
return session;
} catch (error) {
this.logger.error(`Failed to create IPMI session ${sessionId}:`, error);
this.connections.delete(sessionId);
throw error;
}
}
/**
* Connect to BMC via UDP
*/
private async connectToBMC(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
return new Promise((resolve, reject) => {
const connectionState = this.connections.get(sessionId);
if (!connectionState?.socket) {
return reject(new Error('Socket not initialized'));
}
const timeout = setTimeout(() => {
reject(new Error('Connection timeout'));
}, options.timeouts?.connection || 10000);
connectionState.socket.bind(() => {
clearTimeout(timeout);
connectionState.socket!.connect(
options.port || IPMIProtocol.IPMI_PORT,
options.host,
() => {
this.logger.info(
`Connected to BMC ${options.host}:${options.port || IPMIProtocol.IPMI_PORT}`
);
resolve();
}
);
});
});
}
/**
* Establish IPMI 1.5 Session
*/
private async establishIPMI15Session(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
// Get Authentication Capabilities
await this.getAuthenticationCapabilities(sessionId, options);
// Get Session Challenge
const challenge = await this.getSessionChallenge(sessionId, options);
// Activate Session
const sessionInfo = await this.activateSession(
sessionId,
options,
challenge
);
// Set Session Privilege Level
await this.setSessionPrivilegeLevel(sessionId, options, sessionInfo);
}
/**
* Establish IPMI 2.0 Session with RMCP+
*/
private async establishIPMI20Session(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
// Open Session Request (RAKP Message 1)
const openSessionResponse = await this.sendOpenSessionRequest(
sessionId,
options
);
// RAKP Message 1 & 2
const rakp1Response = await this.sendRAKPMessage1(
sessionId,
options,
openSessionResponse
);
// RAKP Message 3 & 4
await this.sendRAKPMessage3(sessionId, options, rakp1Response);
// Set Session Privilege Level
await this.setSessionPrivilegeLevelRMCP(sessionId, options);
// Generate session keys
this.generateSessionKeys(sessionId, options);
}
/**
* Setup Serial over LAN console
*/
private async setupSerialOverLAN(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
if (!options.sol?.enabled) return;
this.logger.info(`Setting up Serial over LAN for session ${sessionId}`);
// Configure SOL
await this.configureSOL(sessionId, options);
// Activate SOL Payload
await this.activateSOLPayload(sessionId, options);
const connectionState = this.connections.get(sessionId);
if (connectionState) {
connectionState.solSession = {
active: true,
payloadType: options.sol.payloadType || 1,
port: options.sol.port || 623,
encryption: options.sol.encryption || false,
authentication: options.sol.authentication || false,
};
}
this.logger.info(`Serial over LAN activated for session ${sessionId}`);
}
/**
* Send IPMI command
*/
private async sendIPMICommand(
sessionId: string,
message: IPMIMessage
): Promise<IPMIResponse> {
return new Promise((resolve, reject) => {
const connectionState = this.connections.get(sessionId);
if (!connectionState?.socket || !connectionState.isConnected) {
return reject(new Error('IPMI session not connected'));
}
const session = this.sessions.get(sessionId);
const requestId = this.sequenceCounter++;
// Build IPMI packet
const ipmiSession = this.ipmiSessions.get(sessionId);
const packet = this.buildIPMIPacket(message, ipmiSession);
// Setup response handler
const timeout = setTimeout(() => {
reject(new Error('IPMI command timeout'));
}, 5000);
const responseHandler = (response: IPMIResponse) => {
if (response.sequence === requestId) {
clearTimeout(timeout);
this.off('ipmiResponse', responseHandler);
resolve(response);
}
};
this.on('ipmiResponse', responseHandler);
// Send packet
connectionState.socket.send(packet, 0, packet.length, (error) => {
if (error) {
clearTimeout(timeout);
this.off('ipmiResponse', responseHandler);
reject(error);
}
});
});
}
/**
* Power control operations
*/
async powerControl(
sessionId: string,
operation: PowerControlOperation
): Promise<boolean> {
this.logger.info(
`Executing power control operation: ${operation} on session ${sessionId}`
);
const operationMap: Record<PowerControlOperation, number> = {
'power-down': 0x00,
'power-up': 0x01,
'power-cycle': 0x02,
'hard-reset': 0x03,
'pulse-diagnostic': 0x04,
'soft-shutdown': 0x05,
};
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_CHASSIS,
cmd: IPMIProtocol.CMD_CHASSIS_CONTROL,
data: Buffer.from([operationMap[operation]]),
};
try {
const response = await this.sendIPMICommand(sessionId, command);
if (response.completionCode === 0x00) {
this.logger.info(
`Power control operation ${operation} completed successfully`
);
this.emit('powerControlComplete', {
sessionId,
operation,
success: true,
});
// Update power state cache
const connectionState = this.connections.get(sessionId);
if (connectionState) {
connectionState.powerState = operation.includes('up') ? 'on' : 'off';
}
return true;
} else {
throw new Error(
`Power control failed with completion code: 0x${response.completionCode.toString(16)}`
);
}
} catch (error) {
this.logger.error(`Power control operation ${operation} failed:`, error);
this.emit('powerControlComplete', {
sessionId,
operation,
success: false,
error,
});
throw error;
}
}
/**
* Read sensor data
*/
async readSensors(sessionId: string): Promise<SensorReading[]> {
this.logger.debug(`Reading sensors for session ${sessionId}`);
const sensors: SensorReading[] = [];
try {
// Get SDR Repository Info
const sdrInfo = await this.getSDRRepositoryInfo(sessionId);
// Iterate through all SDRs
let recordId = 0x0000;
while (recordId !== 0xffff) {
const sdrRecord = await this.getSDRRecord(sessionId, recordId);
if (sdrRecord.sensorNumber !== undefined) {
const reading = await this.getSensorReading(
sessionId,
sdrRecord.sensorNumber
);
if (reading) {
sensors.push(reading);
}
}
recordId = sdrRecord.nextRecordId;
}
// Cache sensor readings
const connectionState = this.connections.get(sessionId);
if (connectionState) {
connectionState.sensorCache = new Map(
sensors.map((s) => [s.sensorNumber, s])
);
connectionState.hardwareHealth.sensors = sensors;
}
this.logger.debug(
`Read ${sensors.length} sensors for session ${sessionId}`
);
return sensors;
} catch (error) {
this.logger.error(
`Failed to read sensors for session ${sessionId}:`,
error
);
throw error;
}
}
/**
* Virtual media operations
*/
async mountVirtualMedia(
sessionId: string,
mediaType: 'floppy' | 'cdrom' | 'hdd' | 'usb',
imagePath: string,
writeProtected: boolean = true
): Promise<boolean> {
this.logger.info(
`Mounting virtual media: ${mediaType} from ${imagePath} on session ${sessionId}`
);
const connectionState = this.connections.get(sessionId);
if (!connectionState) {
throw new Error(`Session ${sessionId} not found`);
}
try {
// Vendor-specific implementation
switch (connectionState.connectionOptions.vendor) {
case 'dell':
return await this.mountDellVirtualMedia(
sessionId,
mediaType,
imagePath,
writeProtected
);
case 'hp':
return await this.mountHPVirtualMedia(
sessionId,
mediaType,
imagePath,
writeProtected
);
case 'ibm':
return await this.mountIBMVirtualMedia(
sessionId,
mediaType,
imagePath,
writeProtected
);
default:
return await this.mountGenericVirtualMedia(
sessionId,
mediaType,
imagePath,
writeProtected
);
}
} catch (error) {
this.logger.error(`Failed to mount virtual media:`, error);
throw error;
}
}
/**
* Firmware update capabilities
*/
async updateFirmware(
sessionId: string,
firmwarePath: string,
component: 'bmc' | 'bios' | 'cpld' = 'bmc'
): Promise<boolean> {
this.logger.info(
`Starting firmware update for ${component} from ${firmwarePath} on session ${sessionId}`
);
const connectionState = this.connections.get(sessionId);
if (!connectionState) {
throw new Error(`Session ${sessionId} not found`);
}
try {
// Start firmware update process
this.emit('firmwareUpdateStarted', {
sessionId,
component,
firmwarePath,
});
// Implementation varies by vendor
switch (connectionState.connectionOptions.vendor) {
case 'dell':
return await this.updateDellFirmware(
sessionId,
firmwarePath,
component
);
case 'hp':
return await this.updateHPFirmware(
sessionId,
firmwarePath,
component
);
case 'ibm':
return await this.updateIBMFirmware(
sessionId,
firmwarePath,
component
);
default:
return await this.updateGenericFirmware(
sessionId,
firmwarePath,
component
);
}
} catch (error) {
this.logger.error(`Firmware update failed:`, error);
this.emit('firmwareUpdateFailed', { sessionId, component, error });
throw error;
}
}
/**
* DCMI Extensions Support
*/
async getDCMIPowerReading(sessionId: string): Promise<{
currentWatts: number;
minimumWatts: number;
maximumWatts: number;
averageWatts: number;
}> {
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_DCMI,
cmd: 0x02, // Get Power Reading
data: Buffer.from([0xdc, 0x01, 0x00, 0x00]), // DCMI Group Extension ID
};
const response = await this.sendIPMICommand(sessionId, command);
if (response.completionCode === 0x00 && response.data.length >= 16) {
return {
currentWatts: response.data.readUInt16LE(6),
minimumWatts: response.data.readUInt16LE(8),
maximumWatts: response.data.readUInt16LE(10),
averageWatts: response.data.readUInt16LE(12),
};
}
throw new Error('Failed to get DCMI power reading');
}
/**
* Hardware monitoring and alerting
*/
private startHardwareMonitoring(sessionId: string): void {
const connectionState = this.connections.get(sessionId);
if (!connectionState) return;
const interval = setInterval(async () => {
try {
// Update sensor readings
const sensors = await this.readSensors(sessionId);
// Update power state
const chassisStatus = await this.getChassisStatus(sessionId);
// Update DCMI power readings if enabled
let powerReading = null;
if (connectionState.connectionOptions.dcmi?.enabled) {
powerReading = await this.getDCMIPowerReading(sessionId);
}
// Check for alerts
this.checkHardwareAlerts(
sessionId,
sensors,
chassisStatus,
powerReading
);
// Emit monitoring data
this.emit('hardwareMonitoring', {
sessionId,
timestamp: new Date(),
sensors,
chassisStatus,
powerReading,
systemHealth: this.calculateSystemHealth(sensors),
});
} catch (error) {
this.logger.error(
`Hardware monitoring failed for session ${sessionId}:`,
error
);
}
}, 30000); // Monitor every 30 seconds
connectionState.monitoringInterval = interval;
}
/**
* Send input to SOL console
*/
async sendSOLInput(sessionId: string, input: string): Promise<void> {
const connectionState = this.connections.get(sessionId);
if (!connectionState?.solSession?.active) {
throw new Error('SOL session not active');
}
const inputBuffer = Buffer.from(input, 'utf8');
// Build SOL packet
const packet = this.buildSOLPacket(sessionId, inputBuffer);
connectionState.socket?.send(packet, 0, packet.length, (error) => {
if (error) {
this.logger.error(`Failed to send SOL input:`, error);
throw error;
}
});
this.logger.debug(
`Sent ${input.length} characters to SOL console ${sessionId}`
);
}
/**
* Close IPMI session
*/
async closeIPMISession(sessionId: string): Promise<void> {
this.logger.info(`Closing IPMI session ${sessionId}`);
const connectionState = this.connections.get(sessionId);
if (!connectionState) {
return; // Session already closed
}
try {
// Deactivate SOL if active
if (connectionState.solSession?.active) {
await this.deactivateSOLPayload(sessionId);
}
// Stop monitoring
if (connectionState.monitoringInterval) {
clearInterval(connectionState.monitoringInterval);
}
// Close IPMI session
const session = this.sessions.get(sessionId);
const ipmiSession = this.ipmiSessions.get(sessionId);
if (session && (session.status === 'running' || ipmiSession?.isActive)) {
await this.sendCloseSessionCommand(sessionId);
}
// Close socket
connectionState.socket?.close();
// Clear session data
this.connections.delete(sessionId);
this.sessions.delete(sessionId);
this.emit('sessionClosed', { sessionId });
this.logger.info(`IPMI session ${sessionId} closed successfully`);
} catch (error) {
this.logger.error(`Error closing IPMI session ${sessionId}:`, error);
// Clean up anyway
this.connections.delete(sessionId);
this.sessions.delete(sessionId);
throw error;
}
}
// Private helper methods for protocol implementation
private buildIPMIPacket(
message: IPMIMessage,
session?: IPMISessionState
): Buffer {
// Implementation would build proper IPMI packet with RMCP header, authentication, etc.
// This is a complex binary protocol implementation
const buffer = Buffer.alloc(1024);
let offset = 0;
// RMCP Header
buffer[offset++] = IPMIProtocol.RMCP_VERSION_1;
buffer[offset++] = 0x00; // Reserved
buffer[offset++] = IPMIProtocol.RMCP_SEQUENCE;
buffer[offset++] = IPMIProtocol.RMCP_CLASS_IPMI;
// Session authentication and message construction would continue here
// This is simplified for demonstration
return buffer.slice(0, offset);
}
private buildSOLPacket(sessionId: string, data: Buffer): Buffer {
// Build SOL payload packet
const buffer = Buffer.alloc(data.length + 20); // Header + data
let offset = 0;
// SOL packet construction
buffer[offset++] = 0x00; // Packet sequence number
buffer[offset++] = 0x00; // Acknowledgment sequence number
buffer[offset++] = 0x00; // Accepted character count
buffer[offset++] = 0x00; // Status
// Copy data
data.copy(buffer, offset);
return buffer.slice(0, offset + data.length);
}
private handleIncomingMessage(
sessionId: string,
message: Buffer,
rinfo: any
): void {
// Parse and handle incoming IPMI messages
// This would include SOL data, sensor readings, responses, etc.
try {
const messageType = this.parseMessageType(message);
switch (messageType) {
case 'sol_data':
this.handleSOLData(sessionId, message);
break;
case 'ipmi_response':
this.handleIPMIResponse(sessionId, message);
break;
case 'async_event':
this.handleAsyncEvent(sessionId, message);
break;
default:
this.logger.debug(`Unknown message type received: ${messageType}`);
}
} catch (error) {
this.logger.error(`Error handling incoming message:`, error);
}
}
private handleSOLData(sessionId: string, message: Buffer): void {
// Extract SOL console data from packet
const solData = this.extractSOLData(message);
if (solData.length > 0) {
const output = solData.toString('utf8');
const connectionState = this.connections.get(sessionId);
if (connectionState) {
connectionState.outputBuffer += output;
connectionState.lastActivity = new Date();
}
const consoleOutput: ConsoleOutput = {
data: output,
timestamp: new Date(),
type: 'stdout',
sessionId,
};
this.emit('output', consoleOutput);
this.logger.debug(
`Received ${output.length} characters from SOL console ${sessionId}`
);
}
}
// Additional helper methods would be implemented here for:
// - parseMessageType
// - extractSOLData
// - handleIPMIResponse
// - handleAsyncEvent
// - getAuthenticationCapabilities
// - getSessionChallenge
// - activateSession
// - setSessionPrivilegeLevel
// - sendOpenSessionRequest
// - sendRAKPMessage1
// - sendRAKPMessage3
// - configureSOL
// - activateSOLPayload
// - deactivateSOLPayload
// - getSDRRepositoryInfo
// - getSDRRecord
// - getSensorReading
// - getChassisStatus
// - checkHardwareAlerts
// - calculateSystemHealth
// - generateSessionKeys
// - Vendor-specific methods (Dell, HP, IBM implementations)
private parseMessageType(message: Buffer): string {
// Simplified message type detection
if (message.length > 4 && message[3] === IPMIProtocol.RMCP_CLASS_IPMI) {
return 'ipmi_response';
}
return 'unknown';
}
private extractSOLData(message: Buffer): Buffer {
// Simplified SOL data extraction
return message.slice(4); // Skip header
}
private handleIPMIResponse(sessionId: string, message: Buffer): void {
// Parse IPMI response and emit event
const response: IPMIResponse = {
completionCode: message[6] || 0,
data: message.slice(7),
netFn: (message[5] >> 2) & 0x3f,
cmd: message[4],
sequence: message[2],
};
this.emit('ipmiResponse', response);
}
private handleAsyncEvent(sessionId: string, message: Buffer): void {
// Handle asynchronous events like sensor alerts
this.emit('asyncEvent', { sessionId, message });
}
// Placeholder implementations for complex methods
private async getAuthenticationCapabilities(
sessionId: string,
options: IPMIConnectionOptions
): Promise<any> {
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_APPLICATION,
cmd: IPMIProtocol.CMD_GET_CHANNEL_AUTH_CAP,
data: Buffer.from([0x0e, 0x04]), // Channel number, privilege level
};
return await this.sendIPMICommand(sessionId, command);
}
private async getSessionChallenge(
sessionId: string,
options: IPMIConnectionOptions
): Promise<Buffer> {
// Implementation would handle session challenge
return randomBytes(16);
}
private async activateSession(
sessionId: string,
options: IPMIConnectionOptions,
challenge: Buffer
): Promise<any> {
// Implementation would activate IPMI 1.5 session
return { sessionId: Math.random() * 0xffffffff };
}
private async setSessionPrivilegeLevel(
sessionId: string,
options: IPMIConnectionOptions,
sessionInfo: any
): Promise<void> {
// Implementation would set privilege level
}
private async sendOpenSessionRequest(
sessionId: string,
options: IPMIConnectionOptions
): Promise<any> {
// RMCP+ Open Session Request implementation
return {};
}
private async sendRAKPMessage1(
sessionId: string,
options: IPMIConnectionOptions,
openResponse: any
): Promise<any> {
// RAKP Message 1 implementation
return {};
}
private async sendRAKPMessage3(
sessionId: string,
options: IPMIConnectionOptions,
rakp1Response: any
): Promise<void> {
// RAKP Message 3 implementation
}
private async setSessionPrivilegeLevelRMCP(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
// Set privilege level for RMCP+ session
}
private generateSessionKeys(
sessionId: string,
options: IPMIConnectionOptions
): void {
// Generate session integrity and confidentiality keys
const session = this.sessions.get(sessionId);
const ipmiSession = this.ipmiSessions.get(sessionId);
if (session && ipmiSession) {
ipmiSession.sik = randomBytes(20); // SHA-1 based
ipmiSession.k1 = randomBytes(20);
ipmiSession.k2 = randomBytes(20);
// Session keys are stored in ipmiSession only, not in the ConsoleSession
}
}
private async configureSOL(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
// Configure Serial over LAN parameters
}
private async activateSOLPayload(
sessionId: string,
options: IPMIConnectionOptions
): Promise<void> {
// Activate SOL payload
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_APPLICATION,
cmd: IPMIProtocol.CMD_ACTIVATE_PAYLOAD,
data: Buffer.from([0x01, 0x01, 0x00, 0x00, 0x00, 0x00]), // SOL payload
};
await this.sendIPMICommand(sessionId, command);
}
private async deactivateSOLPayload(sessionId: string): Promise<void> {
// Deactivate SOL payload
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_APPLICATION,
cmd: IPMIProtocol.CMD_DEACTIVATE_PAYLOAD,
data: Buffer.from([0x01, 0x01, 0x00, 0x00, 0x00, 0x00]),
};
await this.sendIPMICommand(sessionId, command);
}
private async getSDRRepositoryInfo(sessionId: string): Promise<any> {
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_STORAGE,
cmd: IPMIProtocol.CMD_GET_SDR_REPOSITORY_INFO,
data: Buffer.alloc(0),
};
return await this.sendIPMICommand(sessionId, command);
}
private async getSDRRecord(
sessionId: string,
recordId: number
): Promise<any> {
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_STORAGE,
cmd: IPMIProtocol.CMD_GET_SDR,
data: Buffer.from([
0x00,
0x00, // Reservation ID
recordId & 0xff,
(recordId >> 8) & 0xff, // Record ID
0x00, // Offset
0xff, // Bytes to read
]),
};
return await this.sendIPMICommand(sessionId, command);
}
private async getSensorReading(
sessionId: string,
sensorNumber: number
): Promise<SensorReading | null> {
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_SENSOR_EVENT,
cmd: IPMIProtocol.CMD_GET_SENSOR_READING,
data: Buffer.from([sensorNumber]),
};
try {
const response = await this.sendIPMICommand(sessionId, command);
if (response.completionCode === 0x00 && response.data.length >= 3) {
return {
sensorNumber,
sensorName: `Sensor_${sensorNumber}`,
sensorType: 'generic',
entityId: 0,
entityInstance: 0,
reading: response.data[0],
unit: 'raw',
status: response.data[1] & 0x20 ? 'not-readable' : 'ok',
thresholds: {},
timestamp: new Date(),
};
}
} catch (error) {
this.logger.debug(`Failed to read sensor ${sensorNumber}:`, error);
}
return null;
}
private async getChassisStatus(sessionId: string): Promise<any> {
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_CHASSIS,
cmd: IPMIProtocol.CMD_GET_CHASSIS_STATUS,
data: Buffer.alloc(0),
};
return await this.sendIPMICommand(sessionId, command);
}
private checkHardwareAlerts(
sessionId: string,
sensors: SensorReading[],
chassisStatus: any,
powerReading: any
): void {
// Check for critical sensor readings and emit alerts
sensors.forEach((sensor) => {
if (sensor.status === 'critical') {
this.emit('hardwareAlert', {
sessionId,
type: 'sensor_critical',
sensor,
timestamp: new Date(),
severity: 'critical',
});
}
});
}
private calculateSystemHealth(
sensors: SensorReading[]
): 'ok' | 'warning' | 'critical' {
const criticalSensors = sensors.filter((s) => s.status === 'critical');
const warningSensors = sensors.filter((s) => s.status === 'warning');
if (criticalSensors.length > 0) return 'critical';
if (warningSensors.length > 0) return 'warning';
return 'ok';
}
private async sendCloseSessionCommand(sessionId: string): Promise<void> {
const session = this.sessions.get(sessionId);
const ipmiSession = this.ipmiSessions.get(sessionId);
if (!session || !ipmiSession) return;
const command: IPMIMessage = {
netFn: IPMIProtocol.NETFN_APPLICATION,
cmd: IPMIProtocol.CMD_CLOSE_SESSION,
data: Buffer.from([
ipmiSession.sessionId & 0xff,
(ipmiSession.sessionId >> 8) & 0xff,
(ipmiSession.sessionId >> 16) & 0xff,
(ipmiSession.sessionId >> 24) & 0xff,
]),
};
await this.sendIPMICommand(sessionId, command);
}
/**
* IPMI-specific helper methods for BaseProtocol integration
*/
private generateSessionId(): string {
return `ipmi-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private async checkIPMIToolAvailability(): Promise<void> {
try {
await new Promise<void>((resolve, reject) => {
const process = spawn('ipmitool', ['-V'], { stdio: 'pipe' });
process.on('close', (code) => {
if (code === 0) {
this.healthStatus.dependencies.ipmitool = {
available: true,
version: 'detected',
};
resolve();
} else {
reject(new Error('ipmitool not available'));
}
});
process.on('error', () => reject(new Error('ipmitool not found')));
setTimeout(() => {
process.kill();
reject(new Error('ipmitool check timeout'));
}, 3000);
});
} catch (error) {
this.logger.warn(
'ipmitool not available, some functionality may be limited'
);
}
}
private buildIPMIToolCommand(options: IPMIConnectionOptions): string[] {
const args: string[] = [];
// Host connection
args.push('-H', options.host);
// Port if not default
if (options.port && options.port !== 623) {
args.push('-p', options.port.toString());
}
// Username
args.push('-U', options.username);
// Password
args.push('-P', options.password);
// Interface type
args.push('-I', options.interface || 'lanplus');
// Privilege level
if (options.privilegeLevel) {
args.push('-L', options.privilegeLevel.toUpperCase());
}
// Cipher suite for IPMI 2.0
if (options.cipherSuite !== undefined) {
args.push('-C', options.cipherSuite.toString());
}
// BMC key for enhanced security
if (options.kg) {
args.push('-k', options.kg);
}
// Retry options
if (options.maxRetries) {
args.push('-R', options.maxRetries.toString());
}
// Timeout
if (options.sessionTimeout) {
args.push('-N', (options.sessionTimeout / 1000).toString());
}
return args;
}
/**
* Enhanced createSessionWithTypeDetection wrapper for IPMI-specific session creation
*/
protected async doCreateSession(
sessionId: string,
options: SessionOptions,
sessionState: SessionState
): Promise<ConsoleSession> {
const ipmiOptions =
options.ipmiOptions ||
({
host: options.sshOptions?.host || options.wmiHost || 'localhost',
username:
options.sshOptions?.username || options.wmiUsername || 'admin',
password: options.sshOptions?.password || options.wmiPassword || '',
interface: 'lanplus',
} as IPMIConnectionOptions);
const command = 'ipmitool';
const args = this.buildIPMIToolCommand(ipmiOptions);
// Add SOL activate command if enabled
if (ipmiOptions.sol?.enabled) {
args.push('sol', 'activate');
} else {
// Default to shell for interactive session
args.push('shell');
}
const spawnOptions: import('child_process').SpawnOptions = {
cwd: options.cwd || process.cwd(),
env: { ...process.env, ...options.environment } as NodeJS.ProcessEnv,
stdio: ['pipe', 'pipe', 'pipe'],
};
this.logger.debug(`Starting IPMI process: ${command} ${args.join(' ')}`);
const childProcess = spawn(command, args, spawnOptions);
this.ipmiProcesses.set(sessionId, childProcess);
// Handle process events
childProcess.on('spawn', () => {
this.logger.debug(
`IPMI process spawned for session ${sessionId} (PID: ${childProcess.pid})`
);
});
childProcess.on('error', (error) => {
this.logger.error(`IPMI process error for session ${sessionId}:`, error);
this.emit('processError', { sessionId, error });
});
childProcess.on('exit', (code, signal) => {
this.logger.debug(
`IPMI process exited for session ${sessionId}: code=${code}, signal=${signal}`
);
this.ipmiProcesses.delete(sessionId);
});
// Handle stdout/stderr
if (childProcess.stdout) {
(childProcess.stdout as import('stream').Readable).on(
'data',
(data: Buffer) => {
const output: ConsoleOutput = {
sessionId,
data: data.toString(),
timestamp: new Date(),
stream: 'stdout',
type: 'stdout' as const,
};
const outputBuffer = this.outputBuffers.get(sessionId) || [];
outputBuffer.push(output);
this.outputBuffers.set(sessionId, outputBuffer);
this.emit('output', output);
}
);
}
if (childProcess.stderr) {
(childProcess.stderr as import('stream').Readable).on(
'data',
(data: Buffer) => {
const output: ConsoleOutput = {
sessionId,
data: data.toString(),
timestamp: new Date(),
stream: 'stderr',
type: 'stderr' as const,
};
const outputBuffer = this.outputBuffers.get(sessionId) || [];
outputBuffer.push(output);
this.outputBuffers.set(sessionId, outputBuffer);
this.emit('output', output);
}
);
}
// Create and return the ConsoleSession
const consoleSession: ConsoleSession = {
id: sessionId,
command: command,
args: args,
cwd: options.cwd || process.cwd(),
env: Object.fromEntries(
Object.entries({ ...process.env, ...options.environment }).filter(
([_, value]) => typeof value === 'string'
)
) as Record<string, string>,
createdAt: new Date(),
pid: childProcess.pid ?? undefined,
status: 'running',
type: this.type,
streaming: options.streaming ?? false,
lastActivity: new Date(),
executionState: 'idle',
activeCommands: new Map(),
ipmiOptions: ipmiOptions,
};
this.sessions.set(sessionId, consoleSession);
this.logger.info(`IPMI session ${sessionId} created successfully`);
this.emit('session-created', {
sessionId,
type: this.type,
session: consoleSession,
});
return consoleSession;
}
// Compatibility properties for legacy ProtocolFactory interface
public get supportedAuthMethods(): string[] {
return this.capabilities.supportedAuthMethods || [];
}
public get maxConcurrentSessions(): number {
return this.capabilities.maxConcurrentSessions || 5;
}
public get defaultTimeout(): number {
return this.capabilities.defaultTimeout || 30000;
}
public async getOutput(
sessionId: string,
since?: Date
): Promise<ConsoleOutput[]> {
const outputs = this.outputBuffers.get(sessionId) || [];
const filteredOutputs = since
? outputs.filter((output) => output.timestamp > since)
: outputs;
return filteredOutputs;
}
public async getHealthStatus(): Promise<ProtocolHealthStatus> {
this.healthStatus.lastChecked = new Date();
this.healthStatus.metrics.activeSessions = this.sessions.size;
this.healthStatus.isHealthy = this.isInitialized;
return { ...this.healthStatus };
}
// Vendor-specific implementations (simplified placeholders)
private async mountDellVirtualMedia(
sessionId: string,
mediaType: string,
imagePath: string,
writeProtected: boolean
): Promise<boolean> {
this.logger.info(`Mounting Dell iDRAC virtual media: ${mediaType}`);
// Dell iDRAC-specific implementation
return true;
}
private async mountHPVirtualMedia(
sessionId: string,
mediaType: string,
imagePath: string,
writeProtected: boolean
): Promise<boolean> {
this.logger.info(`Mounting HP iLO virtual media: ${mediaType}`);
// HP iLO-specific implementation
return true;
}
private async mountIBMVirtualMedia(
sessionId: string,
mediaType: string,
imagePath: string,
writeProtected: boolean
): Promise<boolean> {
this.logger.info(`Mounting IBM IMM virtual media: ${mediaType}`);
// IBM IMM-specific implementation
return true;
}
private async mountGenericVirtualMedia(
sessionId: string,
mediaType: string,
imagePath: string,
writeProtected: boolean
): Promise<boolean> {
this.logger.info(`Mounting generic IPMI virtual media: ${mediaType}`);
// Generic IPMI virtual media implementation
return true;
}
private async updateDellFirmware(
sessionId: string,
firmwarePath: string,
component: string
): Promise<boolean> {
this.logger.info(`Updating Dell firmware: ${component}`);
// Dell firmware update implementation
return true;
}
private async updateHPFirmware(
sessionId: string,
firmwarePath: string,
component: string
): Promise<boolean> {
this.logger.info(`Updating HP firmware: ${component}`);
// HP firmware update implementation
return true;
}
private async updateIBMFirmware(
sessionId: string,
firmwarePath: string,
component: string
): Promise<boolean> {
this.logger.info(`Updating IBM firmware: ${component}`);
// IBM firmware update implementation
return true;
}
private async updateGenericFirmware(
sessionId: string,
firmwarePath: string,
component: string
): Promise<boolean> {
this.logger.info(`Updating generic IPMI firmware: ${component}`);
// Generic IPMI firmware update implementation
return true;
}
// BaseProtocol required methods
async initialize(): Promise<void> {
if (this.isInitialized) return;
this.logger.info('Initializing IPMI Protocol');
try {
// Check for ipmitool availability
await this.checkIPMIToolAvailability();
this.isInitialized = true;
this.logger.info('IPMI Protocol initialized successfully');
} catch (error) {
this.logger.error('Failed to initialize IPMI Protocol:', error);
throw error;
}
}
async createSession(options: SessionOptions): Promise<ConsoleSession> {
if (!this.isInitialized) {
await this.initialize();
}
const sessionId = this.generateSessionId();
if (!options.ipmiOptions) {
// Use ipmitool with basic options if IPMI options not provided
const command = 'ipmitool';
const args = this.buildIPMIToolCommand({
host: options.sshOptions?.host || options.wmiHost || 'localhost',
username:
options.sshOptions?.username || options.wmiUsername || 'admin',
password: options.sshOptions?.password || options.wmiPassword || '',
interface: 'lanplus',
} as IPMIConnectionOptions);
return await this.createSessionWithTypeDetection(sessionId, {
...options,
command,
args,
});
}
// Call the existing createIPMISession method for complex IPMI operations
const session = await this.createIPMISession(
sessionId,
options.ipmiOptions
);
// Store session in BaseProtocol's session map
this.sessions.set(sessionId, session);
return session;
}
async executeCommand(
sessionId: string,
command: string,
args?: string[]
): Promise<void> {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error(`Session ${sessionId} not found`);
}
const process = this.ipmiProcesses.get(sessionId);
if (process && !process.killed) {
// Send commands to IPMI process stdin if available
if (process.stdin && process.stdin.writable) {
const commandLine = args ? `${command} ${args.join(' ')}` : command;
process.stdin.write(`${commandLine}\n`);
}
}
// IPMI commands are also handled through specific methods
this.emit('commandExecuted', {
sessionId,
command,
args,
timestamp: new Date(),
});
}
async sendInput(sessionId: string, input: string): Promise<void> {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error(`Session ${sessionId} not found`);
}
// Try to send via SOL if active
const connection = this.connections.get(sessionId);
if (connection?.solSession?.active) {
await this.sendSOLInput(sessionId, input);
}
// Also send to process stdin if available
const process = this.ipmiProcesses.get(sessionId);
if (process && !process.killed && process.stdin && process.stdin.writable) {
process.stdin.write(input);
}
}
async closeSession(sessionId: string): Promise<void> {
const session = this.sessions.get(sessionId);
if (!session) {
this.logger.warn(`Attempted to close non-existent session: ${sessionId}`);
return;
}
try {
// Close IPMI-specific resources
await this.closeIPMISession(sessionId);
// Terminate IPMI process
const process = this.ipmiProcesses.get(sessionId);
if (process && !process.killed) {
process.kill('SIGTERM');
// Force kill after 5 seconds
setTimeout(() => {
if (!process.killed) {
process.kill('SIGKILL');
}
}, 5000);
}
// Clean up maps
this.ipmiProcesses.delete(sessionId);
this.sessions.delete(sessionId);
this.outputBuffers.delete(sessionId);
session.status = 'terminated';
this.logger.info(`IPMI session closed: ${sessionId}`);
this.emit('sessionClosed', sessionId);
} catch (error) {
this.logger.error(`Error closing IPMI session ${sessionId}:`, error);
throw error;
}
}
async dispose(): Promise<void> {
this.logger.info('Disposing IPMI Protocol');
// Close all active sessions
const sessionIds = Array.from(this.sessions.keys());
await Promise.all(sessionIds.map((id) => this.closeSession(id)));
// Close all IPMI connections
const connectionIds = Array.from(this.connections.keys());
await Promise.all(connectionIds.map((id) => this.closeIPMISession(id)));
this.removeAllListeners();
this.isInitialized = false;
this.logger.info('IPMI Protocol disposed');
}
}
// Connection State Interface
interface IPMIConnectionState {
sessionId: string;
socket: Socket | null;
connectionOptions: IPMIConnectionOptions;
isConnected: boolean;
lastActivity: Date;
sequenceNumber: number;
outputBuffer: string;
solSession: {
active: boolean;
payloadType: number;
port: number;
encryption: boolean;
authentication: boolean;
} | null;
sensorCache: Map<number, SensorReading>;
monitoringInterval: NodeJS.Timeout | null;
powerState: string;
hardwareHealth: HardwareMonitoringData;
}