ciscollm-cli 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,953 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.MockSession = void 0;
40
- const BaseSession_1 = require("./BaseSession");
41
- const fs = __importStar(require("fs"));
42
- const path = __importStar(require("path"));
43
- const chalk_1 = __importDefault(require("chalk"));
44
- class MockSession extends BaseSession_1.BaseSession {
45
- deviceId;
46
- interfaces = new Map();
47
- activeInterface = null;
48
- vlans = new Set([1]);
49
- shellEnabled = false;
50
- shellVariables = new Map();
51
- shellFunctions = new Map();
52
- routes = [];
53
- activeVlan = null;
54
- vlanNames = new Map();
55
- backupSnapshot = null;
56
- rollbackSnapshots = [];
57
- commandPatterns;
58
- getStateFilePath() {
59
- return path.resolve(process.cwd(), `.mock-state-${this.deviceId.toLowerCase().replace(/[^a-z0-9]/g, '_')}.json`);
60
- }
61
- saveState() {
62
- if (process.env.NODE_ENV === 'test')
63
- return;
64
- try {
65
- const data = {
66
- interfaces: Array.from(this.interfaces.entries()),
67
- vlans: Array.from(this.vlans),
68
- shellVariables: Array.from(this.shellVariables.entries()),
69
- shellFunctions: Array.from(this.shellFunctions.entries()),
70
- routes: this.routes,
71
- state: this.state,
72
- activeInterface: this.activeInterface,
73
- activeVlan: this.activeVlan,
74
- vlanNames: Array.from(this.vlanNames.entries())
75
- };
76
- fs.writeFileSync(this.getStateFilePath(), JSON.stringify(data, null, 2), 'utf8');
77
- }
78
- catch (e) {
79
- }
80
- }
81
- loadState() {
82
- if (process.env.NODE_ENV === 'test')
83
- return;
84
- try {
85
- const filePath = this.getStateFilePath();
86
- if (fs.existsSync(filePath)) {
87
- const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
88
- this.interfaces = new Map(data.interfaces);
89
- this.vlans = new Set(data.vlans);
90
- this.shellVariables = new Map(data.shellVariables);
91
- this.shellFunctions = new Map(data.shellFunctions);
92
- this.routes = data.routes;
93
- this.state = data.state;
94
- this.activeInterface = data.activeInterface;
95
- this.activeVlan = data.activeVlan || null;
96
- this.vlanNames = new Map(data.vlanNames || []);
97
- }
98
- }
99
- catch (e) {
100
- }
101
- }
102
- constructor(deviceId = 'Switch') {
103
- super();
104
- this.deviceId = deviceId;
105
- this.state = {
106
- currentMode: 'USER_EXEC',
107
- hostname: this.deviceId,
108
- prompt: `${this.deviceId}>`
109
- };
110
- this.interfaces.set('GigabitEthernet0/0', {
111
- ip: '192.168.1.254',
112
- subnet: '255.255.255.0',
113
- adminShutdown: false,
114
- lineProtocolUp: true,
115
- description: 'Management Uplink'
116
- });
117
- this.interfaces.set('GigabitEthernet0/1', {
118
- ip: null,
119
- subnet: null,
120
- adminShutdown: true,
121
- lineProtocolUp: false,
122
- description: null
123
- });
124
- this.interfaces.set('GigabitEthernet0/2', {
125
- ip: null,
126
- subnet: null,
127
- adminShutdown: true,
128
- lineProtocolUp: false,
129
- description: null
130
- });
131
- this.refreshConnectedRoutes();
132
- this.loadState();
133
- this.commandPatterns = [
134
- { pattern: ['configure', 'terminal'], action: () => this.transitionToGlobalConfig('Enter configuration commands, one per line. End with CNTL/Z.') },
135
- { pattern: ['conf', 't'], action: () => this.transitionToGlobalConfig('Enter configuration commands, one per line. End with CNTL/Z.') },
136
- { pattern: ['enable'], action: () => this.transitionToPrivilegedExec() },
137
- { pattern: ['disable'], action: () => this.transitionToUserExec() },
138
- { pattern: ['terminal', 'shell'], action: () => this.toggleShell(true) },
139
- { pattern: ['shell', 'processing', 'full'], action: () => this.requireGlobalConfigAndToggleShell(true) },
140
- { pattern: ['no', 'shell', 'processing'], action: () => this.requireGlobalConfigAndToggleShell(false) },
141
- { pattern: ['copy', 'running-config'], action: (command) => this.handleCopyRunningConfig(command) },
142
- { pattern: ['configure', 'replace'], action: (command) => this.handleConfigureReplace(command) },
143
- { pattern: ['interface'], action: (command) => this.handleInterfaceCommand(command) },
144
- { pattern: ['vlan'], action: (command) => this.handleVlanCommand(command) },
145
- { pattern: ['name'], action: (command) => this.handleVlanNameCommand(command) },
146
- { pattern: ['switchport'], action: () => '' },
147
- { pattern: ['exit'], action: () => this.handleExitCommand() },
148
- { pattern: ['end'], action: () => this.handleEndCommand() },
149
- { pattern: ['ip', 'address'], action: (command) => this.handleIpAddressCommand(command) },
150
- { pattern: ['no', 'ip', 'address'], action: (command) => this.handleNoIpAddressCommand(command) },
151
- { pattern: ['ip', 'route'], action: (command) => this.handleIpRouteCommand(command) },
152
- { pattern: ['shutdown'], action: () => this.handleShutdownCommand() },
153
- { pattern: ['no', 'shutdown'], action: () => this.handleNoShutdownCommand() },
154
- { pattern: ['description'], action: (command) => this.handleDescriptionCommand(command) },
155
- { pattern: ['no', 'description'], action: () => this.handleNoDescriptionCommand() },
156
- { pattern: ['show', 'ip', 'interface', 'brief'], action: () => this.handleShowIpInterfaceBrief() },
157
- { pattern: ['show', 'running-config'], action: () => this.handleShowRunningConfig() },
158
- { pattern: ['show', 'run'], action: () => this.handleShowRunningConfig() },
159
- { pattern: ['show', 'ip', 'route'], action: () => this.handleShowIpRoute() },
160
- { pattern: ['show', 'vlan', 'brief'], action: () => this.handleShowVlanBrief() },
161
- { pattern: ['show', 'interfaces'], action: () => this.handleShowInterfaces() },
162
- { pattern: ['show', 'interfaces', 'status'], action: () => this.handleShowInterfacesStatus() },
163
- { pattern: ['show', 'interfaces', 'brief'], action: () => this.handleShowInterfacesStatus() },
164
- { pattern: ['dir', 'flash:'], action: () => this.handleDirFlash() },
165
- { pattern: ['dir'], action: () => this.handleDirFlash() },
166
- { pattern: ['router', 'ospf'], action: (command) => this.handleRouterOspf(command) },
167
- { pattern: ['network'], action: () => '' },
168
- { pattern: ['ip', 'ospf'], action: () => '' },
169
- { pattern: ['ip', 'dhcp', 'pool'], action: (command) => this.handleIpDhcpPool(command) },
170
- { pattern: ['ip', 'pool'], action: (command) => this.handleIpDhcpPool(command) },
171
- { pattern: ['default-router'], action: () => '' },
172
- { pattern: ['dns-server'], action: () => '' },
173
- { pattern: ['ip', 'dhcp', 'excluded-address'], action: () => '' },
174
- { pattern: ['access-list'], action: () => '' },
175
- { pattern: ['ip', 'access-list'], action: (command) => this.handleIpAccessList(command) },
176
- { pattern: ['ip', 'access-group'], action: () => '' },
177
- { pattern: ['permit'], action: () => '' },
178
- { pattern: ['deny'], action: () => '' },
179
- { pattern: ['ip', 'nat', 'inside', 'source'], action: () => '' },
180
- { pattern: ['ip', 'nat', 'inside'], action: () => '' },
181
- { pattern: ['ip', 'nat', 'outside'], action: () => '' }
182
- ];
183
- }
184
- isShellEnabled() {
185
- return this.shellEnabled;
186
- }
187
- async connect() {
188
- console.log(chalk_1.default.green(`✔ Simulated device session for "${this.deviceId}" established.`));
189
- return Promise.resolve();
190
- }
191
- async execute(command, timeoutMs) {
192
- const result = await this.executeInternal(command, timeoutMs);
193
- this.saveState();
194
- return result;
195
- }
196
- async executeInternal(command, timeoutMs) {
197
- let clean = command.trim();
198
- let lower = clean.toLowerCase();
199
- if (this.shellEnabled) {
200
- const assignMatch = clean.match(/^([a-zA-Z_]\w*)=(.*)$/);
201
- if (assignMatch) {
202
- const varName = assignMatch[1];
203
- const varValue = assignMatch[2].trim();
204
- this.shellVariables.set(varName, varValue);
205
- return '';
206
- }
207
- }
208
- if (this.shellEnabled) {
209
- const funcMatch = clean.match(/^(\w+)\(\)\s*\{\s*(.+);\s*\}$/);
210
- if (funcMatch) {
211
- const funcName = funcMatch[1];
212
- const funcBody = funcMatch[2].trim();
213
- this.shellFunctions.set(funcName, funcBody);
214
- return '';
215
- }
216
- }
217
- if (this.shellEnabled) {
218
- const forMatch = clean.match(/^for\s+(\w+)\s+in\s+([^;]+);\s*do\s+([^;]+);\s*done$/i);
219
- if (forMatch) {
220
- const varName = forMatch[1];
221
- const itemsStr = forMatch[2];
222
- const loopBody = forMatch[3].trim();
223
- const items = itemsStr.trim().split(/\s+/);
224
- let outputs = [];
225
- for (const item of items) {
226
- this.shellVariables.set(varName, item);
227
- const out = await this.execute(loopBody, timeoutMs);
228
- if (out) {
229
- outputs.push(out);
230
- }
231
- }
232
- return outputs.join('\n');
233
- }
234
- }
235
- if (this.shellEnabled) {
236
- clean = this.substituteShellVariables(clean);
237
- lower = clean.toLowerCase();
238
- }
239
- let baseCommand = clean;
240
- let pipeParts = [];
241
- if (clean.includes('|')) {
242
- pipeParts = clean.split('|').map(p => p.trim());
243
- baseCommand = pipeParts[0];
244
- }
245
- let baseOutput = await this.executeBase(baseCommand, timeoutMs);
246
- if (pipeParts.length > 1) {
247
- let finalOutput = baseOutput;
248
- for (let i = 1; i < pipeParts.length; i++) {
249
- const filterExpr = pipeParts[i];
250
- const filterLower = filterExpr.toLowerCase();
251
- const lines = finalOutput.split(/\r?\n/);
252
- if (filterLower.startsWith('include ') || filterLower.startsWith('grep ')) {
253
- const pattern = filterExpr.substring(filterExpr.indexOf(' ') + 1).trim().toLowerCase();
254
- finalOutput = lines.filter(line => line.toLowerCase().includes(pattern)).join('\n');
255
- }
256
- else if (filterLower.startsWith('exclude ')) {
257
- const pattern = filterExpr.substring(filterExpr.indexOf(' ') + 1).trim().toLowerCase();
258
- finalOutput = lines.filter(line => !line.toLowerCase().includes(pattern)).join('\n');
259
- }
260
- else if (filterLower.startsWith('begin ')) {
261
- const pattern = filterExpr.substring(filterExpr.indexOf(' ') + 1).trim().toLowerCase();
262
- const idx = lines.findIndex(line => line.toLowerCase().includes(pattern));
263
- if (idx !== -1) {
264
- finalOutput = lines.slice(idx).join('\n');
265
- }
266
- else {
267
- finalOutput = '';
268
- }
269
- }
270
- }
271
- return finalOutput;
272
- }
273
- return baseOutput;
274
- }
275
- async executeBase(command, timeoutMs) {
276
- const clean = command.trim();
277
- const lower = clean.toLowerCase();
278
- if (lower === 'terminal shell') {
279
- if (this.state.currentMode === 'PRIVILEGED_EXEC') {
280
- this.shellEnabled = true;
281
- return '';
282
- }
283
- return '% Command rejected: Place in Privileged EXEC mode first.';
284
- }
285
- if (lower === 'shell processing full') {
286
- if (this.state.currentMode === 'GLOBAL_CONFIG') {
287
- this.shellEnabled = true;
288
- return '';
289
- }
290
- return '% Command rejected: Place in Global Config mode first.';
291
- }
292
- if (lower === 'no shell processing') {
293
- if (this.state.currentMode === 'GLOBAL_CONFIG') {
294
- this.shellEnabled = false;
295
- return '';
296
- }
297
- return '% Command rejected: Place in Global Config mode first.';
298
- }
299
- if (lower.startsWith('echo ')) {
300
- return clean.substring(5).trim();
301
- }
302
- if (lower === 'show shell environment' || lower === 'sh shell env') {
303
- let output = 'Shell Variables:\n';
304
- for (const [key, val] of this.shellVariables.entries()) {
305
- output += `${key}=${val}\n`;
306
- }
307
- return output.trim();
308
- }
309
- if (lower === 'show shell functions' || lower === 'sh shell func') {
310
- let output = 'Shell Functions:\n';
311
- for (const [key, val] of this.shellFunctions.entries()) {
312
- output += `${key}() { ${val}; }\n`;
313
- }
314
- return output.trim();
315
- }
316
- if (this.shellEnabled && this.shellFunctions.has(lower)) {
317
- const body = this.shellFunctions.get(lower);
318
- return this.execute(body, timeoutMs);
319
- }
320
- await this.simulateLatency(clean, timeoutMs);
321
- const parsed = this.matchCommand(clean);
322
- if (parsed) {
323
- const handled = parsed.action(clean, lower);
324
- if (handled !== null) {
325
- return handled;
326
- }
327
- }
328
- if (lower.startsWith('show ')) {
329
- return this.formatInvalidInput(clean, this.findInvalidTokenIndex(clean));
330
- }
331
- if (lower.startsWith('ip ')) {
332
- return this.formatInvalidInput(clean, this.findInvalidTokenIndex(clean));
333
- }
334
- if (lower.startsWith('no ')) {
335
- return this.formatInvalidInput(clean, this.findInvalidTokenIndex(clean));
336
- }
337
- if (lower.startsWith('ping ')) {
338
- const dest = clean.split(/\s+/)[1];
339
- if (!dest)
340
- return this.formatIncompleteCommand(clean);
341
- const pingSuccess = this.isDestinationReachable(dest);
342
- let output = `Type escape sequence to abort.\nSending 5, 100-byte ICMP Echos to ${dest}, timeout is 2 seconds:\n`;
343
- if (pingSuccess) {
344
- output += `!!!!!\nSuccess rate is 100 percent (5/5), round-trip min/avg/max = 1/3/12 ms`;
345
- }
346
- else {
347
- output += `.....\nSuccess rate is 0 percent (0/5)`;
348
- }
349
- return output;
350
- }
351
- return `% Unrecognized command: "${clean}"`;
352
- }
353
- async disconnect() {
354
- console.log(chalk_1.default.gray(`❯ Simulated session for "${this.deviceId}" disconnected.`));
355
- return Promise.resolve();
356
- }
357
- updateMode(mode) {
358
- this.state.currentMode = mode;
359
- let suffix = '>';
360
- if (mode === 'PRIVILEGED_EXEC')
361
- suffix = '#';
362
- else if (mode === 'GLOBAL_CONFIG')
363
- suffix = '(config)#';
364
- else if (mode === 'INTERFACE_CONFIG')
365
- suffix = `(config-if)#`;
366
- else if (mode === 'VLAN_CONFIG')
367
- suffix = `(config-vlan)#`;
368
- this.state.prompt = `${this.state.hostname}${suffix}`;
369
- }
370
- matchCommand(command) {
371
- const tokens = command.trim().split(/\s+/).filter(Boolean);
372
- for (const entry of this.commandPatterns) {
373
- if (this.matchesPattern(tokens, entry.pattern)) {
374
- return { action: entry.action };
375
- }
376
- }
377
- return null;
378
- }
379
- matchesPattern(inputTokens, pattern) {
380
- if (inputTokens.length < pattern.length) {
381
- return false;
382
- }
383
- for (let i = 0; i < pattern.length; i++) {
384
- const inputToken = inputTokens[i].toLowerCase();
385
- const expected = pattern[i].toLowerCase();
386
- if (!expected.startsWith(inputToken)) {
387
- return false;
388
- }
389
- }
390
- return true;
391
- }
392
- isConfigMode() {
393
- return this.state.currentMode !== 'USER_EXEC' && this.state.currentMode !== 'PRIVILEGED_EXEC';
394
- }
395
- formatInvalidInput(command, caretIndex) {
396
- const safeIndex = Math.max(0, Math.min(caretIndex, command.length));
397
- const caretLine = `${' '.repeat(safeIndex)}^`;
398
- return `${command}\n${caretLine}\n% Invalid input detected at '^' marker.`;
399
- }
400
- formatIncompleteCommand(command) {
401
- return `${command}\n% Incomplete command.`;
402
- }
403
- findInvalidTokenIndex(command) {
404
- const tokens = command.trim().split(/\s+/).filter(Boolean);
405
- if (tokens.length === 0) {
406
- return 0;
407
- }
408
- const lower = tokens.map(t => t.toLowerCase());
409
- const knownStartTokens = new Set(['enable', 'disable', 'configure', 'conf', 'terminal', 'shell', 'interface', 'int', 'vlan', 'exit', 'end', 'ip', 'no', 'shutdown', 'description', 'show', 'ping']);
410
- for (let i = 0; i < lower.length; i++) {
411
- if (!knownStartTokens.has(lower[i])) {
412
- return command.toLowerCase().indexOf(tokens[i].toLowerCase());
413
- }
414
- }
415
- return command.length;
416
- }
417
- transitionToGlobalConfig(message) {
418
- const allowedModes = ['PRIVILEGED_EXEC', 'GLOBAL_CONFIG', 'INTERFACE_CONFIG', 'VLAN_CONFIG'];
419
- if (allowedModes.includes(this.state.currentMode)) {
420
- this.updateMode('GLOBAL_CONFIG');
421
- return message;
422
- }
423
- return '% Command rejected: Place in Privileged EXEC mode first.';
424
- }
425
- transitionToPrivilegedExec() {
426
- if (this.state.currentMode === 'USER_EXEC') {
427
- this.updateMode('PRIVILEGED_EXEC');
428
- return '';
429
- }
430
- return '';
431
- }
432
- transitionToUserExec() {
433
- if (this.state.currentMode === 'PRIVILEGED_EXEC') {
434
- this.updateMode('USER_EXEC');
435
- return '';
436
- }
437
- return '';
438
- }
439
- toggleShell(enabled) {
440
- if (enabled) {
441
- if (this.state.currentMode === 'PRIVILEGED_EXEC') {
442
- this.shellEnabled = true;
443
- return '';
444
- }
445
- return '% Command rejected: Place in Privileged EXEC mode first.';
446
- }
447
- if (this.state.currentMode === 'GLOBAL_CONFIG') {
448
- this.shellEnabled = false;
449
- return '';
450
- }
451
- return '% Command rejected: Place in Global Config mode first.';
452
- }
453
- requireGlobalConfigAndToggleShell(enabled) {
454
- if (this.state.currentMode === 'GLOBAL_CONFIG') {
455
- this.shellEnabled = enabled;
456
- return '';
457
- }
458
- return '% Command rejected: Place in Global Config mode first.';
459
- }
460
- handleInterfaceCommand(command) {
461
- if (!this.isConfigMode()) {
462
- return this.formatInvalidInput(command, 0);
463
- }
464
- const parts = command.trim().split(/\s+/);
465
- if (parts.length < 2) {
466
- return this.formatIncompleteCommand(command);
467
- }
468
- const intName = parts.slice(1).join('');
469
- const resolvedName = this.resolveInterfaceName(intName);
470
- if (!resolvedName) {
471
- return this.formatBadInterfaceParameter(command, parts[1]);
472
- }
473
- if (!this.interfaces.has(resolvedName)) {
474
- if (resolvedName.toLowerCase().startsWith('loopback') || resolvedName.toLowerCase().startsWith('vlan')) {
475
- this.interfaces.set(resolvedName, {
476
- ip: null,
477
- subnet: null,
478
- adminShutdown: false,
479
- lineProtocolUp: true,
480
- description: null
481
- });
482
- }
483
- else {
484
- return this.formatBadInterfaceParameter(command, parts[1]);
485
- }
486
- }
487
- this.activeInterface = resolvedName;
488
- this.activeVlan = null;
489
- this.updateMode('INTERFACE_CONFIG');
490
- return '';
491
- }
492
- handleVlanCommand(command) {
493
- if (!this.isConfigMode()) {
494
- return this.formatInvalidInput(command, 0);
495
- }
496
- const parts = command.trim().split(/\s+/);
497
- if (parts.length > 2) {
498
- return this.formatInvalidInput(command, command.indexOf(parts[2]));
499
- }
500
- const vlanId = parseInt(parts[1], 10);
501
- if (isNaN(vlanId)) {
502
- return `${command}\n${' '.repeat(command.indexOf(parts[1] || ''))}^\n% Invalid VLAN ID format.`;
503
- }
504
- this.vlans.add(vlanId);
505
- this.activeVlan = vlanId;
506
- this.activeInterface = null;
507
- this.updateMode('VLAN_CONFIG');
508
- return '';
509
- }
510
- handleVlanNameCommand(command) {
511
- if (this.state.currentMode !== 'VLAN_CONFIG' || this.activeVlan === null) {
512
- return this.formatInvalidInput(command, 0);
513
- }
514
- const parts = command.trim().split(/\s+/);
515
- if (parts.length < 2) {
516
- return this.formatIncompleteCommand(command);
517
- }
518
- this.pushRollbackSnapshot();
519
- const vlanName = parts.slice(1).join(' ');
520
- this.vlanNames.set(this.activeVlan, vlanName);
521
- return '';
522
- }
523
- handleExitCommand() {
524
- if (this.state.currentMode === 'VLAN_CONFIG') {
525
- this.activeVlan = null;
526
- this.updateMode('GLOBAL_CONFIG');
527
- return '';
528
- }
529
- if (this.state.prompt.includes('(') && !this.state.prompt.includes('(config-if)')) {
530
- this.updateMode('GLOBAL_CONFIG');
531
- return '';
532
- }
533
- if (this.state.currentMode === 'INTERFACE_CONFIG') {
534
- this.activeInterface = null;
535
- this.updateMode('GLOBAL_CONFIG');
536
- return '';
537
- }
538
- if (this.state.currentMode === 'GLOBAL_CONFIG') {
539
- this.updateMode('PRIVILEGED_EXEC');
540
- return '';
541
- }
542
- if (this.state.currentMode === 'PRIVILEGED_EXEC') {
543
- this.updateMode('USER_EXEC');
544
- return '';
545
- }
546
- return '% Connection closed.';
547
- }
548
- handleEndCommand() {
549
- if (this.state.currentMode === 'GLOBAL_CONFIG' || this.state.currentMode === 'INTERFACE_CONFIG' || this.state.currentMode === 'VLAN_CONFIG' || this.state.prompt.includes(')')) {
550
- this.activeInterface = null;
551
- this.activeVlan = null;
552
- this.updateMode('PRIVILEGED_EXEC');
553
- return '';
554
- }
555
- return '% Invalid input detected.';
556
- }
557
- handleIpAddressCommand(command) {
558
- if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
559
- this.pushRollbackSnapshot();
560
- const parts = command.trim().split(/\s+/);
561
- const ip = parts[2];
562
- const subnet = parts[3];
563
- if (!ip || !subnet) {
564
- return this.formatIncompleteCommand(command);
565
- }
566
- const intf = this.interfaces.get(this.activeInterface);
567
- intf.ip = ip;
568
- intf.subnet = subnet;
569
- intf.lineProtocolUp = !intf.adminShutdown;
570
- this.refreshConnectedRoutes();
571
- return '';
572
- }
573
- return this.formatInvalidInput(command, 0);
574
- }
575
- handleNoIpAddressCommand(command) {
576
- if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
577
- this.pushRollbackSnapshot();
578
- const intf = this.interfaces.get(this.activeInterface);
579
- intf.ip = null;
580
- intf.subnet = null;
581
- this.refreshConnectedRoutes();
582
- return '';
583
- }
584
- return this.formatInvalidInput(command, 0);
585
- }
586
- handleShutdownCommand() {
587
- if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
588
- this.pushRollbackSnapshot();
589
- const intf = this.interfaces.get(this.activeInterface);
590
- intf.adminShutdown = true;
591
- intf.lineProtocolUp = false;
592
- this.refreshConnectedRoutes();
593
- return '';
594
- }
595
- return this.formatInvalidInput('shutdown', 0);
596
- }
597
- handleNoShutdownCommand() {
598
- if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
599
- this.pushRollbackSnapshot();
600
- const intf = this.interfaces.get(this.activeInterface);
601
- intf.adminShutdown = false;
602
- intf.lineProtocolUp = true;
603
- this.refreshConnectedRoutes();
604
- return '';
605
- }
606
- return this.formatInvalidInput('no shutdown', 0);
607
- }
608
- handleDescriptionCommand(command) {
609
- if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
610
- this.pushRollbackSnapshot();
611
- const desc = command.trim().substring(command.trim().indexOf(' ') + 1).trim();
612
- const intf = this.interfaces.get(this.activeInterface);
613
- intf.description = desc;
614
- return '';
615
- }
616
- return this.formatInvalidInput(command, 0);
617
- }
618
- handleNoDescriptionCommand() {
619
- if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
620
- this.pushRollbackSnapshot();
621
- const intf = this.interfaces.get(this.activeInterface);
622
- intf.description = null;
623
- return '';
624
- }
625
- return this.formatInvalidInput('no description', 0);
626
- }
627
- handleShowIpInterfaceBrief() {
628
- let output = 'Interface IP-Address OK? Method Status Protocol\n';
629
- for (const [name, config] of this.interfaces.entries()) {
630
- const ip = config.ip || 'unassigned';
631
- const status = config.adminShutdown ? 'administratively down' : 'up';
632
- const proto = config.lineProtocolUp ? 'up' : 'down';
633
- output += `${name.padEnd(26)} ${ip.padEnd(15)} YES manual ${status.padEnd(21)} ${proto}\n`;
634
- }
635
- return output;
636
- }
637
- handleShowRunningConfig() {
638
- let output = `Building configuration...\n\nCurrent configuration : 1584 bytes\n!\nversion 15.2\nhostname ${this.state.hostname}\n!`;
639
- for (const [name, config] of this.interfaces.entries()) {
640
- output += `\ninterface ${name}`;
641
- if (config.description)
642
- output += `\n description ${config.description}`;
643
- if (config.ip)
644
- output += `\n ip address ${config.ip} ${config.subnet}`;
645
- if (config.adminShutdown)
646
- output += '\n shutdown';
647
- output += '\n!';
648
- }
649
- for (const route of this.routes) {
650
- output += `\nip route ${route.network} ${route.mask} ${route.nextHop || route.outgoingInterface || 'null'}`;
651
- }
652
- return output;
653
- }
654
- handleShowVlanBrief() {
655
- let output = 'VLAN Name Status Ports\n';
656
- for (const vlanId of this.vlans.values()) {
657
- const name = this.vlanNames.get(vlanId) || (vlanId === 1 ? 'default' : `VLAN${String(vlanId).padStart(4, '0')}`);
658
- output += `${String(vlanId).padEnd(5)} ${name.padEnd(32)} active \n`;
659
- }
660
- return output;
661
- }
662
- handleShowInterfaces() {
663
- let output = '';
664
- for (const [name, config] of this.interfaces.entries()) {
665
- const status = config.adminShutdown ? 'administratively down' : 'up';
666
- const proto = config.lineProtocolUp ? 'up' : 'down';
667
- output += `${name} is ${status}, line protocol is ${proto}\n`;
668
- }
669
- return output.trim();
670
- }
671
- handleShowInterfacesStatus() {
672
- let output = 'Port Name Status Vlan Duplex Speed Type\n';
673
- for (const [name, config] of this.interfaces.entries()) {
674
- const status = config.adminShutdown ? 'disabled' : (config.lineProtocolUp ? 'connected' : 'notconnect');
675
- output += `${name.padEnd(9)} ${(config.description || '').padEnd(18)} ${status.padEnd(12)} 1 auto auto 10/100/1000BaseTX\n`;
676
- }
677
- return output.trim();
678
- }
679
- handleShowIpRoute() {
680
- let output = 'Codes: C - connected, S - static\n\n';
681
- for (const route of this.routes) {
682
- const code = route.connected ? 'C' : 'S';
683
- const target = route.connected ? route.network : `${route.network} [1/0] via ${route.nextHop || route.outgoingInterface || ''}`;
684
- output += `${code} ${target}\n`;
685
- }
686
- return output.trim();
687
- }
688
- handleCopyRunningConfig(command) {
689
- if (this.state.currentMode !== 'PRIVILEGED_EXEC') {
690
- return this.formatInvalidInput(command, 0);
691
- }
692
- this.backupSnapshot = this.cloneSnapshot();
693
- this.pushRollbackSnapshot();
694
- return 'Copy complete, 1584 bytes copied in 0.000 secs (0 bytes/sec)';
695
- }
696
- handleConfigureReplace(command) {
697
- if (this.state.currentMode !== 'PRIVILEGED_EXEC') {
698
- return this.formatInvalidInput(command, 0);
699
- }
700
- if (!this.backupSnapshot) {
701
- return '% No backup configuration available.';
702
- }
703
- this.restoreSnapshot(this.backupSnapshot);
704
- this.clearRollbackSnapshots();
705
- return 'Configure replace completed successfully.';
706
- }
707
- handleIpRouteCommand(command) {
708
- if (!this.isConfigMode()) {
709
- return this.formatInvalidInput(command, 0);
710
- }
711
- const parts = command.trim().split(/\s+/);
712
- if (parts.length < 5) {
713
- return this.formatIncompleteCommand(command);
714
- }
715
- this.pushRollbackSnapshot();
716
- const [, , network, mask, nextHop] = parts;
717
- const route = {
718
- network,
719
- mask,
720
- nextHop: nextHop || null,
721
- outgoingInterface: null,
722
- connected: false
723
- };
724
- this.routes.push(route);
725
- return '';
726
- }
727
- formatBadInterfaceParameter(command, intName) {
728
- const caretIndex = Math.max(0, command.toLowerCase().indexOf(intName.toLowerCase()));
729
- return `${command}\n${' '.repeat(caretIndex)}^\n% Bad interface parameter: ${intName}`;
730
- }
731
- resolveInterfaceName(intName) {
732
- const lowerName = intName.toLowerCase();
733
- const aliases = [
734
- { canonical: 'GigabitEthernet', aliases: ['gigabitethernet', 'gi'] },
735
- { canonical: 'FastEthernet', aliases: ['fastethernet', 'fa'] },
736
- { canonical: 'Loopback', aliases: ['loopback', 'lo'] },
737
- { canonical: 'Vlan', aliases: ['vlan', 'vl'] }
738
- ];
739
- for (const entry of aliases) {
740
- const matchedAlias = entry.aliases.find(alias => lowerName.startsWith(alias));
741
- if (matchedAlias) {
742
- return `${entry.canonical}${intName.substring(matchedAlias.length)}`;
743
- }
744
- }
745
- if (this.interfaces.has(intName)) {
746
- return intName;
747
- }
748
- return null;
749
- }
750
- hasSnapshots() {
751
- return this.rollbackSnapshots.length > 0;
752
- }
753
- restoreToInitialSnapshot() {
754
- const snapshot = this.rollbackSnapshots[0];
755
- if (!snapshot) {
756
- return false;
757
- }
758
- this.restoreSnapshot(snapshot);
759
- this.clearRollbackSnapshots();
760
- return true;
761
- }
762
- restoreBackupSnapshot() {
763
- if (!this.backupSnapshot) {
764
- return false;
765
- }
766
- this.restoreSnapshot(this.backupSnapshot);
767
- this.clearRollbackSnapshots();
768
- return true;
769
- }
770
- pushRollbackSnapshot() {
771
- this.rollbackSnapshots.push(this.cloneSnapshot());
772
- }
773
- clearRollbackSnapshots() {
774
- this.rollbackSnapshots = [];
775
- }
776
- cloneSnapshot() {
777
- return {
778
- state: this.getState(),
779
- interfaces: new Map(Array.from(this.interfaces.entries()).map(([name, config]) => [name, { ...config }])),
780
- activeInterface: this.activeInterface,
781
- vlans: new Set(this.vlans),
782
- shellEnabled: this.shellEnabled,
783
- shellVariables: new Map(this.shellVariables),
784
- shellFunctions: new Map(this.shellFunctions),
785
- routes: this.routes.map(route => ({ ...route })),
786
- activeVlan: this.activeVlan,
787
- vlanNames: new Map(this.vlanNames)
788
- };
789
- }
790
- restoreSnapshot(snapshot) {
791
- this.state = { ...snapshot.state };
792
- this.interfaces = new Map(Array.from(snapshot.interfaces.entries()).map(([name, config]) => [name, { ...config }]));
793
- this.activeInterface = snapshot.activeInterface;
794
- this.vlans = new Set(snapshot.vlans);
795
- this.shellEnabled = snapshot.shellEnabled;
796
- this.shellVariables = new Map(snapshot.shellVariables);
797
- this.shellFunctions = new Map(snapshot.shellFunctions);
798
- this.routes = snapshot.routes.map(route => ({ ...route }));
799
- this.activeVlan = snapshot.activeVlan;
800
- this.vlanNames = new Map(snapshot.vlanNames);
801
- }
802
- refreshConnectedRoutes() {
803
- const connectedRoutes = new Map();
804
- for (const [name, config] of this.interfaces.entries()) {
805
- if (config.ip && config.subnet) {
806
- const network = this.computeNetworkAddress(config.ip, config.subnet);
807
- const key = `${network}/${config.subnet}`;
808
- connectedRoutes.set(key, {
809
- network,
810
- mask: config.subnet,
811
- nextHop: null,
812
- outgoingInterface: name,
813
- connected: true
814
- });
815
- }
816
- }
817
- const staticRoutes = this.routes.filter(route => !route.connected);
818
- this.routes = [...connectedRoutes.values(), ...staticRoutes];
819
- }
820
- isDestinationReachable(dest) {
821
- if (dest === '127.0.0.1' || dest === '8.8.8.8') {
822
- return true;
823
- }
824
- for (const [name, config] of this.interfaces.entries()) {
825
- if (!config.ip || !config.subnet || config.adminShutdown || !config.lineProtocolUp) {
826
- continue;
827
- }
828
- if (config.ip === dest) {
829
- return true;
830
- }
831
- if (this.isInSameSubnet(dest, config.ip, config.subnet)) {
832
- return true;
833
- }
834
- }
835
- for (const route of this.routes) {
836
- if (route.connected) {
837
- continue;
838
- }
839
- if (this.ipMatchesRoute(dest, route.network, route.mask)) {
840
- return true;
841
- }
842
- }
843
- return false;
844
- }
845
- computeNetworkAddress(ip, mask) {
846
- const ipParts = this.ipToIntParts(ip);
847
- const maskParts = this.ipToIntParts(mask);
848
- return this.intPartsToIp(ipParts.map((octet, idx) => octet & maskParts[idx]));
849
- }
850
- isInSameSubnet(ip, interfaceIp, mask) {
851
- return this.computeNetworkAddress(ip, mask) === this.computeNetworkAddress(interfaceIp, mask);
852
- }
853
- ipMatchesRoute(ip, network, mask) {
854
- return this.computeNetworkAddress(ip, mask) === network;
855
- }
856
- ipToIntParts(ip) {
857
- return ip.split('.').map(part => parseInt(part, 10));
858
- }
859
- intPartsToIp(parts) {
860
- return parts.map(part => Math.max(0, Math.min(255, part))).join('.');
861
- }
862
- async simulateLatency(command, timeoutMs) {
863
- const normalized = command.toLowerCase();
864
- let delayMs = 0;
865
- if (normalized.startsWith('copy ')) {
866
- delayMs = 120;
867
- }
868
- else if (normalized.startsWith('ping ')) {
869
- delayMs = 45;
870
- }
871
- else if (normalized.startsWith('show ')) {
872
- delayMs = 20;
873
- }
874
- if (timeoutMs && delayMs > timeoutMs) {
875
- await new Promise(resolve => setTimeout(resolve, timeoutMs));
876
- return;
877
- }
878
- if (delayMs > 0) {
879
- await new Promise(resolve => setTimeout(resolve, delayMs));
880
- }
881
- }
882
- substituteShellVariables(command) {
883
- let output = '';
884
- for (let index = 0; index < command.length; index++) {
885
- const char = command[index];
886
- if (char !== '$') {
887
- output += char;
888
- continue;
889
- }
890
- const next = command[index + 1];
891
- if (next === '{') {
892
- const endBrace = command.indexOf('}', index + 2);
893
- if (endBrace !== -1) {
894
- const name = command.slice(index + 2, endBrace);
895
- output += this.shellVariables.get(name) || '';
896
- index = endBrace;
897
- continue;
898
- }
899
- }
900
- let nameEnd = index + 1;
901
- while (nameEnd < command.length && /[A-Za-z0-9_]/.test(command[nameEnd])) {
902
- nameEnd++;
903
- }
904
- if (nameEnd > index + 1) {
905
- const name = command.slice(index + 1, nameEnd);
906
- output += this.shellVariables.get(name) || '';
907
- index = nameEnd - 1;
908
- continue;
909
- }
910
- output += '$';
911
- }
912
- return output;
913
- }
914
- handleDirFlash() {
915
- return `Directory of flash:/
916
-
917
- 1 -rw- 1584 May 27 2026 10:30:00 +00:00 backup-agent.cfg
918
-
919
- 15728640 bytes total (15727056 bytes free)`;
920
- }
921
- handleRouterOspf(command) {
922
- if (!this.isConfigMode()) {
923
- return '% Command rejected: Place in configuration mode first.';
924
- }
925
- this.activeInterface = null;
926
- this.activeVlan = null;
927
- this.updateMode('GLOBAL_CONFIG');
928
- this.state.prompt = `${this.state.hostname}(config-router)#`;
929
- return '';
930
- }
931
- handleIpDhcpPool(command) {
932
- if (!this.isConfigMode()) {
933
- return '% Command rejected: Place in configuration mode first.';
934
- }
935
- this.activeInterface = null;
936
- this.activeVlan = null;
937
- this.updateMode('GLOBAL_CONFIG');
938
- this.state.prompt = `${this.state.hostname}(dhcp-config)#`;
939
- return '';
940
- }
941
- handleIpAccessList(command) {
942
- if (!this.isConfigMode()) {
943
- return '% Command rejected: Place in configuration mode first.';
944
- }
945
- this.activeInterface = null;
946
- this.activeVlan = null;
947
- this.updateMode('GLOBAL_CONFIG');
948
- this.state.prompt = `${this.state.hostname}(config-ext-nacl)#`;
949
- return '';
950
- }
951
- }
952
- exports.MockSession = MockSession;
953
- //# sourceMappingURL=MockSession.js.map