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.
Files changed (67) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +32 -0
  3. package/dist/hp6530/connection.d.ts +51 -0
  4. package/dist/hp6530/connection.js +258 -0
  5. package/dist/hp6530/constants.d.ts +64 -0
  6. package/dist/hp6530/constants.js +135 -0
  7. package/dist/hp6530/encoder.d.ts +37 -0
  8. package/dist/hp6530/encoder.js +89 -0
  9. package/dist/hp6530/parser.d.ts +45 -0
  10. package/dist/hp6530/parser.js +255 -0
  11. package/dist/hp6530/screen.d.ts +104 -0
  12. package/dist/hp6530/screen.js +252 -0
  13. package/dist/mock/mock-routes.d.ts +2 -0
  14. package/dist/mock/mock-routes.js +231 -0
  15. package/dist/protocols/hp6530-handler.d.ts +29 -0
  16. package/dist/protocols/hp6530-handler.js +64 -0
  17. package/dist/protocols/index.d.ts +11 -0
  18. package/dist/protocols/index.js +27 -0
  19. package/dist/protocols/tn3270-handler.d.ts +26 -0
  20. package/dist/protocols/tn3270-handler.js +61 -0
  21. package/dist/protocols/tn5250-handler.d.ts +26 -0
  22. package/dist/protocols/tn5250-handler.js +62 -0
  23. package/dist/protocols/types.d.ts +59 -0
  24. package/dist/protocols/types.js +7 -0
  25. package/dist/protocols/vt-handler.d.ts +30 -0
  26. package/dist/protocols/vt-handler.js +67 -0
  27. package/dist/routes.d.ts +2 -0
  28. package/dist/routes.js +141 -0
  29. package/dist/server.d.ts +1 -0
  30. package/dist/server.js +34 -0
  31. package/dist/session.d.ts +32 -0
  32. package/dist/session.js +88 -0
  33. package/dist/tn3270/connection.d.ts +31 -0
  34. package/dist/tn3270/connection.js +266 -0
  35. package/dist/tn3270/constants.d.ts +262 -0
  36. package/dist/tn3270/constants.js +261 -0
  37. package/dist/tn3270/encoder.d.ts +24 -0
  38. package/dist/tn3270/encoder.js +97 -0
  39. package/dist/tn3270/parser.d.ts +22 -0
  40. package/dist/tn3270/parser.js +284 -0
  41. package/dist/tn3270/screen.d.ts +89 -0
  42. package/dist/tn3270/screen.js +207 -0
  43. package/dist/tn5250/connection.d.ts +41 -0
  44. package/dist/tn5250/connection.js +254 -0
  45. package/dist/tn5250/constants.d.ts +128 -0
  46. package/dist/tn5250/constants.js +156 -0
  47. package/dist/tn5250/ebcdic.d.ts +10 -0
  48. package/dist/tn5250/ebcdic.js +89 -0
  49. package/dist/tn5250/encoder.d.ts +30 -0
  50. package/dist/tn5250/encoder.js +121 -0
  51. package/dist/tn5250/parser.d.ts +33 -0
  52. package/dist/tn5250/parser.js +412 -0
  53. package/dist/tn5250/screen.d.ts +80 -0
  54. package/dist/tn5250/screen.js +155 -0
  55. package/dist/vt/connection.d.ts +45 -0
  56. package/dist/vt/connection.js +229 -0
  57. package/dist/vt/constants.d.ts +97 -0
  58. package/dist/vt/constants.js +163 -0
  59. package/dist/vt/encoder.d.ts +30 -0
  60. package/dist/vt/encoder.js +55 -0
  61. package/dist/vt/parser.d.ts +36 -0
  62. package/dist/vt/parser.js +534 -0
  63. package/dist/vt/screen.d.ts +101 -0
  64. package/dist/vt/screen.js +424 -0
  65. package/dist/websocket.d.ts +6 -0
  66. package/dist/websocket.js +50 -0
  67. 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
+ }