dsh-ensp 0.1.3 → 0.1.7

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.
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Authoritative interface table for every eNSP model this project supports.
3
+ *
4
+ * This table used to be duplicated: `build.mjs` carried one copy for the topology
5
+ * builder and `topology.mjs` carried another for the serialiser and index maths.
6
+ * The two drifted, and the drift was not cosmetic — the serialiser's copy never
7
+ * learned that firewalls number their ports as `GigabitEthernet1/0/x`, so
8
+ * `ensp_build_topology` refused every `GigabitEthernet1/0/0` a caller passed in.
9
+ * A single table removes that whole class of bug.
10
+ *
11
+ * Group layout is **not** cosmetic either. eNSP rebuilds a device's port list from
12
+ * the `<interface>` elements verbatim; declaring one `count="3"` group on an
13
+ * AR2220 does not expand to GE0/0/0 + GE0/0/1..2, and the device silently fails to
14
+ * render (empty canvas, no error). The groupings below were counted across all 40
15
+ * bundled sample topologies — see `docs/ENSP-FORMAT.md`.
16
+ *
17
+ * Group tuple: `[sztype, interfacename, count, firstNumber, cliPrefix]`.
18
+ *
19
+ * @module dsh-ensp/models
20
+ */
21
+
22
+ export const MODEL_SPECS = {
23
+ // 24 GE ports in one group, numbering from 1.
24
+ S5700: {
25
+ groups: [['Ethernet', 'GE', 24, 1]],
26
+ base: 1,
27
+ vrp: 'GigabitEthernet0/0/',
28
+ console: true,
29
+ },
30
+ // 22 fast ports (Ethernet0/0/1..22) then 2 gigabit (GE0/0/1..2).
31
+ S3700: {
32
+ groups: [['Ethernet', 'Ethernet', 22, 1], ['Ethernet', 'GE', 2, 1]],
33
+ base: 1,
34
+ vrp: 'Ethernet0/0/',
35
+ console: true,
36
+ },
37
+ // GE0/0/0, then GE0/0/1..2 — the split is required, and the second group starts
38
+ // numbering at 1, not 0.
39
+ AR2220: {
40
+ groups: [['Ethernet', 'GE', 1, 0], ['Ethernet', 'GE', 2, 1]],
41
+ base: 0,
42
+ vrp: 'GigabitEthernet0/0/',
43
+ console: true,
44
+ },
45
+ AR2240: {
46
+ groups: [['Ethernet', 'GE', 4, 0]],
47
+ base: 0,
48
+ vrp: 'GigabitEthernet0/0/',
49
+ console: true,
50
+ },
51
+ // GE0/0/0..1 then Ethernet0/0/0..7 — two CLI families in one slot.
52
+ AR1220: {
53
+ groups: [['Ethernet', 'GE', 2, 0], ['Ethernet', 'Ethernet', 8, 0]],
54
+ base: 0,
55
+ vrp: 'GigabitEthernet0/0/',
56
+ console: true,
57
+ },
58
+ // Firewalls number their CLI ports from 1/0/0 while eNSP stores them from index
59
+ // 0, so the CLI prefix is stated explicitly. Guessing it from the eNSP prefix
60
+ // produces `GigabitEthernet0/0/x` and rejects every real firewall interface.
61
+ USG5500: {
62
+ groups: [['Ethernet', 'GE', 9, 0, 'GigabitEthernet1/0/']],
63
+ base: 0,
64
+ vrp: 'GigabitEthernet1/0/',
65
+ console: true,
66
+ },
67
+ USG6000V: {
68
+ groups: [['Ethernet', 'GE', 9, 0, 'GigabitEthernet1/0/']],
69
+ base: 0,
70
+ vrp: 'GigabitEthernet1/0/',
71
+ console: true,
72
+ },
73
+ AC6005: {
74
+ groups: [['Ethernet', 'GE', 8, 1]],
75
+ base: 1,
76
+ vrp: 'GigabitEthernet0/0/',
77
+ console: true,
78
+ },
79
+ // PC and Server declare `interfacename="GE"` in the file but expose
80
+ // `Ethernet0/0/1` on the CLI. eNSP's file vocabulary and VRP's differ here.
81
+ PC: {
82
+ groups: [['Ethernet', 'GE', 1, 1, 'Ethernet0/0/']],
83
+ base: 1,
84
+ vrp: 'Ethernet0/0/',
85
+ console: false,
86
+ },
87
+ Server: {
88
+ groups: [['Ethernet', 'GE', 1, 1, 'Ethernet0/0/']],
89
+ base: 1,
90
+ vrp: 'Ethernet0/0/',
91
+ console: false,
92
+ },
93
+ Cloud: {
94
+ groups: [['Ethernet', 'Ethernet', 3, 0]],
95
+ base: 0,
96
+ vrp: 'Ethernet0/0/',
97
+ console: false,
98
+ },
99
+ // Wireless endpoints: no console, one uplink.
100
+ AP6050: {
101
+ groups: [['Ethernet', 'GE', 2, 1]],
102
+ base: 1,
103
+ vrp: 'GigabitEthernet0/0/',
104
+ console: true,
105
+ },
106
+ STA: { groups: [], base: 0, vrp: '', console: false },
107
+ Laptop: {
108
+ groups: [['Ethernet', 'Ethernet', 1, 1, 'Ethernet0/0/']],
109
+ base: 1,
110
+ vrp: 'Ethernet0/0/',
111
+ console: false,
112
+ },
113
+ }
114
+
115
+ /** Every model name this module knows. */
116
+ export const MODEL_NAMES = Object.keys(MODEL_SPECS)
117
+
118
+ /** Canvas geometry constants, taken from eNSP's own writer. */
119
+ export const GEOMETRY = { editLeftOffset: 27, editTopOffset: 54 }
120
+
121
+ /**
122
+ * Icon bounding box, measured from every cable anchor in eNSP's own samples.
123
+ *
124
+ * The box is **not** centred on (cx, cy): it runs from `cx - 16.2` to
125
+ * `cx + 70.2`, so its centre is `cx + 27`, not `cx + 43`. Using `height / 2`
126
+ * puts the anchor outside the icon and eNSP then discards the whole `<lines>`
127
+ * block, which shows up as an empty canvas with no error.
128
+ *
129
+ * Frequencies across the bundled samples: (+70.2,+27.0) x35, (-16.2,+27.0) x35,
130
+ * (+27.0,+70.2) x29, (+27.0,-16.2) x27.
131
+ */
132
+ export const ICON = { w: 86, h: 86, left: -16.2, right: 70.2, top: -16.2, bottom: 70.2 }
133
+
134
+ /** Total declared interface count for a model. */
135
+ export function portCount(model) {
136
+ const spec = MODEL_SPECS[model]
137
+ if (!spec) return 0
138
+ return spec.groups.reduce((sum, group) => sum + group[2], 0)
139
+ }
140
+
141
+ /** CLI prefix for an eNSP interface-name group, when the group does not state one. */
142
+ export function cliPrefixFor(enspPrefix) {
143
+ return enspPrefix === 'GE' ? 'GigabitEthernet0/0/' : 'Ethernet0/0/'
144
+ }
145
+
146
+ /** Human-readable list of a model's ports, for error messages. */
147
+ export function describePorts(model) {
148
+ const spec = MODEL_SPECS[model]
149
+ if (!spec) return '(unknown model)'
150
+ if (!spec.groups.length) return '(no interfaces)'
151
+ return spec.groups
152
+ .map((group) => {
153
+ const [, enspPrefix, count, firstNumber, cliOverride] = group
154
+ const first = firstNumber ?? spec.base
155
+ const cli = cliOverride ?? cliPrefixFor(enspPrefix)
156
+ return `${cli}${first}..${first + count - 1}`
157
+ })
158
+ .join(', ')
159
+ }
160
+
161
+ /**
162
+ * Resolve a VRP interface name to the flat index eNSP stores.
163
+ *
164
+ * The flat index walks every declared group, so on an AR1220 index 2 is the first
165
+ * `Ethernet` port, not `GE0/0/2`.
166
+ *
167
+ * @param {string} model
168
+ * @param {string} port e.g. `GigabitEthernet0/0/1`
169
+ * @returns {number|null}
170
+ */
171
+ export function vrpToFlatIndex(model, port) {
172
+ const spec = MODEL_SPECS[model]
173
+ if (!spec) return null
174
+
175
+ let offset = 0
176
+ for (const group of spec.groups) {
177
+ const [, enspPrefix, count, firstNumber, cliOverride] = group
178
+ const first = firstNumber ?? spec.base
179
+ const cli = cliOverride ?? cliPrefixFor(enspPrefix)
180
+ if (port.startsWith(cli)) {
181
+ const tail = port.slice(cli.length)
182
+ if (/^\d+$/.test(tail)) {
183
+ const n = Number(tail) - first
184
+ if (n >= 0 && n < count) return offset + n
185
+ }
186
+ }
187
+ offset += count
188
+ }
189
+ return null
190
+ }
191
+
192
+ /**
193
+ * Inverse of {@link vrpToFlatIndex}.
194
+ *
195
+ * @param {string} model
196
+ * @param {number} index
197
+ * @returns {string|null}
198
+ */
199
+ export function flatIndexToVrp(model, index) {
200
+ const spec = MODEL_SPECS[model]
201
+ if (!spec) return null
202
+
203
+ let remaining = index
204
+ for (const group of spec.groups) {
205
+ const [, enspPrefix, count, firstNumber, cliOverride] = group
206
+ const first = firstNumber ?? spec.base
207
+ const cli = cliOverride ?? cliPrefixFor(enspPrefix)
208
+ if (remaining < count) return `${cli}${remaining + first}`
209
+ remaining -= count
210
+ }
211
+ return null
212
+ }