green-screen-proxy 0.3.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +32 -0
- package/dist/hp6530/connection.d.ts +51 -0
- package/dist/hp6530/connection.js +258 -0
- package/dist/hp6530/constants.d.ts +64 -0
- package/dist/hp6530/constants.js +135 -0
- package/dist/hp6530/encoder.d.ts +37 -0
- package/dist/hp6530/encoder.js +89 -0
- package/dist/hp6530/parser.d.ts +45 -0
- package/dist/hp6530/parser.js +255 -0
- package/dist/hp6530/screen.d.ts +104 -0
- package/dist/hp6530/screen.js +252 -0
- package/dist/mock/mock-routes.d.ts +2 -0
- package/dist/mock/mock-routes.js +231 -0
- package/dist/protocols/hp6530-handler.d.ts +29 -0
- package/dist/protocols/hp6530-handler.js +64 -0
- package/dist/protocols/index.d.ts +11 -0
- package/dist/protocols/index.js +27 -0
- package/dist/protocols/tn3270-handler.d.ts +26 -0
- package/dist/protocols/tn3270-handler.js +61 -0
- package/dist/protocols/tn5250-handler.d.ts +26 -0
- package/dist/protocols/tn5250-handler.js +62 -0
- package/dist/protocols/types.d.ts +59 -0
- package/dist/protocols/types.js +7 -0
- package/dist/protocols/vt-handler.d.ts +30 -0
- package/dist/protocols/vt-handler.js +67 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +141 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +34 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +88 -0
- package/dist/tn3270/connection.d.ts +31 -0
- package/dist/tn3270/connection.js +266 -0
- package/dist/tn3270/constants.d.ts +262 -0
- package/dist/tn3270/constants.js +261 -0
- package/dist/tn3270/encoder.d.ts +24 -0
- package/dist/tn3270/encoder.js +97 -0
- package/dist/tn3270/parser.d.ts +22 -0
- package/dist/tn3270/parser.js +284 -0
- package/dist/tn3270/screen.d.ts +89 -0
- package/dist/tn3270/screen.js +207 -0
- package/dist/tn5250/connection.d.ts +41 -0
- package/dist/tn5250/connection.js +254 -0
- package/dist/tn5250/constants.d.ts +128 -0
- package/dist/tn5250/constants.js +156 -0
- package/dist/tn5250/ebcdic.d.ts +10 -0
- package/dist/tn5250/ebcdic.js +89 -0
- package/dist/tn5250/encoder.d.ts +30 -0
- package/dist/tn5250/encoder.js +121 -0
- package/dist/tn5250/parser.d.ts +33 -0
- package/dist/tn5250/parser.js +412 -0
- package/dist/tn5250/screen.d.ts +80 -0
- package/dist/tn5250/screen.js +155 -0
- package/dist/vt/connection.d.ts +45 -0
- package/dist/vt/connection.js +229 -0
- package/dist/vt/constants.d.ts +97 -0
- package/dist/vt/constants.js +163 -0
- package/dist/vt/encoder.d.ts +30 -0
- package/dist/vt/encoder.js +55 -0
- package/dist/vt/parser.d.ts +36 -0
- package/dist/vt/parser.js +534 -0
- package/dist/vt/screen.d.ts +101 -0
- package/dist/vt/screen.js +424 -0
- package/dist/websocket.d.ts +6 -0
- package/dist/websocket.js +50 -0
- package/package.json +57 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { TELNET, TERMINAL_TYPE } from './constants.js';
|
|
4
|
+
/**
|
|
5
|
+
* Manages raw TCP socket to IBM i, handles Telnet negotiation,
|
|
6
|
+
* and extracts 5250 data records (delimited by IAC EOR).
|
|
7
|
+
*/
|
|
8
|
+
export class TN5250Connection extends EventEmitter {
|
|
9
|
+
socket = null;
|
|
10
|
+
host = '';
|
|
11
|
+
port = 23;
|
|
12
|
+
connected = false;
|
|
13
|
+
recvBuffer = Buffer.alloc(0);
|
|
14
|
+
negotiationDone = false;
|
|
15
|
+
get isConnected() {
|
|
16
|
+
return this.connected;
|
|
17
|
+
}
|
|
18
|
+
get remoteHost() {
|
|
19
|
+
return this.host;
|
|
20
|
+
}
|
|
21
|
+
get remotePort() {
|
|
22
|
+
return this.port;
|
|
23
|
+
}
|
|
24
|
+
connect(host, port) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
if (this.socket) {
|
|
27
|
+
this.disconnect();
|
|
28
|
+
}
|
|
29
|
+
this.host = host;
|
|
30
|
+
this.port = port;
|
|
31
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
32
|
+
this.negotiationDone = false;
|
|
33
|
+
this.socket = new net.Socket();
|
|
34
|
+
this.socket.setTimeout(30000);
|
|
35
|
+
const onError = (err) => {
|
|
36
|
+
this.cleanup();
|
|
37
|
+
reject(err);
|
|
38
|
+
};
|
|
39
|
+
this.socket.once('error', onError);
|
|
40
|
+
this.socket.connect(port, host, () => {
|
|
41
|
+
this.connected = true;
|
|
42
|
+
this.socket.removeListener('error', onError);
|
|
43
|
+
this.socket.on('error', (err) => {
|
|
44
|
+
this.emit('error', err);
|
|
45
|
+
this.cleanup();
|
|
46
|
+
});
|
|
47
|
+
this.socket.on('close', () => {
|
|
48
|
+
this.cleanup();
|
|
49
|
+
this.emit('disconnected');
|
|
50
|
+
});
|
|
51
|
+
this.socket.on('timeout', () => {
|
|
52
|
+
this.emit('error', new Error('Connection timeout'));
|
|
53
|
+
});
|
|
54
|
+
this.socket.on('data', (data) => this.onData(data));
|
|
55
|
+
this.emit('connected');
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
disconnect() {
|
|
61
|
+
if (this.socket) {
|
|
62
|
+
this.socket.destroy();
|
|
63
|
+
this.cleanup();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Send raw bytes over the socket */
|
|
67
|
+
sendRaw(data) {
|
|
68
|
+
if (this.socket && this.connected) {
|
|
69
|
+
this.socket.write(data);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
cleanup() {
|
|
73
|
+
this.connected = false;
|
|
74
|
+
if (this.socket) {
|
|
75
|
+
this.socket.removeAllListeners();
|
|
76
|
+
this.socket.destroy();
|
|
77
|
+
this.socket = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
onData(data) {
|
|
81
|
+
// Append to receive buffer
|
|
82
|
+
this.recvBuffer = Buffer.concat([this.recvBuffer, data]);
|
|
83
|
+
// Process all complete messages in the buffer
|
|
84
|
+
this.processBuffer();
|
|
85
|
+
}
|
|
86
|
+
processBuffer() {
|
|
87
|
+
while (this.recvBuffer.length > 0) {
|
|
88
|
+
// Check for Telnet commands (IAC ...)
|
|
89
|
+
if (this.recvBuffer[0] === TELNET.IAC && this.recvBuffer.length >= 2) {
|
|
90
|
+
const cmd = this.recvBuffer[1];
|
|
91
|
+
// IAC IAC = escaped 0xFF byte (part of data, not a command)
|
|
92
|
+
if (cmd === TELNET.IAC) {
|
|
93
|
+
// This is data, not a command — handled below with record extraction
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// Subnegotiation: IAC SB ... IAC SE
|
|
97
|
+
if (cmd === TELNET.SB) {
|
|
98
|
+
const seIdx = this.findSubnegEnd();
|
|
99
|
+
if (seIdx === -1)
|
|
100
|
+
return; // Wait for more data
|
|
101
|
+
const subData = this.recvBuffer.subarray(2, seIdx);
|
|
102
|
+
this.recvBuffer = this.recvBuffer.subarray(seIdx + 2); // skip IAC SE
|
|
103
|
+
this.handleSubnegotiation(subData);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// DO/DONT/WILL/WONT: 3 bytes
|
|
107
|
+
if (cmd === TELNET.DO || cmd === TELNET.DONT || cmd === TELNET.WILL || cmd === TELNET.WONT) {
|
|
108
|
+
if (this.recvBuffer.length < 3)
|
|
109
|
+
return; // Wait for option byte
|
|
110
|
+
const option = this.recvBuffer[2];
|
|
111
|
+
this.recvBuffer = this.recvBuffer.subarray(3);
|
|
112
|
+
this.handleNegotiation(cmd, option);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Other IAC commands (EOR handled in record extraction below)
|
|
116
|
+
if (cmd === TELNET.EOR) {
|
|
117
|
+
// Should not happen here at start of buffer in isolation
|
|
118
|
+
this.recvBuffer = this.recvBuffer.subarray(2);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// Unknown 2-byte IAC command, skip
|
|
122
|
+
this.recvBuffer = this.recvBuffer.subarray(2);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Extract a 5250 record: data terminated by IAC EOR
|
|
126
|
+
const recordEnd = this.findRecordEnd();
|
|
127
|
+
if (recordEnd === -1)
|
|
128
|
+
return; // Wait for more data
|
|
129
|
+
// Extract the record data (removing IAC EOR and unescaping IAC IAC)
|
|
130
|
+
const rawRecord = this.recvBuffer.subarray(0, recordEnd);
|
|
131
|
+
this.recvBuffer = this.recvBuffer.subarray(recordEnd + 2); // skip IAC EOR
|
|
132
|
+
const record = this.unescapeIAC(rawRecord);
|
|
133
|
+
if (record.length > 0) {
|
|
134
|
+
this.emit('data', record);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Find IAC SE sequence for subnegotiation end */
|
|
140
|
+
findSubnegEnd() {
|
|
141
|
+
for (let i = 2; i < this.recvBuffer.length - 1; i++) {
|
|
142
|
+
if (this.recvBuffer[i] === TELNET.IAC && this.recvBuffer[i + 1] === TELNET.SE) {
|
|
143
|
+
return i;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return -1;
|
|
147
|
+
}
|
|
148
|
+
/** Find IAC EOR (end of 5250 record) */
|
|
149
|
+
findRecordEnd() {
|
|
150
|
+
for (let i = 0; i < this.recvBuffer.length - 1; i++) {
|
|
151
|
+
if (this.recvBuffer[i] === TELNET.IAC && this.recvBuffer[i + 1] === TELNET.EOR) {
|
|
152
|
+
return i;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return -1;
|
|
156
|
+
}
|
|
157
|
+
/** Remove IAC IAC escaping from data */
|
|
158
|
+
unescapeIAC(data) {
|
|
159
|
+
const result = [];
|
|
160
|
+
for (let i = 0; i < data.length; i++) {
|
|
161
|
+
if (data[i] === TELNET.IAC && i + 1 < data.length && data[i + 1] === TELNET.IAC) {
|
|
162
|
+
result.push(TELNET.IAC);
|
|
163
|
+
i++; // skip doubled IAC
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
result.push(data[i]);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return Buffer.from(result);
|
|
170
|
+
}
|
|
171
|
+
handleNegotiation(cmd, option) {
|
|
172
|
+
switch (cmd) {
|
|
173
|
+
case TELNET.DO:
|
|
174
|
+
// Server asks us to enable something
|
|
175
|
+
if (option === TELNET.OPT_TTYPE ||
|
|
176
|
+
option === TELNET.OPT_EOR ||
|
|
177
|
+
option === TELNET.OPT_BINARY ||
|
|
178
|
+
option === TELNET.OPT_NEW_ENVIRON ||
|
|
179
|
+
option === TELNET.OPT_TN5250E) {
|
|
180
|
+
this.sendTelnet(TELNET.WILL, option);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.sendTelnet(TELNET.WONT, option);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
case TELNET.WILL:
|
|
187
|
+
// Server offers to enable something
|
|
188
|
+
if (option === TELNET.OPT_EOR ||
|
|
189
|
+
option === TELNET.OPT_BINARY ||
|
|
190
|
+
option === TELNET.OPT_TN5250E) {
|
|
191
|
+
this.sendTelnet(TELNET.DO, option);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this.sendTelnet(TELNET.DONT, option);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
case TELNET.DONT:
|
|
198
|
+
this.sendTelnet(TELNET.WONT, option);
|
|
199
|
+
break;
|
|
200
|
+
case TELNET.WONT:
|
|
201
|
+
this.sendTelnet(TELNET.DONT, option);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
handleSubnegotiation(data) {
|
|
206
|
+
if (data.length === 0)
|
|
207
|
+
return;
|
|
208
|
+
const option = data[0];
|
|
209
|
+
if (option === TELNET.OPT_TTYPE && data.length >= 2 && data[1] === TELNET.TTYPE_SEND) {
|
|
210
|
+
// Server asks for terminal type — respond with our type
|
|
211
|
+
this.sendTerminalType();
|
|
212
|
+
}
|
|
213
|
+
else if (option === TELNET.OPT_NEW_ENVIRON) {
|
|
214
|
+
// Server asks for environment variables — send empty response
|
|
215
|
+
this.sendEnviron(data);
|
|
216
|
+
}
|
|
217
|
+
else if (option === TELNET.OPT_TN5250E) {
|
|
218
|
+
// TN5250E subnegotiation — handle device name etc.
|
|
219
|
+
this.handleTN5250ESubneg(data);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
sendTerminalType() {
|
|
223
|
+
const typeStr = TERMINAL_TYPE;
|
|
224
|
+
const buf = Buffer.alloc(4 + typeStr.length + 2);
|
|
225
|
+
let i = 0;
|
|
226
|
+
buf[i++] = TELNET.IAC;
|
|
227
|
+
buf[i++] = TELNET.SB;
|
|
228
|
+
buf[i++] = TELNET.OPT_TTYPE;
|
|
229
|
+
buf[i++] = TELNET.TTYPE_IS;
|
|
230
|
+
for (let j = 0; j < typeStr.length; j++) {
|
|
231
|
+
buf[i++] = typeStr.charCodeAt(j);
|
|
232
|
+
}
|
|
233
|
+
buf[i++] = TELNET.IAC;
|
|
234
|
+
buf[i++] = TELNET.SE;
|
|
235
|
+
this.sendRaw(buf);
|
|
236
|
+
}
|
|
237
|
+
sendEnviron(data) {
|
|
238
|
+
// Send empty NEW-ENVIRON response
|
|
239
|
+
const buf = Buffer.from([
|
|
240
|
+
TELNET.IAC, TELNET.SB, TELNET.OPT_NEW_ENVIRON,
|
|
241
|
+
0x00, // IS
|
|
242
|
+
TELNET.IAC, TELNET.SE,
|
|
243
|
+
]);
|
|
244
|
+
this.sendRaw(buf);
|
|
245
|
+
}
|
|
246
|
+
handleTN5250ESubneg(data) {
|
|
247
|
+
// For TN5250E, we may need to send device info
|
|
248
|
+
// Minimal: just acknowledge. Most servers work with basic TTYPE negotiation.
|
|
249
|
+
// The actual device name negotiation via TN5250E is optional.
|
|
250
|
+
}
|
|
251
|
+
sendTelnet(cmd, option) {
|
|
252
|
+
this.sendRaw(Buffer.from([TELNET.IAC, cmd, option]));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export declare const TELNET: {
|
|
2
|
+
readonly IAC: 255;
|
|
3
|
+
readonly DONT: 254;
|
|
4
|
+
readonly DO: 253;
|
|
5
|
+
readonly WONT: 252;
|
|
6
|
+
readonly WILL: 251;
|
|
7
|
+
readonly SB: 250;
|
|
8
|
+
readonly SE: 240;
|
|
9
|
+
readonly EOR: 239;
|
|
10
|
+
readonly NOP: 241;
|
|
11
|
+
readonly OPT_BINARY: 0;
|
|
12
|
+
readonly OPT_ECHO: 1;
|
|
13
|
+
readonly OPT_SGA: 3;
|
|
14
|
+
readonly OPT_TTYPE: 24;
|
|
15
|
+
readonly OPT_EOR: 25;
|
|
16
|
+
readonly OPT_NAWS: 31;
|
|
17
|
+
readonly OPT_NEW_ENVIRON: 39;
|
|
18
|
+
readonly OPT_TN5250E: 40;
|
|
19
|
+
readonly TTYPE_IS: 0;
|
|
20
|
+
readonly TTYPE_SEND: 1;
|
|
21
|
+
};
|
|
22
|
+
export declare const RECORD_TYPE: {
|
|
23
|
+
readonly GDS: 4768;
|
|
24
|
+
};
|
|
25
|
+
export declare const OPCODE: {
|
|
26
|
+
readonly NO_OP: 0;
|
|
27
|
+
readonly INVITE: 1;
|
|
28
|
+
readonly OUTPUT: 2;
|
|
29
|
+
readonly PUT_GET: 3;
|
|
30
|
+
readonly SAVE_SCREEN: 4;
|
|
31
|
+
readonly RESTORE_SCREEN: 5;
|
|
32
|
+
readonly READ_IMMEDIATE: 6;
|
|
33
|
+
readonly RESERVED: 7;
|
|
34
|
+
readonly READ_SCREEN: 8;
|
|
35
|
+
readonly CANCEL_INVITE: 10;
|
|
36
|
+
readonly TURN_ON_MSG_LIGHT: 11;
|
|
37
|
+
readonly TURN_OFF_MSG_LIGHT: 12;
|
|
38
|
+
};
|
|
39
|
+
export declare const CMD: {
|
|
40
|
+
readonly CLEAR_UNIT: 64;
|
|
41
|
+
readonly CLEAR_FORMAT_TABLE: 80;
|
|
42
|
+
readonly CLEAR_UNIT_ALT: 32;
|
|
43
|
+
readonly WRITE_TO_DISPLAY: 17;
|
|
44
|
+
readonly WRITE_ERROR_CODE: 33;
|
|
45
|
+
readonly WRITE_ERROR_CODE_WIN: 34;
|
|
46
|
+
readonly READ_MDT_FIELDS: 82;
|
|
47
|
+
readonly READ_INPUT_FIELDS: 66;
|
|
48
|
+
readonly READ_IMMEDIATE: 114;
|
|
49
|
+
readonly WRITE_STRUCTURED_FIELD: 243;
|
|
50
|
+
readonly SAVE_SCREEN: 2;
|
|
51
|
+
readonly RESTORE_SCREEN: 18;
|
|
52
|
+
readonly ROLL: 35;
|
|
53
|
+
};
|
|
54
|
+
export declare const ORDER: {
|
|
55
|
+
readonly SBA: 17;
|
|
56
|
+
readonly IC: 19;
|
|
57
|
+
readonly MC: 20;
|
|
58
|
+
readonly RA: 2;
|
|
59
|
+
readonly EA: 3;
|
|
60
|
+
readonly SOH: 1;
|
|
61
|
+
readonly TD: 16;
|
|
62
|
+
readonly WEA: 4;
|
|
63
|
+
readonly SF: 29;
|
|
64
|
+
readonly SA: 40;
|
|
65
|
+
};
|
|
66
|
+
export declare const AID: {
|
|
67
|
+
readonly ENTER: 241;
|
|
68
|
+
readonly F1: 49;
|
|
69
|
+
readonly F2: 50;
|
|
70
|
+
readonly F3: 51;
|
|
71
|
+
readonly F4: 52;
|
|
72
|
+
readonly F5: 53;
|
|
73
|
+
readonly F6: 54;
|
|
74
|
+
readonly F7: 55;
|
|
75
|
+
readonly F8: 56;
|
|
76
|
+
readonly F9: 57;
|
|
77
|
+
readonly F10: 58;
|
|
78
|
+
readonly F11: 59;
|
|
79
|
+
readonly F12: 60;
|
|
80
|
+
readonly F13: 177;
|
|
81
|
+
readonly F14: 178;
|
|
82
|
+
readonly F15: 179;
|
|
83
|
+
readonly F16: 180;
|
|
84
|
+
readonly F17: 181;
|
|
85
|
+
readonly F18: 182;
|
|
86
|
+
readonly F19: 183;
|
|
87
|
+
readonly F20: 184;
|
|
88
|
+
readonly F21: 185;
|
|
89
|
+
readonly F22: 186;
|
|
90
|
+
readonly F23: 187;
|
|
91
|
+
readonly F24: 188;
|
|
92
|
+
readonly PAGE_UP: 244;
|
|
93
|
+
readonly PAGE_DOWN: 245;
|
|
94
|
+
readonly CLEAR: 189;
|
|
95
|
+
readonly HELP: 243;
|
|
96
|
+
readonly PRINT: 246;
|
|
97
|
+
readonly RECORD_BACKSPACE: 248;
|
|
98
|
+
readonly SYS_REQUEST: 1;
|
|
99
|
+
};
|
|
100
|
+
export declare const KEY_TO_AID: Record<string, number>;
|
|
101
|
+
export declare const FFW: {
|
|
102
|
+
readonly BYPASS: 32;
|
|
103
|
+
readonly DUP_ENABLE: 16;
|
|
104
|
+
readonly MDT: 8;
|
|
105
|
+
readonly SHIFT_MASK: 7;
|
|
106
|
+
readonly AUTO_ENTER: 128;
|
|
107
|
+
readonly FER: 64;
|
|
108
|
+
readonly MONOCASE: 32;
|
|
109
|
+
readonly MANDATORY_ENTRY: 8;
|
|
110
|
+
readonly RIGHT_ADJUST_MASK: 7;
|
|
111
|
+
};
|
|
112
|
+
export declare const ATTR: {
|
|
113
|
+
readonly NORMAL: 32;
|
|
114
|
+
readonly REVERSE: 33;
|
|
115
|
+
readonly HIGH_INTENSITY: 34;
|
|
116
|
+
readonly UNDERSCORE: 36;
|
|
117
|
+
readonly BLINK: 37;
|
|
118
|
+
readonly NON_DISPLAY: 39;
|
|
119
|
+
readonly COLUMN_SEPARATOR: 35;
|
|
120
|
+
};
|
|
121
|
+
export declare const SCREEN: {
|
|
122
|
+
readonly ROWS_24: 24;
|
|
123
|
+
readonly COLS_80: 80;
|
|
124
|
+
readonly ROWS_27: 27;
|
|
125
|
+
readonly COLS_132: 132;
|
|
126
|
+
};
|
|
127
|
+
export declare const TERMINAL_TYPE = "IBM-3179-2";
|
|
128
|
+
export declare const TERMINAL_TYPE_WIDE = "IBM-3477-FC";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// === Telnet Constants ===
|
|
2
|
+
export const TELNET = {
|
|
3
|
+
IAC: 0xFF, // Interpret As Command
|
|
4
|
+
DONT: 0xFE,
|
|
5
|
+
DO: 0xFD,
|
|
6
|
+
WONT: 0xFC,
|
|
7
|
+
WILL: 0xFB,
|
|
8
|
+
SB: 0xFA, // Subnegotiation Begin
|
|
9
|
+
SE: 0xF0, // Subnegotiation End
|
|
10
|
+
EOR: 0xEF, // End of Record
|
|
11
|
+
NOP: 0xF1,
|
|
12
|
+
// Telnet options
|
|
13
|
+
OPT_BINARY: 0x00,
|
|
14
|
+
OPT_ECHO: 0x01,
|
|
15
|
+
OPT_SGA: 0x03, // Suppress Go Ahead
|
|
16
|
+
OPT_TTYPE: 0x18, // Terminal Type
|
|
17
|
+
OPT_EOR: 0x19, // End of Record
|
|
18
|
+
OPT_NAWS: 0x1F, // Negotiate About Window Size
|
|
19
|
+
OPT_NEW_ENVIRON: 0x27, // New Environment
|
|
20
|
+
OPT_TN5250E: 0x28, // TN5250E (40 decimal)
|
|
21
|
+
// Terminal type subneg
|
|
22
|
+
TTYPE_IS: 0x00,
|
|
23
|
+
TTYPE_SEND: 0x01,
|
|
24
|
+
};
|
|
25
|
+
// === 5250 Data Stream Constants ===
|
|
26
|
+
// Record types
|
|
27
|
+
export const RECORD_TYPE = {
|
|
28
|
+
GDS: 0x12A0, // General Data Stream
|
|
29
|
+
};
|
|
30
|
+
// 5250 opcodes (in the header)
|
|
31
|
+
export const OPCODE = {
|
|
32
|
+
NO_OP: 0x00,
|
|
33
|
+
INVITE: 0x01,
|
|
34
|
+
OUTPUT: 0x02,
|
|
35
|
+
PUT_GET: 0x03,
|
|
36
|
+
SAVE_SCREEN: 0x04,
|
|
37
|
+
RESTORE_SCREEN: 0x05,
|
|
38
|
+
READ_IMMEDIATE: 0x06,
|
|
39
|
+
RESERVED: 0x07,
|
|
40
|
+
READ_SCREEN: 0x08,
|
|
41
|
+
CANCEL_INVITE: 0x0A,
|
|
42
|
+
TURN_ON_MSG_LIGHT: 0x0B,
|
|
43
|
+
TURN_OFF_MSG_LIGHT: 0x0C,
|
|
44
|
+
};
|
|
45
|
+
// 5250 command codes (within WTD, etc.)
|
|
46
|
+
export const CMD = {
|
|
47
|
+
CLEAR_UNIT: 0x40,
|
|
48
|
+
CLEAR_FORMAT_TABLE: 0x50,
|
|
49
|
+
CLEAR_UNIT_ALT: 0x20,
|
|
50
|
+
WRITE_TO_DISPLAY: 0x11, // WTD
|
|
51
|
+
WRITE_ERROR_CODE: 0x21,
|
|
52
|
+
WRITE_ERROR_CODE_WIN: 0x22,
|
|
53
|
+
READ_MDT_FIELDS: 0x52,
|
|
54
|
+
READ_INPUT_FIELDS: 0x42,
|
|
55
|
+
READ_IMMEDIATE: 0x72,
|
|
56
|
+
WRITE_STRUCTURED_FIELD: 0xF3,
|
|
57
|
+
SAVE_SCREEN: 0x02,
|
|
58
|
+
RESTORE_SCREEN: 0x12,
|
|
59
|
+
ROLL: 0x23,
|
|
60
|
+
};
|
|
61
|
+
// 5250 order codes
|
|
62
|
+
export const ORDER = {
|
|
63
|
+
SBA: 0x11, // Set Buffer Address
|
|
64
|
+
IC: 0x13, // Insert Cursor
|
|
65
|
+
MC: 0x14, // Move Cursor
|
|
66
|
+
RA: 0x02, // Repeat to Address
|
|
67
|
+
EA: 0x03, // Erase to Address
|
|
68
|
+
SOH: 0x01, // Start of Header
|
|
69
|
+
TD: 0x10, // Transparent Data
|
|
70
|
+
WEA: 0x04, // Write Extended Attribute
|
|
71
|
+
SF: 0x1D, // Start Field (used in field attribute)
|
|
72
|
+
SA: 0x28, // Set Attribute
|
|
73
|
+
};
|
|
74
|
+
// 5250 Aid key bytes (sent from client to host)
|
|
75
|
+
export const AID = {
|
|
76
|
+
ENTER: 0xF1,
|
|
77
|
+
F1: 0x31,
|
|
78
|
+
F2: 0x32,
|
|
79
|
+
F3: 0x33,
|
|
80
|
+
F4: 0x34,
|
|
81
|
+
F5: 0x35,
|
|
82
|
+
F6: 0x36,
|
|
83
|
+
F7: 0x37,
|
|
84
|
+
F8: 0x38,
|
|
85
|
+
F9: 0x39,
|
|
86
|
+
F10: 0x3A,
|
|
87
|
+
F11: 0x3B,
|
|
88
|
+
F12: 0x3C,
|
|
89
|
+
F13: 0xB1,
|
|
90
|
+
F14: 0xB2,
|
|
91
|
+
F15: 0xB3,
|
|
92
|
+
F16: 0xB4,
|
|
93
|
+
F17: 0xB5,
|
|
94
|
+
F18: 0xB6,
|
|
95
|
+
F19: 0xB7,
|
|
96
|
+
F20: 0xB8,
|
|
97
|
+
F21: 0xB9,
|
|
98
|
+
F22: 0xBA,
|
|
99
|
+
F23: 0xBB,
|
|
100
|
+
F24: 0xBC,
|
|
101
|
+
PAGE_UP: 0xF4, // Roll Down
|
|
102
|
+
PAGE_DOWN: 0xF5, // Roll Up
|
|
103
|
+
CLEAR: 0xBD,
|
|
104
|
+
HELP: 0xF3,
|
|
105
|
+
PRINT: 0xF6,
|
|
106
|
+
RECORD_BACKSPACE: 0xF8,
|
|
107
|
+
SYS_REQUEST: 0x01, // Attention key
|
|
108
|
+
};
|
|
109
|
+
// Map key names (from frontend) to AID bytes
|
|
110
|
+
export const KEY_TO_AID = {
|
|
111
|
+
'Enter': AID.ENTER,
|
|
112
|
+
'F1': AID.F1, 'F2': AID.F2, 'F3': AID.F3, 'F4': AID.F4,
|
|
113
|
+
'F5': AID.F5, 'F6': AID.F6, 'F7': AID.F7, 'F8': AID.F8,
|
|
114
|
+
'F9': AID.F9, 'F10': AID.F10, 'F11': AID.F11, 'F12': AID.F12,
|
|
115
|
+
'F13': AID.F13, 'F14': AID.F14, 'F15': AID.F15, 'F16': AID.F16,
|
|
116
|
+
'F17': AID.F17, 'F18': AID.F18, 'F19': AID.F19, 'F20': AID.F20,
|
|
117
|
+
'F21': AID.F21, 'F22': AID.F22, 'F23': AID.F23, 'F24': AID.F24,
|
|
118
|
+
'PageUp': AID.PAGE_UP,
|
|
119
|
+
'PageDown': AID.PAGE_DOWN,
|
|
120
|
+
'Clear': AID.CLEAR,
|
|
121
|
+
'Help': AID.HELP,
|
|
122
|
+
'Print': AID.PRINT,
|
|
123
|
+
};
|
|
124
|
+
// Field attribute bits (in the FFW - Field Format Word)
|
|
125
|
+
export const FFW = {
|
|
126
|
+
BYPASS: 0x20, // Bypass (protected/output-only)
|
|
127
|
+
DUP_ENABLE: 0x10,
|
|
128
|
+
MDT: 0x08, // Modified Data Tag
|
|
129
|
+
SHIFT_MASK: 0x07, // Data type shift bits
|
|
130
|
+
// Second byte flags
|
|
131
|
+
AUTO_ENTER: 0x80,
|
|
132
|
+
FER: 0x40, // Field Exit Required
|
|
133
|
+
MONOCASE: 0x20,
|
|
134
|
+
MANDATORY_ENTRY: 0x08,
|
|
135
|
+
RIGHT_ADJUST_MASK: 0x07,
|
|
136
|
+
};
|
|
137
|
+
// Field attribute display attributes (SA order)
|
|
138
|
+
export const ATTR = {
|
|
139
|
+
NORMAL: 0x20,
|
|
140
|
+
REVERSE: 0x21,
|
|
141
|
+
HIGH_INTENSITY: 0x22,
|
|
142
|
+
UNDERSCORE: 0x24,
|
|
143
|
+
BLINK: 0x25,
|
|
144
|
+
NON_DISPLAY: 0x27,
|
|
145
|
+
COLUMN_SEPARATOR: 0x23,
|
|
146
|
+
};
|
|
147
|
+
// Screen dimensions
|
|
148
|
+
export const SCREEN = {
|
|
149
|
+
ROWS_24: 24,
|
|
150
|
+
COLS_80: 80,
|
|
151
|
+
ROWS_27: 27,
|
|
152
|
+
COLS_132: 132,
|
|
153
|
+
};
|
|
154
|
+
// Terminal type strings for negotiation
|
|
155
|
+
export const TERMINAL_TYPE = 'IBM-3179-2';
|
|
156
|
+
export const TERMINAL_TYPE_WIDE = 'IBM-3477-FC';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Convert an EBCDIC byte to a UTF-8 character */
|
|
2
|
+
export declare function ebcdicToChar(byte: number): string;
|
|
3
|
+
/** Convert a UTF-8 character to an EBCDIC byte */
|
|
4
|
+
export declare function charToEbcdic(char: string): number;
|
|
5
|
+
/** Convert an EBCDIC buffer to a UTF-8 string */
|
|
6
|
+
export declare function ebcdicBufferToString(buf: Buffer, start?: number, length?: number): string;
|
|
7
|
+
/** Convert a UTF-8 string to an EBCDIC buffer */
|
|
8
|
+
export declare function stringToEbcdicBuffer(str: string): Buffer;
|
|
9
|
+
export declare const EBCDIC_SPACE = 64;
|
|
10
|
+
export declare const EBCDIC_NULL = 0;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// EBCDIC CCSID 37 (US/Canada) ↔ UTF-8 conversion tables
|
|
2
|
+
// EBCDIC byte → Unicode code point (CCSID 37)
|
|
3
|
+
const EBCDIC_TO_UNICODE = [
|
|
4
|
+
// 0x00-0x0F
|
|
5
|
+
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
|
6
|
+
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
|
7
|
+
// 0x10-0x1F
|
|
8
|
+
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
|
9
|
+
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
|
10
|
+
// 0x20-0x2F
|
|
11
|
+
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
|
12
|
+
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
|
13
|
+
// 0x30-0x3F
|
|
14
|
+
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
|
15
|
+
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
|
16
|
+
// 0x40-0x4F
|
|
17
|
+
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
|
18
|
+
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x007C,
|
|
19
|
+
// 0x50-0x5F
|
|
20
|
+
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
|
21
|
+
0x00EC, 0x00DF, 0x0021, 0x0024, 0x002A, 0x0029, 0x003B, 0x00AC,
|
|
22
|
+
// 0x60-0x6F
|
|
23
|
+
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
|
24
|
+
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
|
25
|
+
// 0x70-0x7F
|
|
26
|
+
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
|
27
|
+
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
|
28
|
+
// 0x80-0x8F
|
|
29
|
+
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
|
30
|
+
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
|
31
|
+
// 0x90-0x9F
|
|
32
|
+
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
|
33
|
+
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
|
34
|
+
// 0xA0-0xAF
|
|
35
|
+
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
|
36
|
+
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
|
37
|
+
// 0xB0-0xBF
|
|
38
|
+
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
|
39
|
+
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
|
40
|
+
// 0xC0-0xCF
|
|
41
|
+
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
|
42
|
+
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
|
43
|
+
// 0xD0-0xDF
|
|
44
|
+
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
|
45
|
+
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
|
46
|
+
// 0xE0-0xEF
|
|
47
|
+
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
|
48
|
+
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
|
49
|
+
// 0xF0-0xFF
|
|
50
|
+
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
|
51
|
+
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
|
52
|
+
];
|
|
53
|
+
// Build reverse lookup: Unicode code point → EBCDIC byte
|
|
54
|
+
const UNICODE_TO_EBCDIC = new Map();
|
|
55
|
+
for (let i = 0; i < 256; i++) {
|
|
56
|
+
UNICODE_TO_EBCDIC.set(EBCDIC_TO_UNICODE[i], i);
|
|
57
|
+
}
|
|
58
|
+
/** Convert an EBCDIC byte to a UTF-8 character */
|
|
59
|
+
export function ebcdicToChar(byte) {
|
|
60
|
+
const cp = EBCDIC_TO_UNICODE[byte & 0xFF];
|
|
61
|
+
return String.fromCharCode(cp);
|
|
62
|
+
}
|
|
63
|
+
/** Convert a UTF-8 character to an EBCDIC byte */
|
|
64
|
+
export function charToEbcdic(char) {
|
|
65
|
+
const cp = char.charCodeAt(0);
|
|
66
|
+
const eb = UNICODE_TO_EBCDIC.get(cp);
|
|
67
|
+
return eb !== undefined ? eb : 0x40; // default to EBCDIC space
|
|
68
|
+
}
|
|
69
|
+
/** Convert an EBCDIC buffer to a UTF-8 string */
|
|
70
|
+
export function ebcdicBufferToString(buf, start = 0, length) {
|
|
71
|
+
const end = length !== undefined ? start + length : buf.length;
|
|
72
|
+
let result = '';
|
|
73
|
+
for (let i = start; i < end; i++) {
|
|
74
|
+
result += ebcdicToChar(buf[i]);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/** Convert a UTF-8 string to an EBCDIC buffer */
|
|
79
|
+
export function stringToEbcdicBuffer(str) {
|
|
80
|
+
const buf = Buffer.alloc(str.length);
|
|
81
|
+
for (let i = 0; i < str.length; i++) {
|
|
82
|
+
buf[i] = charToEbcdic(str[i]);
|
|
83
|
+
}
|
|
84
|
+
return buf;
|
|
85
|
+
}
|
|
86
|
+
// EBCDIC space = 0x40
|
|
87
|
+
export const EBCDIC_SPACE = 0x40;
|
|
88
|
+
// EBCDIC null = 0x00
|
|
89
|
+
export const EBCDIC_NULL = 0x00;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ScreenBuffer } from './screen.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encodes client responses (aid keys + field data) into 5250 data stream
|
|
4
|
+
* for sending back to the IBM i host.
|
|
5
|
+
*/
|
|
6
|
+
export declare class TN5250Encoder {
|
|
7
|
+
private screen;
|
|
8
|
+
constructor(screen: ScreenBuffer);
|
|
9
|
+
/**
|
|
10
|
+
* Build a 5250 input response for an aid key press.
|
|
11
|
+
* Collects modified field data and builds the response record.
|
|
12
|
+
* Returns a Buffer ready to send over the TCP socket (with Telnet EOR framing).
|
|
13
|
+
*/
|
|
14
|
+
buildAidResponse(keyName: string): Buffer | null;
|
|
15
|
+
/**
|
|
16
|
+
* Build a GDS (General Data Stream) header for a response.
|
|
17
|
+
*/
|
|
18
|
+
private buildGDSHeader;
|
|
19
|
+
/**
|
|
20
|
+
* Wrap data with Telnet IAC EOR framing.
|
|
21
|
+
* Also escapes any 0xFF bytes in the data as IAC IAC.
|
|
22
|
+
*/
|
|
23
|
+
private wrapWithEOR;
|
|
24
|
+
/**
|
|
25
|
+
* Insert text at the current cursor position in the current field.
|
|
26
|
+
* Updates the screen buffer and marks the field as modified.
|
|
27
|
+
* Returns true if text was successfully inserted.
|
|
28
|
+
*/
|
|
29
|
+
insertText(text: string): boolean;
|
|
30
|
+
}
|