tnz3270-node 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.cjs +5489 -0
- package/dist/index.d.cts +1305 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1305 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5384 -0
- package/package.json +61 -0
- package/src/automation/ati.ts +1057 -0
- package/src/automation/file-transfer.ts +305 -0
- package/src/core/base.ts +59 -0
- package/src/core/buffer.ts +228 -0
- package/src/core/keyboard.ts +396 -0
- package/src/core/parser.ts +422 -0
- package/src/core/screen.ts +110 -0
- package/src/core/telnet.ts +213 -0
- package/src/core/tnz-state.ts +4 -0
- package/src/core/tnz.ts +2403 -0
- package/src/index.ts +113 -0
- package/src/types.ts +348 -0
- package/src/utils/codepage.ts +281 -0
- package/src/utils/session-utils.ts +94 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import type { Tnz } from './tnz';
|
|
2
|
+
import { TnzError, bit6 } from './base';
|
|
3
|
+
import { isProtectedAttr } from './keyboard';
|
|
4
|
+
import { CMD, ORDER, SF_ID, QR_TYPE } from '../types';
|
|
5
|
+
|
|
6
|
+
export function _proc3270ds(tnz: Tnz, data: Buffer): void {
|
|
7
|
+
const op = data[0];
|
|
8
|
+
|
|
9
|
+
switch (op) {
|
|
10
|
+
case CMD.WRITE:
|
|
11
|
+
_processW(tnz, data, 0, data.length);
|
|
12
|
+
break;
|
|
13
|
+
case CMD.ERASE_WRITE:
|
|
14
|
+
_processEw(tnz, data, 0, data.length);
|
|
15
|
+
break;
|
|
16
|
+
case CMD.ERASE_WRITE_ALTERNATE:
|
|
17
|
+
_processEwa(tnz, data, 0, data.length);
|
|
18
|
+
break;
|
|
19
|
+
case CMD.ERASE_ALL_UNPROTECTED:
|
|
20
|
+
if (data.length !== 1) throw new TnzError(`EAU must be 1 byte, got ${data.length}`);
|
|
21
|
+
_processEau(tnz);
|
|
22
|
+
break;
|
|
23
|
+
case CMD.READ_BUFFER:
|
|
24
|
+
_processRb(tnz);
|
|
25
|
+
break;
|
|
26
|
+
case CMD.READ_MODIFIED:
|
|
27
|
+
_processRm(tnz);
|
|
28
|
+
break;
|
|
29
|
+
case CMD.READ_MODIFIED_ALL:
|
|
30
|
+
_processRma(tnz);
|
|
31
|
+
break;
|
|
32
|
+
case CMD.WRITE_STRUCTURED_FIELD:
|
|
33
|
+
_processWsf(tnz, data, 0, data.length);
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
if (op === 0x01) _processW(tnz, data, 0, data.length);
|
|
37
|
+
else if (op === 0x02) _processRb(tnz);
|
|
38
|
+
else if (op === 0x05) _processEw(tnz, data, 0, data.length);
|
|
39
|
+
else if (op === 0x06) _processRm(tnz);
|
|
40
|
+
else if (op === 0x0d) _processEwa(tnz, data, 0, data.length);
|
|
41
|
+
else if (op === 0x0f) _processEau(tnz);
|
|
42
|
+
else if (op === 0x11) _processWsf(tnz, data, 0, data.length);
|
|
43
|
+
else throw new TnzError(`Unknown 3270 command: 0x${op.toString(16)}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (tnz.onScreenUpdate) {
|
|
47
|
+
tnz.onScreenUpdate();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function _processWsf(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
52
|
+
let i = start + 1;
|
|
53
|
+
while (i < stop) {
|
|
54
|
+
const sfLen = data.readUInt16BE(i);
|
|
55
|
+
if (sfLen < 3) throw new TnzError(`Bad Structured Field length: ${sfLen}`);
|
|
56
|
+
|
|
57
|
+
const sfId = data[i + 2];
|
|
58
|
+
_processWsfById(tnz, sfId, data, i, i + sfLen);
|
|
59
|
+
i += sfLen;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function _processWsfById(tnz: Tnz, sfId: number, data: Buffer, start: number, stop: number): void {
|
|
64
|
+
switch (sfId) {
|
|
65
|
+
case SF_ID.READ_PARTITION:
|
|
66
|
+
_wsfReadPartition(tnz, data, start, stop);
|
|
67
|
+
break;
|
|
68
|
+
case 0x03:
|
|
69
|
+
_wsfEraseReset(tnz, data, start, stop);
|
|
70
|
+
break;
|
|
71
|
+
case 0x09:
|
|
72
|
+
_wsfSetReplyMode(tnz, data, start, stop);
|
|
73
|
+
break;
|
|
74
|
+
case 0x40:
|
|
75
|
+
_wsfOutbound3270ds(tnz, data, start, stop);
|
|
76
|
+
break;
|
|
77
|
+
case SF_ID.DDM:
|
|
78
|
+
tnz.emit('ddm', data.subarray(start, stop));
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
throw new TnzError(`Bad Structured Field ID: ${sfId}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function _wsfReadPartition(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
86
|
+
const rpType = data[start + 4];
|
|
87
|
+
if (rpType === 0x02) {
|
|
88
|
+
tnz.inop = rpType;
|
|
89
|
+
_queryReply(tnz, data, start, stop);
|
|
90
|
+
} else if (rpType === 0x03) {
|
|
91
|
+
_processRb(tnz);
|
|
92
|
+
} else {
|
|
93
|
+
throw new TnzError(`Unknown Read Partition type: ${rpType}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function _wsfEraseReset(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
98
|
+
const sfLen = stop - start;
|
|
99
|
+
if (sfLen < 4) throw new TnzError(`Erase/Reset needs 4 bytes, got ${sfLen}`);
|
|
100
|
+
const ipz = (data[start + 3] & 0x80) !== 0;
|
|
101
|
+
_eraseReset(tnz, ipz);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function _wsfSetReplyMode(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
105
|
+
const pid = data[start + 3];
|
|
106
|
+
if (pid) throw new TnzError('Non-zero PID not implemented');
|
|
107
|
+
|
|
108
|
+
const mode = data[start + 4];
|
|
109
|
+
if (mode <= 1) {
|
|
110
|
+
tnz._replyCattrs = Buffer.alloc(0);
|
|
111
|
+
} else if (mode === 2) {
|
|
112
|
+
tnz._replyCattrs = Buffer.from(data.subarray(start + 5, stop));
|
|
113
|
+
} else {
|
|
114
|
+
throw new TnzError(`Bad reply mode: ${mode}`);
|
|
115
|
+
}
|
|
116
|
+
tnz._replyMode = mode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function _wsfOutbound3270ds(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
120
|
+
const pid = data[start + 3];
|
|
121
|
+
const cmdByte = data[start + 4];
|
|
122
|
+
|
|
123
|
+
switch (cmdByte) {
|
|
124
|
+
case CMD.WRITE:
|
|
125
|
+
_processW(tnz, data, start + 4, stop);
|
|
126
|
+
break;
|
|
127
|
+
case CMD.ERASE_WRITE:
|
|
128
|
+
if (pid) throw new TnzError('Non-zero PID not implemented');
|
|
129
|
+
_processEw(tnz, data, start + 4, stop);
|
|
130
|
+
break;
|
|
131
|
+
case CMD.ERASE_WRITE_ALTERNATE:
|
|
132
|
+
if (pid) throw new TnzError('Non-zero PID not implemented');
|
|
133
|
+
_processEwa(tnz, data, start + 4, stop);
|
|
134
|
+
break;
|
|
135
|
+
case CMD.ERASE_ALL_UNPROTECTED:
|
|
136
|
+
if (stop - start !== 5) throw new TnzError(`EAU must be 5 bytes`);
|
|
137
|
+
_processEau(tnz);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
throw new TnzError(`Unknown Outbound 3270DS command: 0x${cmdByte.toString(16)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function _eraseReset(tnz: Tnz, ipz: boolean): void {
|
|
145
|
+
tnz.bufferSize = tnz.maxRow * tnz.maxCol;
|
|
146
|
+
tnz.planeDc.fill(0, 0, tnz.bufferSize);
|
|
147
|
+
tnz.planeFa.fill(0, 0, tnz.bufferSize);
|
|
148
|
+
tnz.planeEh.fill(0, 0, tnz.bufferSize);
|
|
149
|
+
tnz.planeCs.fill(0, 0, tnz.bufferSize);
|
|
150
|
+
tnz.planeFg.fill(0, 0, tnz.bufferSize);
|
|
151
|
+
tnz.planeBg.fill(0, 0, tnz.bufferSize);
|
|
152
|
+
|
|
153
|
+
(tnz)._resetPartition();
|
|
154
|
+
|
|
155
|
+
if (!ipz) {
|
|
156
|
+
tnz.curadd = 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function _processW(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
161
|
+
if (stop - start <= 1) return;
|
|
162
|
+
_processOrdersData(tnz, data, start + 2, stop);
|
|
163
|
+
(tnz)._processWcc(data[start + 1]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function _processEw(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
167
|
+
if (stop - start <= 1) return;
|
|
168
|
+
_eraseReset(tnz, false);
|
|
169
|
+
_processOrdersData(tnz, data, start + 2, stop);
|
|
170
|
+
(tnz)._processWcc(data[start + 1]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function _processEwa(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
174
|
+
if (stop - start <= 1) return;
|
|
175
|
+
_eraseReset(tnz, true);
|
|
176
|
+
_processOrdersData(tnz, data, start + 2, stop);
|
|
177
|
+
(tnz)._processWcc(data[start + 1]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function _processEau(tnz: Tnz): void {
|
|
181
|
+
tnz._eraseInput(0, 0);
|
|
182
|
+
tnz._resetMdt();
|
|
183
|
+
tnz.keyHome();
|
|
184
|
+
(tnz)._restoreKeyboard();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function _processRb(tnz: Tnz): void {
|
|
188
|
+
(tnz)._readBuffer();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function _processRm(tnz: Tnz): void {
|
|
192
|
+
tnz.sendAid(0x60, false);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function _processRma(tnz: Tnz): void {
|
|
196
|
+
tnz.sendAid(0x60, false);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function _processOrdersData(tnz: Tnz, data: Buffer, start: number, stop: number): void {
|
|
200
|
+
let p = start;
|
|
201
|
+
while (p < stop) {
|
|
202
|
+
if (ORDER_SET.has(data[p])) {
|
|
203
|
+
p = _processOrder(tnz, data, p, stop);
|
|
204
|
+
} else {
|
|
205
|
+
p = _processCharData(tnz, data, p, stop);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const ORDER_SET = new Set<number>([
|
|
211
|
+
ORDER.SF, ORDER.SBA, ORDER.IC, ORDER.PT, ORDER.RA,
|
|
212
|
+
ORDER.EUA, ORDER.GE, ORDER.SA, ORDER.SFE, ORDER.MF,
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
export function _processOrder(tnz: Tnz, data: Buffer, start: number, stop: number): number {
|
|
216
|
+
const order = data[start];
|
|
217
|
+
|
|
218
|
+
switch (order) {
|
|
219
|
+
case ORDER.SF:
|
|
220
|
+
if (start + 1 >= stop) throw new TnzError('SF needs 1 byte');
|
|
221
|
+
tnz.planeFa[tnz.curadd] = bit6(data[start + 1]);
|
|
222
|
+
tnz.planeDc[tnz.curadd] = 0;
|
|
223
|
+
tnz.planeEh[tnz.curadd] = 0;
|
|
224
|
+
tnz.planeCs[tnz.curadd] = 0;
|
|
225
|
+
tnz.planeFg[tnz.curadd] = 0;
|
|
226
|
+
tnz.planeBg[tnz.curadd] = 0;
|
|
227
|
+
tnz.curadd = (tnz.curadd + 1) % tnz.bufferSize;
|
|
228
|
+
return start + 2;
|
|
229
|
+
|
|
230
|
+
case ORDER.SBA:
|
|
231
|
+
if (start + 2 >= stop) throw new TnzError('SBA needs 2 bytes');
|
|
232
|
+
tnz.curadd = tnz.addressDecode(data, start + 1);
|
|
233
|
+
return start + 3;
|
|
234
|
+
|
|
235
|
+
case ORDER.IC:
|
|
236
|
+
return start + 1;
|
|
237
|
+
|
|
238
|
+
case ORDER.PT:
|
|
239
|
+
tnz.curadd = tnz._tab(tnz.curadd);
|
|
240
|
+
return start + 1;
|
|
241
|
+
|
|
242
|
+
case ORDER.RA:
|
|
243
|
+
if (start + 3 >= stop) throw new TnzError('RA needs 3 bytes');
|
|
244
|
+
const ea = tnz.addressDecode(data, start + 1);
|
|
245
|
+
const ch = data[start + 3];
|
|
246
|
+
if (ORDER_SET.has(ch) || isProtectedAttr(ch)) {
|
|
247
|
+
throw new TnzError('Invalid character for RA');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let rlen = ea - tnz.curadd;
|
|
251
|
+
if (rlen <= 0) rlen += tnz.bufferSize;
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < rlen; i++) {
|
|
254
|
+
const addr = (tnz.curadd + i) % tnz.bufferSize;
|
|
255
|
+
tnz.planeDc[addr] = ch;
|
|
256
|
+
tnz.planeFa[addr] = 0;
|
|
257
|
+
tnz.planeEh[addr] = 0;
|
|
258
|
+
tnz.planeCs[addr] = 0;
|
|
259
|
+
tnz.planeFg[addr] = 0;
|
|
260
|
+
tnz.planeBg[addr] = 0;
|
|
261
|
+
}
|
|
262
|
+
tnz.curadd = ea;
|
|
263
|
+
return start + 4;
|
|
264
|
+
|
|
265
|
+
case ORDER.EUA:
|
|
266
|
+
if (start + 2 >= stop) throw new TnzError('EUA needs 2 bytes');
|
|
267
|
+
const euaAddr = tnz.addressDecode(data, start + 1);
|
|
268
|
+
tnz._eraseInput(tnz.curadd, euaAddr);
|
|
269
|
+
tnz.curadd = euaAddr;
|
|
270
|
+
return start + 3;
|
|
271
|
+
|
|
272
|
+
case ORDER.SA:
|
|
273
|
+
if (start + 2 >= stop) throw new TnzError('SA needs 2 bytes');
|
|
274
|
+
tnz._processSa(data[start + 1], data[start + 2]);
|
|
275
|
+
return start + 3;
|
|
276
|
+
|
|
277
|
+
case ORDER.SFE:
|
|
278
|
+
if (start + 1 >= stop) throw new TnzError('SFE needs 1 byte');
|
|
279
|
+
const numPairs = data[start + 1];
|
|
280
|
+
if (start + 1 + numPairs * 2 >= stop) throw new TnzError('SFE length mismatch');
|
|
281
|
+
|
|
282
|
+
const sfeAddr = tnz.curadd;
|
|
283
|
+
tnz.planeFa[sfeAddr] = 0;
|
|
284
|
+
tnz.planeDc[sfeAddr] = 0;
|
|
285
|
+
tnz.planeEh[sfeAddr] = 0;
|
|
286
|
+
tnz.planeCs[sfeAddr] = 0;
|
|
287
|
+
tnz.planeFg[sfeAddr] = 0;
|
|
288
|
+
tnz.planeBg[sfeAddr] = 0;
|
|
289
|
+
|
|
290
|
+
for (let i = 0; i < numPairs; i++) {
|
|
291
|
+
const type = data[start + 2 + i * 2];
|
|
292
|
+
const value = data[start + 3 + i * 2];
|
|
293
|
+
if (type === 0xc0) {
|
|
294
|
+
tnz.planeFa[sfeAddr] = bit6(value);
|
|
295
|
+
} else {
|
|
296
|
+
tnz._processSa(type, value, sfeAddr);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
tnz.curadd = (sfeAddr + 1) % tnz.bufferSize;
|
|
300
|
+
return start + 2 + numPairs * 2;
|
|
301
|
+
|
|
302
|
+
case ORDER.MF:
|
|
303
|
+
if (start + 1 >= stop) throw new TnzError('MF needs 1 byte');
|
|
304
|
+
const mfPairs = data[start + 1];
|
|
305
|
+
if (start + 1 + mfPairs * 2 >= stop) throw new TnzError('MF length mismatch');
|
|
306
|
+
const mfAddr = tnz.curadd;
|
|
307
|
+
for (let i = 0; i < mfPairs; i++) {
|
|
308
|
+
const type = data[start + 2 + i * 2];
|
|
309
|
+
const value = data[start + 3 + i * 2];
|
|
310
|
+
if (type === 0xc0) {
|
|
311
|
+
tnz.planeFa[mfAddr] = bit6(value);
|
|
312
|
+
} else {
|
|
313
|
+
tnz._processSa(type, value, mfAddr);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return start + 2 + mfPairs * 2;
|
|
317
|
+
|
|
318
|
+
case ORDER.GE:
|
|
319
|
+
if (start + 1 >= stop) throw new TnzError('GE needs 1 byte');
|
|
320
|
+
const geAddr = tnz.curadd;
|
|
321
|
+
tnz.planeDc[geAddr] = data[start + 1];
|
|
322
|
+
tnz.planeCs[geAddr] = 0xf1;
|
|
323
|
+
tnz.curadd = (geAddr + 1) % tnz.bufferSize;
|
|
324
|
+
return start + 2;
|
|
325
|
+
|
|
326
|
+
default:
|
|
327
|
+
throw new TnzError(`Unknown order: 0x${order.toString(16)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function _processCharData(tnz: Tnz, data: Buffer, start: number, stop: number): number {
|
|
332
|
+
let p = start;
|
|
333
|
+
while (p < stop && !ORDER_SET.has(data[p])) {
|
|
334
|
+
const addr = tnz.curadd;
|
|
335
|
+
tnz.planeDc[addr] = data[p];
|
|
336
|
+
tnz.planeCs[addr] = 0; // default character set
|
|
337
|
+
tnz.planeEh[addr] = 0; // default highlighting
|
|
338
|
+
tnz.planeFg[addr] = 0; // default foreground
|
|
339
|
+
tnz.planeBg[addr] = 0; // default background
|
|
340
|
+
tnz.curadd = (addr + 1) % tnz.bufferSize;
|
|
341
|
+
p++;
|
|
342
|
+
}
|
|
343
|
+
return p;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function _queryReply(tnz: Tnz, _req: Buffer, _start: number, _stop: number): void {
|
|
347
|
+
const parts: Buffer[] = [];
|
|
348
|
+
|
|
349
|
+
const summaryBuf = Buffer.from([
|
|
350
|
+
0x80,
|
|
351
|
+
0x80,
|
|
352
|
+
QR_TYPE.USABLE_AREA,
|
|
353
|
+
QR_TYPE.CHARACTER_SETS,
|
|
354
|
+
QR_TYPE.HIGHLIGHT,
|
|
355
|
+
QR_TYPE.REPLY_MODES,
|
|
356
|
+
QR_TYPE.DDM,
|
|
357
|
+
QR_TYPE.IMPLICIT_PARTITION,
|
|
358
|
+
]);
|
|
359
|
+
_addQueryReplyField(parts, summaryBuf);
|
|
360
|
+
|
|
361
|
+
const usableAreaBuf = Buffer.alloc(20);
|
|
362
|
+
usableAreaBuf[0] = QR_TYPE.USABLE_AREA;
|
|
363
|
+
usableAreaBuf[1] = 0x01;
|
|
364
|
+
usableAreaBuf[2] = 0x00;
|
|
365
|
+
usableAreaBuf.writeUInt16BE(1, 3);
|
|
366
|
+
usableAreaBuf.writeUInt16BE(1, 5);
|
|
367
|
+
usableAreaBuf.writeUInt16BE(tnz.amaxRow, 7);
|
|
368
|
+
usableAreaBuf.writeUInt16BE(tnz.amaxCol, 9);
|
|
369
|
+
usableAreaBuf.writeUInt16BE(0, 11);
|
|
370
|
+
usableAreaBuf.writeUInt16BE(1, 13);
|
|
371
|
+
usableAreaBuf.writeUInt16BE(tnz.amaxRow, 15);
|
|
372
|
+
usableAreaBuf.writeUInt16BE(tnz.amaxCol, 17);
|
|
373
|
+
_addQueryReplyField(parts, usableAreaBuf.subarray(0, 19));
|
|
374
|
+
|
|
375
|
+
const charSetsBuf = Buffer.from([
|
|
376
|
+
QR_TYPE.CHARACTER_SETS, 0x82, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
377
|
+
0x00, 0x00, tnz.cs00 >> 8, tnz.cs00 & 0xff, tnz.cp00 >> 8, tnz.cp00 & 0xff, 0x01, 0x00,
|
|
378
|
+
0x00, 0xf1, tnz.csF1 >> 8, tnz.csF1 & 0xff, tnz.cpF1 >> 8, tnz.cpF1 & 0xff, 0x01, 0x00,
|
|
379
|
+
]);
|
|
380
|
+
_addQueryReplyField(parts, charSetsBuf);
|
|
381
|
+
|
|
382
|
+
const ddmBuf = Buffer.alloc(9);
|
|
383
|
+
ddmBuf[0] = QR_TYPE.DDM;
|
|
384
|
+
ddmBuf[1] = 0x00;
|
|
385
|
+
ddmBuf[2] = 0x00;
|
|
386
|
+
ddmBuf.writeUInt16BE(tnz._limin, 3);
|
|
387
|
+
ddmBuf.writeUInt16BE(tnz._limout, 5);
|
|
388
|
+
ddmBuf[7] = 0x01;
|
|
389
|
+
ddmBuf[8] = 0x01;
|
|
390
|
+
_addQueryReplyField(parts, ddmBuf);
|
|
391
|
+
|
|
392
|
+
const replyModesBuf = Buffer.from([
|
|
393
|
+
QR_TYPE.REPLY_MODES, 0x00, 0x01, 0x02,
|
|
394
|
+
]);
|
|
395
|
+
_addQueryReplyField(parts, replyModesBuf);
|
|
396
|
+
|
|
397
|
+
const highlightBuf = Buffer.from([
|
|
398
|
+
QR_TYPE.HIGHLIGHT, 0x04, 0x00, 0xf1, 0xf2, 0xf4, 0xf8,
|
|
399
|
+
]);
|
|
400
|
+
_addQueryReplyField(parts, highlightBuf);
|
|
401
|
+
|
|
402
|
+
const implicitBuf = Buffer.alloc(13);
|
|
403
|
+
implicitBuf[0] = QR_TYPE.IMPLICIT_PARTITION;
|
|
404
|
+
implicitBuf[1] = 0x00;
|
|
405
|
+
implicitBuf[2] = 0x00;
|
|
406
|
+
implicitBuf[3] = 0x0b;
|
|
407
|
+
implicitBuf[4] = 0x01;
|
|
408
|
+
implicitBuf[5] = 0x00;
|
|
409
|
+
implicitBuf[6] = 0x00;
|
|
410
|
+
implicitBuf.writeUInt16BE(tnz.maxRow, 7);
|
|
411
|
+
implicitBuf.writeUInt16BE(tnz.maxCol, 9);
|
|
412
|
+
_addQueryReplyField(parts, implicitBuf.subarray(0, 11));
|
|
413
|
+
|
|
414
|
+
const finalPayload = Buffer.concat([Buffer.from([0x88]), ...parts]);
|
|
415
|
+
tnz.send3270Data(finalPayload);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function _addQueryReplyField(parts: Buffer[], payload: Buffer): void {
|
|
419
|
+
const lenHeader = Buffer.alloc(2);
|
|
420
|
+
lenHeader.writeUInt16BE(payload.length + 2, 0);
|
|
421
|
+
parts.push(Buffer.concat([lenHeader, payload]));
|
|
422
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Tnz } from './tnz';
|
|
2
|
+
import * as bufUtil from './buffer';
|
|
3
|
+
|
|
4
|
+
export function _translateDcToC(buf: Buffer): Buffer {
|
|
5
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
6
|
+
for (let i = 0; i < buf.length; i++) {
|
|
7
|
+
const b = buf[i];
|
|
8
|
+
if (b === 0x00 || b === 0x0c || b === 0x0d || b === 0x15 || b === 0x19 || b === 0xff) {
|
|
9
|
+
out[i] = 0x40; // EBCDIC space
|
|
10
|
+
} else {
|
|
11
|
+
out[i] = b;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function _translateOrds(str: string): string {
|
|
18
|
+
let result = '';
|
|
19
|
+
for (const ch of str) {
|
|
20
|
+
const cp = ch.codePointAt(0)!;
|
|
21
|
+
if (cp === 0x1a) {
|
|
22
|
+
result += '\u2218'; // solid circle
|
|
23
|
+
} else if (cp === 0x1c) {
|
|
24
|
+
result += '\u2611'; // check mark
|
|
25
|
+
} else if (cp === 0x1e) {
|
|
26
|
+
result += '\u2612'; // x mark
|
|
27
|
+
} else {
|
|
28
|
+
result += ch;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function* _iterCsAddr(tnz: Tnz, saddr: number, eaddr: number): Generator<number> {
|
|
35
|
+
const planeCs = tnz.planeCs;
|
|
36
|
+
const bufSize = tnz.bufferSize;
|
|
37
|
+
|
|
38
|
+
if (saddr === eaddr) {
|
|
39
|
+
let pos = saddr;
|
|
40
|
+
let curVal = planeCs[pos];
|
|
41
|
+
for (let i = 1; i < bufSize; i++) {
|
|
42
|
+
pos = (pos + 1) % bufSize;
|
|
43
|
+
if (planeCs[pos] !== curVal) {
|
|
44
|
+
yield pos;
|
|
45
|
+
curVal = planeCs[pos];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
yield eaddr;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let len: number;
|
|
53
|
+
if (eaddr > saddr) {
|
|
54
|
+
len = eaddr - saddr;
|
|
55
|
+
} else {
|
|
56
|
+
len = bufSize - saddr + eaddr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let pos = saddr;
|
|
60
|
+
let curVal = planeCs[pos];
|
|
61
|
+
for (let i = 1; i < len; i++) {
|
|
62
|
+
pos = (pos + 1) % bufSize;
|
|
63
|
+
if (planeCs[pos] !== curVal) {
|
|
64
|
+
yield pos;
|
|
65
|
+
curVal = planeCs[pos];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
yield eaddr;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function scrstr(tnz: Tnz, saddr = 0, eaddr = 0, rstrip?: boolean): string {
|
|
72
|
+
if (rstrip === undefined) {
|
|
73
|
+
rstrip = saddr === 0 && eaddr === 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const parts: string[] = [];
|
|
77
|
+
let addr0 = saddr;
|
|
78
|
+
for (const addr1 of _iterCsAddr(tnz, saddr, eaddr)) {
|
|
79
|
+
const raw = bufUtil.rcba(tnz.planeDc, addr0, addr1);
|
|
80
|
+
const translated = _translateDcToC(Buffer.from(raw));
|
|
81
|
+
const csIdx = tnz.planeCs[addr0];
|
|
82
|
+
|
|
83
|
+
if (csIdx === 0xf1 && tnz._codecF1) {
|
|
84
|
+
parts.push(tnz._codecF1.decode(translated));
|
|
85
|
+
} else {
|
|
86
|
+
parts.push(tnz.codec.decode(translated));
|
|
87
|
+
}
|
|
88
|
+
addr0 = addr1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let str = _translateOrds(parts.join(''));
|
|
92
|
+
|
|
93
|
+
if (!rstrip) {
|
|
94
|
+
return str;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const maxCol = tnz.maxCol;
|
|
98
|
+
const rows: string[] = [];
|
|
99
|
+
for (let i = 0; i < tnz.bufferSize; i += maxCol) {
|
|
100
|
+
const end = Math.min(i + maxCol, str.length);
|
|
101
|
+
rows.push(str.slice(i, end).trimEnd());
|
|
102
|
+
}
|
|
103
|
+
rows.push('');
|
|
104
|
+
return rows.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function scrhas(tnz: Tnz, text: string, saddr = 0): boolean {
|
|
108
|
+
const fullText = scrstr(tnz, saddr, saddr, false);
|
|
109
|
+
return fullText.includes(text);
|
|
110
|
+
}
|