/**
* 端口会话管理实现
*/
import { logger } from '@/utils/logger';
import { IPortSession, SessionContext, SessionOptions, SerialError, IURCDetector } from '@/types';
import { ErrorCode } from '@/utils/error-codes';
import { v4 as uuidv4 } from 'uuid';
/**
* 端口会话管理实现类
*/
export class PortSession implements IPortSession {
private sessions: Map<string, SessionContext> = new Map();
private portSessions: Map<string, string> = new Map(); // port -> sessionId
private urcDetector?: IURCDetector;
private cleanupInterval: NodeJS.Timeout;
private readonly defaultSessionTimeout: number = 60000; // 60秒默认超时
constructor(urcDetector?: IURCDetector) {
this.urcDetector = urcDetector;
// 定期清理过期会话
this.cleanupInterval = setInterval(() => {
this.cleanupExpired();
}, 10000); // 每10秒清理一次
logger.debug('PortSession initialized');
}
/**
* 创建会话
*/
createSession(port: string, options?: SessionOptions): SessionContext {
try {
// 检查端口是否已有活跃会话
const existingSessionId = this.portSessions.get(port);
if (existingSessionId) {
const existingSession = this.sessions.get(existingSessionId);
if (existingSession) {
logger.warn(`Port already has active session: ${port}`, {
existingSessionId,
createdAt: new Date(existingSession.createdAt).toISOString()
});
// 可以选择结束现有会话或抛出错误
// 这里选择结束现有会话
this.endSession(existingSessionId);
}
}
const sessionId = uuidv4();
const now = Date.now();
const session: SessionContext = {
sessionId,
port,
createdAt: now,
buffer: Buffer.alloc(0),
urcBuffer: [],
timeoutMs: options?.timeoutMs || this.defaultSessionTimeout,
filterURC: options?.filterURC !== false // 默认为true
};
// 保存会话
this.sessions.set(sessionId, session);
this.portSessions.set(port, sessionId);
logger.info(`Session created: ${sessionId}`, {
port,
timeoutMs: session.timeoutMs,
filterURC: session.filterURC
});
return session;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error(`Failed to create session for port: ${port}`, errorObj);
throw errorObj;
}
}
/**
* 结束会话
*/
endSession(sessionId: string): void {
try {
const session = this.sessions.get(sessionId);
if (!session) {
logger.warn(`Session not found: ${sessionId}`);
return;
}
logger.info(`Ending session: ${sessionId}`, {
port: session.port,
dataReceived: session.buffer.length,
urcDetected: session.urcBuffer.length
});
// 清理端口映射
this.portSessions.delete(session.port);
// 删除会话
this.sessions.delete(sessionId);
logger.debug(`Session ended: ${sessionId}`);
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error(`Failed to end session: ${sessionId}`, errorObj);
}
}
/**
* 获取会话
*/
getSession(sessionId: string): SessionContext | null {
return this.sessions.get(sessionId) || null;
}
/**
* 获取端口的活跃会话
*/
getActiveSession(port: string): string | null {
return this.portSessions.get(port) || null;
}
/**
* 验证数据是否属于会话
*/
isSessionData(data: Buffer, sessionId: string): boolean {
try {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
// TODO: 实现更复杂的数据验证逻辑
// 目前简单地认为所有数据都属于会话
// 可以基于时间戳、序列号、特殊标记等进行验证
return true;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error(`Failed to validate session data: ${sessionId}`, errorObj);
return false;
}
}
/**
* 提取URC和数据
*/
extractURC(data: Buffer, port: string): { urc: string[]; data: Buffer } {
try {
const sessionId = this.portSessions.get(port);
const session = sessionId ? this.sessions.get(sessionId) : undefined;
if (!session || !session.filterURC) {
// 没有会话或不启用URC过滤,直接返回
return {
urc: [],
data
};
}
// 如果有URC检测器,使用它进行检测
if (this.urcDetector) {
const result = this.urcDetector.extract(data, port);
// 记录检测到的URC
if (result.urc.length > 0) {
session.urcBuffer.push(...result.urc);
logger.debug(`URC detected in session: ${sessionId}`, {
port,
urcCount: result.urc.length,
urcList: result.urc
});
}
return result;
}
// 简单的行分割处理(备用方案)
const text = data.toString('utf8');
const lines = text.split(/\r?\n/);
const urcLines: string[] = [];
const dataLines: string[] = [];
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.length === 0) {
continue;
}
// 简单的URC检测规则(以+开头的行)
if (trimmedLine.startsWith('+') || trimmedLine.startsWith('RING')) {
urcLines.push(trimmedLine);
if (session) {
session.urcBuffer.push(trimmedLine);
}
} else {
dataLines.push(line);
}
}
const result = {
urc: urcLines,
data: dataLines.join('\r\n') ? Buffer.from(dataLines.join('\r\n'), 'utf8') : Buffer.alloc(0)
};
logger.debug(`URC extraction completed: ${port}`, {
sessionId,
urcCount: result.urc.length,
dataSize: result.data.length
});
return result;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error(`Failed to extract URC from port: ${port}`, errorObj);
return {
urc: [],
data
};
}
}
/**
* 向会话添加数据
*/
addSessionData(sessionId: string, data: Buffer): void {
try {
const session = this.sessions.get(sessionId);
if (!session) {
logger.warn(`Session not found when adding data: ${sessionId}`);
return;
}
// 提取URC和数据
const { urc, data: cleanData } = this.extractURC(data, session.port);
// 添加非URC数据到缓冲区
if (cleanData.length > 0) {
session.buffer = Buffer.concat([session.buffer, cleanData]);
}
// 记录URC
if (urc.length > 0) {
session.urcBuffer.push(...urc);
}
logger.debug(`Data added to session: ${sessionId}`, {
port: session.port,
dataSize: cleanData.length,
urcCount: urc.length,
totalBufferSize: session.buffer.length
});
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error(`Failed to add data to session: ${sessionId}`, errorObj);
}
}
/**
* 获取会话数据
*/
getSessionData(sessionId: string): Buffer {
const session = this.sessions.get(sessionId);
if (!session) {
return Buffer.alloc(0);
}
return session.buffer;
}
/**
* 获取会话URC
*/
getSessionURC(sessionId: string): string[] {
const session = this.sessions.get(sessionId);
if (!session) {
return [];
}
return [...session.urcBuffer];
}
/**
* 清空会话数据
*/
clearSessionData(sessionId: string): void {
const session = this.sessions.get(sessionId);
if (!session) {
return;
}
session.buffer = Buffer.alloc(0);
session.urcBuffer = [];
logger.debug(`Session data cleared: ${sessionId}`, {
port: session.port
});
}
/**
* 检查会话是否过期
*/
isSessionExpired(sessionId: string): boolean {
const session = this.sessions.get(sessionId);
if (!session) {
return true;
}
const now = Date.now();
const age = now - session.createdAt;
return session.timeoutMs ? age > session.timeoutMs : false;
}
/**
* 获取会话统计信息
*/
getSessionStats(sessionId: string): any {
const session = this.sessions.get(sessionId);
if (!session) {
return null;
}
const now = Date.now();
const age = now - session.createdAt;
return {
sessionId,
port: session.port,
createdAt: session.createdAt,
age,
timeoutMs: session.timeoutMs,
isExpired: session.timeoutMs ? age > session.timeoutMs : false,
bufferSize: session.buffer.length,
urcCount: session.urcBuffer.length,
filterURC: session.filterURC
};
}
/**
* 获取所有会话统计
*/
getAllSessionStats(): any[] {
return Array.from(this.sessions.keys()).map(sessionId =>
this.getSessionStats(sessionId)
);
}
/**
* 清理过期会话
*/
cleanupExpired(): void {
const expiredSessions: string[] = [];
for (const [sessionId, session] of this.sessions) {
if (this.isSessionExpired(sessionId)) {
expiredSessions.push(sessionId);
}
}
for (const sessionId of expiredSessions) {
logger.info(`Cleaning up expired session: ${sessionId}`);
this.endSession(sessionId);
}
if (expiredSessions.length > 0) {
logger.debug(`Cleaned up ${expiredSessions.length} expired sessions`);
}
}
/**
* 结束端口的所有会话
*/
endPortSessions(port: string): void {
const sessionId = this.portSessions.get(port);
if (sessionId) {
this.endSession(sessionId);
}
}
/**
* 获取活跃会话数量
*/
getActiveSessionCount(): number {
return this.sessions.size;
}
/**
* 获取端口活跃会话数量
*/
getActivePortCount(): number {
return this.portSessions.size;
}
/**
* 设置URC检测器
*/
setURCDetector(urcDetector: IURCDetector): void {
this.urcDetector = urcDetector;
logger.debug('URC detector set for PortSession');
}
/**
* 销毁会话管理器
*/
dispose(): void {
try {
// 清理定时器
clearInterval(this.cleanupInterval);
// 结束所有会话
for (const sessionId of this.sessions.keys()) {
this.endSession(sessionId);
}
// 清理状态
this.sessions.clear();
this.portSessions.clear();
logger.info('PortSession disposed');
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error('Failed to dispose PortSession', errorObj);
}
}
}