1 /* sim_tmxr.c: Telnet terminal multiplexor library
2
3 Copyright (c) 2001-2021, Robert M Supnik
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 Except as contained in this notice, the name of Robert M Supnik shall not be
23 used in advertising or otherwise to promote the sale, use or other dealings
24 in this Software without prior written authorization from Robert M Supnik.
25
26 Based on the original DZ11 simulator by Thord Nilson, as updated by
27 Arthur Krewat.
28
29 31-Jan-21 JDB Added a cast in "tmxr_set_lnorder" from t_addr to uint32
30 26-Oct-20 JDB Line order now supports partial connection lists
31 23-Oct-20 JDB Added tmxr_table and tmxr_post_logs
32 tmxr_set_log now takes -N switch for new file
33 19-Dec-19 JDB Added tmxr_is_extended global hook
34 19-Mar-19 JDB Added tmxr_read, tmxr_write, tmxr_show, tmxr_close
35 global hooks and associated local hook routines;
36 added tmxr_init_line, tmxr_report_connection,
37 and tmxr_disconnect_line global routines
38 06-Mar-18 RMS Revised for new IP address format in sim_sock
39 08-Jul-17 JDB Corrected misleading indentation in tmxr_poll_tx
40 06-Aug-15 JDB [4.0] Added modem control functions
41 28-Mar-15 RMS Revised to use sim_printf
42 16-Jan-11 MP Made option negotiation more reliable
43 20-Nov-08 RMS Added three new standardized SHOW routines
44 30-Sep-08 JDB Reverted tmxr_find_ldsc to original implementation
45 27-May-08 JDB Added line connection order to tmxr_poll_conn,
46 added tmxr_set_lnorder and tmxr_show_lnorder
47 14-May-08 JDB Print device and line to which connection was made
48 11-Apr-07 JDB Worked around Telnet negotiation problem with QCTerm
49 16-Aug-05 RMS Fixed C++ declaration and cast problems
50 29-Jun-05 RMS Extended tmxr_dscln to support unit array devices
51 Fixed bug in SET LOG/NOLOG
52 04-Jan-04 RMS Changed TMXR ldsc to be pointer to linedesc array
53 Added tmxr_linemsg, circular output pointers, logging
54 (from Mark Pizzolato)
55 29-Dec-03 RMS Added output stall support
56 01-Nov-03 RMS Cleaned up attach routine
57 09-Mar-03 RMS Fixed bug in SHOW CONN
58 22-Dec-02 RMS Fixed bugs in IAC+IAC receive and transmit sequences
59 Added support for received break (all from by Mark Pizzolato)
60 Fixed bug in attach
61 31-Oct-02 RMS Fixed bug in 8b (binary) support
62 22-Aug-02 RMS Added tmxr_open_master, tmxr_close_master
63 30-Dec-01 RMS Added tmxr_fstats, tmxr_dscln, renamed tmxr_fstatus
64 03-Dec-01 RMS Changed tmxr_fconns for extended SET/SHOW
65 20-Oct-01 RMS Fixed bugs in read logic (found by Thord Nilson).
66 Added tmxr_rqln, tmxr_tqln
67
68 This library includes:
69
70 tmxr_poll_conn - poll for connection
71 tmxr_reset_ln - reset line
72 tmxr_getc_ln - get character for line
73 tmxr_poll_rx - poll receive
74 tmxr_putc_ln - put character for line
75 tmxr_poll_tx - poll transmit
76 tmxr_set_modem_control_passthru - enable modem control on a multiplexer
77 tmxr_set_get_modem_bits - set and/or get a line modem bits
78 tmxr_open_master - open master connection
79 tmxr_close_master - close master connection
80 tmxr_attach - attach terminal multiplexor
81 tmxr_detach - detach terminal multiplexor
82 tmxr_ex - (null) examine
83 tmxr_dep - (null) deposit
84 tmxr_msg - send message to socket
85 tmxr_linemsg - send message to line
86 tmxr_fconns - output line connection status
87 tmxr_fstats - output line connection statistics
88 tmxr_set_log - open a log file for a terminal line
89 tmxr_set_nolog - close a log file for a terminal line
90 tmxr_post_logs - flush and optionally close all log files
91 tmxr_show_log - output log file status
92 tmxr_dscln - disconnect line (SET routine)
93 tmxr_rqln - number of available characters for line
94 tmxr_tqln - number of buffered characters for line
95 tmxr_set_lnorder - set line connection order
96 tmxr_show_lnorder - show line connection order
97 tmxr_show_summ - output the number of connections
98 tmxr_show_cstat - output device connection status or statistics
99 tmxr_show_lines - output the number of lines
100 tmxr_find_ldsc - find a line descriptor
101 tmxr_send_buffered_data - write the line data
102 tmxr_init_line - initialize the line data
103 tmxr_report_connection - report a line connection to the port
104 tmxr_disconnect_line - disconnect a line
105
106 All routines are OS-independent.
107 */
108
109 #include "sim_defs.h"
110 #include "sim_sock.h"
111 #include "sim_tmxr.h"
112 #include "scp.h"
113 #include <ctype.h>
114
115 /* Telnet protocol constants - negatives are for init'ing signed char data */
116
117 /* Commands */
118 #define TN_IAC -1 /* protocol delim */
119 #define TN_DONT -2 /* dont */
120 #define TN_DO -3 /* do */
121 #define TN_WONT -4 /* wont */
122 #define TN_WILL -5 /* will */
123 #define TN_SB -6 /* sub-option negotiation */
124 #define TN_GA -7 /* go ahead */
125 #define TN_EL -8 /* erase line */
126 #define TN_EC -9 /* erase character */
127 #define TN_AYT -10 /* are you there */
128 #define TN_AO -11 /* abort output */
129 #define TN_IP -12 /* interrupt process */
130 #define TN_BRK -13 /* break */
131 #define TN_DATAMK -14 /* data mark */
132 #define TN_NOP -15 /* no operation */
133 #define TN_SE -16 /* end sub-option negot */
134
135 /* Options */
136
137 #define TN_BIN 0 /* bin */
138 #define TN_ECHO 1 /* echo */
139 #define TN_SGA 3 /* sga */
140 #define TN_LINE 34 /* line mode */
141 #define TN_CR 015 /* carriage return */
142 #define TN_LF 012 /* line feed */
143 #define TN_NUL 000 /* null */
144
145 /* Telnet line states */
146
147 #define TNS_NORM 000 /* normal */
148 #define TNS_IAC 001 /* IAC seen */
149 #define TNS_WILL 002 /* WILL seen */
150 #define TNS_WONT 003 /* WONT seen */
151 #define TNS_SKIP 004 /* skip next cmd */
152 #define TNS_CRPAD 005 /* CR padding */
153 #define TNS_DO 006 /* DO request pending rejection */
154
155 /* Multiplexer-descriptor table.
156
157 Each device multiplexer declares a multiplexer descriptor (TMXR) structure to
158 identify the mux and its terminal lines. The structure is local to the
159 device simulator, and nothing in the DEVICE structure points to it, so there
160 is no external way of accessing the line (TMLN) structures. Access is needed
161 if the associated terminal line logs are to be flushed when a simulator stop
162 occurs and closed when the simulator exits. This is provided by a global
163 table of TMXR structures that is filled in when line logs are opened.
164 */
165
166 #define TABLE_COUNT 10 /* the number of table entries provided */
167
168 static TMXR *tmxr_table [TABLE_COUNT] = { NULL }; /* the table of multiplexer descriptors */
169
170 void tmxr_rmvrc (TMLN *lp, int32 p);
171
172 extern char sim_name[];
173 extern uint32 sim_os_msec (void);
174
175 static int32 tmxr_local_read (TMLN *lp, int32 length);
176 static int32 tmxr_local_write (TMLN *lp, int32 length);
177 static void tmxr_local_show (TMLN *lp, FILE *stream);
178 static void tmxr_local_close (TMLN *lp);
179
180 int32 (*tmxr_read) (TMLN *lp, int32 length) = tmxr_local_read;
181 int32 (*tmxr_write) (TMLN *lp, int32 length) = tmxr_local_write;
182 void (*tmxr_show) (TMLN *lp, FILE *stream) = tmxr_local_show;
183 void (*tmxr_close) (TMLN *lp) = tmxr_local_close;
184 t_bool (*tmxr_is_extended) (TMLN *lp) = NULL;
185
186 /* Poll for new connection
187
188 Called from unit service routine to test for new connection
189
190 Inputs:
191 *mp = pointer to terminal multiplexor descriptor
192 Outputs:
193 line number activated, -1 if none
194
195 If a connection order is defined for the descriptor, and the first value is
196 not < 0 to indicate the default order, then the order array is used to find
197 an open line. Otherwise, a search is made of all lines in numerical
198 sequence.
199
200 If some valid lines are to be omitted from the connection order, a value < 0
201 will appear after the last allowed line value. For example, specifying
202 connection order values of 1, 0, 2, and -1 will allow connections to lines 1,
203 0, and 2 in that order. Additional connection attempts will fail with "All
204 connections busy," even though more lines are available in the device.
205 */
206
tmxr_poll_conn(TMXR * mp)207 int32 tmxr_poll_conn (TMXR *mp)
208 {
209 SOCKET newsock;
210 TMLN *lp;
211 int32 *op, *fop;
212 int32 i, j;
213 char *ipaddr = NULL;
214
215 static char mantra[] = {
216 TN_IAC, TN_WILL, TN_LINE,
217 TN_IAC, TN_WILL, TN_SGA,
218 TN_IAC, TN_WILL, TN_ECHO,
219 TN_IAC, TN_WILL, TN_BIN,
220 TN_IAC, TN_DO, TN_BIN
221 };
222
223 newsock = sim_accept_conn (mp->master, &ipaddr); /* poll connect */
224 if (newsock != INVALID_SOCKET) { /* got a live one? */
225 fop = op = mp->lnorder; /* get line connection order list pointer */
226 i = mp->lines; /* play it safe in case lines == 0 */
227
228 for (j = 0; j < mp->lines; j++, i++) { /* find the next available line */
229 if (fop == NULL || *fop < 0) /* if the first list entry is undefined or defaulted */
230 i = j; /* then use the next sequential line */
231
232 else if (*op >= 0 && *op < mp->lines) /* otherwise if the line number is legal */
233 i = *op++; /* then use the next listed line */
234
235 else { /* otherwise the list entry is invalid */
236 i = mp->lines; /* so abandon the search now */
237 break; /* and report that no lines are free */
238 }
239
240 lp = mp->ldsc + i; /* get pointer to line descriptor */
241 if (lp->conn == 0) /* is the line available? */
242 break; /* yes, so stop search */
243 }
244
245 if (i >= mp->lines) { /* all busy? */
246 tmxr_msg (newsock, "All connections busy\r\n");
247 sim_close_sock (newsock);
248 }
249 else {
250 lp = mp->ldsc + i; /* get line desc */
251 lp->conn = newsock; /* record connection */
252 lp->ipad = ipaddr; /* ip address */
253 lp->cnms = sim_os_msec (); /* time of conn */
254 tmxr_init_line (lp); /* initialize the line */
255 sim_write_sock (newsock, mantra, sizeof (mantra));
256 tmxr_report_connection (mp, lp, i); /* report the connection */
257 return i;
258 }
259 } /* end if newsock */
260 return -1;
261 }
262
263 /* Reset line */
264
tmxr_reset_ln(TMLN * lp)265 void tmxr_reset_ln (TMLN *lp)
266 {
267 if (lp->txlog) /* dump log */
268 fflush (lp->txlog);
269 tmxr_send_buffered_data (lp); /* send buffered data */
270 free (lp->ipad);
271 lp->ipad = NULL;
272 tmxr_close (lp); /* reset the connection */
273 tmxr_init_line (lp); /* initialize the line */
274 lp->conn = 0; /* and clear the connection */
275 return;
276 }
277
278 /* Enable modem control pass thru
279
280 Inputs:
281 none
282
283 Output:
284 none
285
286 Implementation note:
287
288 1 Calling this API disables any actions on the part of this
289 library to directly manipulate DTR (&RTS) on serial ports.
290
291 2 Calling this API enables the tmxr_set_get_modem_bits and
292 tmxr_set_config_line APIs.
293
294 */
tmxr_clear_modem_control_passthru_state(TMXR * mp,t_bool state)295 static t_stat tmxr_clear_modem_control_passthru_state (TMXR *mp, t_bool state)
296 {
297 if (mp->master)
298 return SCPE_ALATT;
299 else
300 return SCPE_OK;
301 }
302
tmxr_set_modem_control_passthru(TMXR * mp)303 t_stat tmxr_set_modem_control_passthru (TMXR *mp)
304 {
305 return tmxr_clear_modem_control_passthru_state (mp, TRUE);
306 }
307
308 /* Manipulate the modem control bits of a specific line
309
310 Inputs:
311 *lp = pointer to terminal line descriptor
312 bits_to_set TMXR_MDM_DTR and/or TMXR_MDM_RTS as desired
313 bits_to_clear TMXR_MDM_DTR and/or TMXR_MDM_RTS as desired
314
315 Output:
316 incoming_bits if non NULL, returns the current stat of DCD,
317 RNG, CTS and DSR along with the current state
318 of DTR and RTS
319
320 Implementation note:
321
322 If a line is connected to a serial port, then these values affect
323 and reflect the state of the serial port. If the line is connected
324 to a network socket (or could be) then the network session state is
325 set, cleared and/or returned.
326 */
tmxr_set_get_modem_bits(TMLN * lp,int32 bits_to_set,int32 bits_to_clear,int32 * incoming_bits)327 t_stat tmxr_set_get_modem_bits (TMLN *lp, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)
328 {
329 int32 incoming_state;
330
331 if ((bits_to_set & ~(TMXR_MDM_OUTGOING)) || /* Assure only settable bits */
332 (bits_to_clear & ~(TMXR_MDM_OUTGOING)) ||
333 (bits_to_set & bits_to_clear)) /* and can't set and clear the same bits */
334 return SCPE_ARG;
335
336 if (lp->conn)
337 incoming_state = TMXR_MDM_DCD | TMXR_MDM_DSR;
338 else
339 incoming_state = 0;
340
341 if (incoming_bits)
342 *incoming_bits = incoming_state;
343
344 if (lp->conn && (bits_to_clear & TMXR_MDM_DTR)) { /* drop DTR? */
345 tmxr_linemsg (lp, "\r\nDisconnected from the ");
346 tmxr_linemsg (lp, sim_name);
347 tmxr_linemsg (lp, " simulator\r\n\n");
348
349 tmxr_reset_ln (lp);
350 }
351
352 return SCPE_OK;
353 }
354
355 /* Get character from specific line
356
357 Inputs:
358 *lp = pointer to terminal line descriptor
359 Output:
360 valid + char, 0 if line
361 */
362
tmxr_getc_ln(TMLN * lp)363 int32 tmxr_getc_ln (TMLN *lp)
364 {
365 int32 j, val = 0;
366 uint32 tmp;
367
368 if (lp->conn && lp->rcve) { /* conn & enb? */
369 j = lp->rxbpi - lp->rxbpr; /* # input chrs */
370 if (j) { /* any? */
371 tmp = lp->rxb[lp->rxbpr]; /* get char */
372 val = TMXR_VALID | (tmp & 0377); /* valid + chr */
373 if (lp->rbr[lp->rxbpr]) { /* break? */
374 lp->rbr[lp->rxbpr] = 0; /* clear status */
375 val = val | SCPE_BREAK;
376 }
377 lp->rxbpr = lp->rxbpr + 1; /* adv pointer */
378 }
379 } /* end if conn */
380 if (lp->rxbpi == lp->rxbpr) /* empty? zero ptrs */
381 lp->rxbpi = lp->rxbpr = 0;
382 return val;
383 }
384
385 /* Poll for input
386
387 Inputs:
388 *mp = pointer to terminal multiplexor descriptor
389 Outputs: none
390 */
391
tmxr_poll_rx(TMXR * mp)392 void tmxr_poll_rx (TMXR *mp)
393 {
394 int32 i, nbytes, j;
395 TMLN *lp;
396
397 for (i = 0; i < mp->lines; i++) { /* loop thru lines */
398 lp = mp->ldsc + i; /* get line desc */
399 if (!lp->conn || !lp->rcve) /* skip if !conn */
400 continue;
401
402 nbytes = 0;
403 if (lp->rxbpi == 0) /* need input? */
404 nbytes = tmxr_read (lp, /* yes, read */
405 TMXR_MAXBUF - TMXR_GUARD); /* leave spc for Telnet cruft */
406 else if (lp->tsta) /* in Telnet seq? */
407 nbytes = tmxr_read (lp, /* yes, read to end */
408 TMXR_MAXBUF - lp->rxbpi);
409 if (nbytes < 0) /* closed? reset ln */
410 tmxr_reset_ln (lp);
411 else if (nbytes > 0) { /* if data rcvd */
412 j = lp->rxbpi; /* start of data */
413 lp->rxbpi = lp->rxbpi + nbytes; /* adv pointers */
414 lp->rxcnt = lp->rxcnt + nbytes;
415
416 if (tmxr_is_extended != NULL /* if the line */
417 && tmxr_is_extended (lp) == TRUE) /* is extended */
418 continue; /* then skip the Telnet processing */
419
420 memset (&lp->rbr[j], 0, nbytes); /* clear status */
421
422 /* Examine new data, remove TELNET cruft before making input available */
423
424 for (; j < lp->rxbpi; ) { /* loop thru char */
425 signed char tmp = lp->rxb[j]; /* get char */
426 switch (lp->tsta) { /* case tlnt state */
427
428 case TNS_NORM: /* normal */
429 if (tmp == TN_IAC) { /* IAC? */
430 lp->tsta = TNS_IAC; /* change state */
431 tmxr_rmvrc (lp, j); /* remove char */
432 break;
433 }
434 if ((tmp == TN_CR) && lp->dstb) /* CR, no bin */
435 lp->tsta = TNS_CRPAD; /* skip pad char */
436 j = j + 1; /* advance j */
437 break;
438
439 case TNS_IAC: /* IAC prev */
440 if (tmp == TN_IAC) { /* IAC + IAC */
441 lp->tsta = TNS_NORM; /* treat as normal */
442 j = j + 1; /* advance j */
443 break; /* keep IAC */
444 }
445 if (tmp == TN_BRK) { /* IAC + BRK? */
446 lp->tsta = TNS_NORM; /* treat as normal */
447 lp->rxb[j] = 0; /* char is null */
448 lp->rbr[j] = 1; /* flag break */
449 j = j + 1; /* advance j */
450 break;
451 }
452 switch (tmp) {
453 case TN_WILL: /* IAC + WILL? */
454 lp->tsta = TNS_WILL;
455 break;
456 case TN_WONT: /* IAC + WONT? */
457 lp->tsta = TNS_WONT;
458 break;
459 case TN_DO: /* IAC + DO? */
460 lp->tsta = TNS_DO;
461 break;
462 case TN_DONT: /* IAC + DONT? */
463 lp->tsta = TNS_SKIP; /* IAC + other */
464 break;
465 case TN_GA: case TN_EL: /* IAC + other 2 byte types */
466 case TN_EC: case TN_AYT:
467 case TN_AO: case TN_IP:
468 case TN_NOP:
469 lp->tsta = TNS_NORM; /* ignore */
470 break;
471 case TN_SB: /* IAC + SB sub-opt negotiation */
472 case TN_DATAMK: /* IAC + data mark */
473 case TN_SE: /* IAC + SE sub-opt end */
474 lp->tsta = TNS_NORM; /* ignore */
475 break;
476 }
477 tmxr_rmvrc (lp, j); /* remove char */
478 break;
479
480 case TNS_WILL: case TNS_WONT: /* IAC+WILL/WONT prev */
481 if (tmp == TN_BIN) { /* BIN? */
482 if (lp->tsta == TNS_WILL)
483 lp->dstb = 0;
484 else lp->dstb = 1;
485 }
486 tmxr_rmvrc (lp, j); /* remove it */
487 lp->tsta = TNS_NORM; /* next normal */
488 break;
489
490 /* Negotiation with the HP terminal emulator "QCTerm" is not working.
491 QCTerm says "WONT BIN" but sends bare CRs. RFC 854 says:
492
493 Note that "CR LF" or "CR NUL" is required in both directions
494 (in the default ASCII mode), to preserve the symmetry of the
495 NVT model. ...The protocol requires that a NUL be inserted
496 following a CR not followed by a LF in the data stream.
497
498 Until full negotiation is implemented, we work around the problem
499 by checking the character following the CR in non-BIN mode and
500 strip it only if it is LF or NUL. This should not affect
501 conforming clients.
502 */
503
504 case TNS_CRPAD: /* only LF or NUL should follow CR */
505 lp->tsta = TNS_NORM; /* next normal */
506 if ((tmp == TN_LF) || /* CR + LF ? */
507 (tmp == TN_NUL)) /* CR + NUL? */
508 tmxr_rmvrc (lp, j); /* remove it */
509 break;
510
511 case TNS_DO: /* pending DO request */
512 if (tmp == TN_BIN) { /* reject all but binary mode */
513 char accept[] = {TN_IAC, TN_WILL, TN_BIN};
514 sim_write_sock (lp->conn, accept, sizeof(accept));
515 }
516 tmxr_rmvrc (lp, j); /* remove it */
517 lp->tsta = TNS_NORM; /* next normal */
518 break;
519
520 case TNS_SKIP: default: /* skip char */
521 tmxr_rmvrc (lp, j); /* remove char */
522 lp->tsta = TNS_NORM; /* next normal */
523 break;
524 } /* end case state */
525 } /* end for char */
526 } /* end else nbytes */
527 } /* end for lines */
528 for (i = 0; i < mp->lines; i++) { /* loop thru lines */
529 lp = mp->ldsc + i; /* get line desc */
530 if (lp->rxbpi == lp->rxbpr) /* if buf empty, */
531 lp->rxbpi = lp->rxbpr = 0; /* reset pointers */
532 } /* end for */
533 return;
534 }
535
536 /* Return count of available characters for line */
537
tmxr_rqln(TMLN * lp)538 int32 tmxr_rqln (TMLN *lp)
539 {
540 return (lp->rxbpi - lp->rxbpr);
541 }
542
543 /* Remove character p (and matching status) from line l input buffer */
544
tmxr_rmvrc(TMLN * lp,int32 p)545 void tmxr_rmvrc (TMLN *lp, int32 p)
546 {
547 for ( ; p < lp->rxbpi; p++) {
548 lp->rxb[p] = lp->rxb[p + 1];
549 lp->rbr[p] = lp->rbr[p + 1];
550 }
551 lp->rbr[p] = 0; /* clear potential break from vacated slot */
552 lp->rxbpi = lp->rxbpi - 1;
553 return;
554 }
555
556 /* Store character in line buffer
557
558 Inputs:
559 *lp = pointer to line descriptor
560 chr = characters
561 Outputs:
562 status = ok, connection lost, or stall
563 */
564
tmxr_putc_ln(TMLN * lp,int32 chr)565 t_stat tmxr_putc_ln (TMLN *lp, int32 chr)
566 {
567 if (lp->txlog) /* log if available */
568 fputc (chr, lp->txlog);
569 if (lp->conn == 0) /* no conn? lost */
570 return SCPE_LOST;
571 if (tmxr_tqln (lp) < (TMXR_MAXBUF - 1)) { /* room for char (+ IAC)? */
572 lp->txb[lp->txbpi] = (char) chr; /* buffer char */
573 lp->txbpi = lp->txbpi + 1; /* adv pointer */
574 if (lp->txbpi >= TMXR_MAXBUF) /* wrap? */
575 lp->txbpi = 0;
576 if ((char) chr == TN_IAC) { /* IAC? */
577 lp->txb[lp->txbpi] = (char) chr; /* IAC + IAC */
578 lp->txbpi = lp->txbpi + 1; /* adv pointer */
579 if (lp->txbpi >= TMXR_MAXBUF) /* wrap? */
580 lp->txbpi = 0;
581 }
582 if (tmxr_tqln (lp) > (TMXR_MAXBUF - TMXR_GUARD)) /* near full? */
583 lp->xmte = 0; /* disable line */
584 return SCPE_OK; /* char sent */
585 }
586 lp->xmte = 0; /* no room, dsbl line */
587 return SCPE_STALL; /* char not sent */
588 }
589
590 /* Poll for output
591
592 Inputs:
593 *mp = pointer to terminal multiplexor descriptor
594 Outputs:
595 none
596 */
597
tmxr_poll_tx(TMXR * mp)598 void tmxr_poll_tx (TMXR *mp)
599 {
600 int32 i, nbytes;
601 TMLN *lp;
602
603 for (i = 0; i < mp->lines; i++) { /* loop thru lines */
604 lp = mp->ldsc + i; /* get line desc */
605 if (lp->conn == 0) /* skip if !conn */
606 continue;
607 nbytes = tmxr_send_buffered_data (lp); /* buffered bytes */
608 if (nbytes == 0) /* buf empty? enab line */
609 lp->xmte = 1;
610 } /* end for */
611 return;
612 }
613
614 /* Send buffered data across network
615
616 Inputs:
617 *lp = pointer to line descriptor
618 Outputs:
619 returns number of bytes still buffered
620 */
621
tmxr_send_buffered_data(TMLN * lp)622 int32 tmxr_send_buffered_data (TMLN *lp)
623 {
624 int32 nbytes, sbytes;
625
626 nbytes = tmxr_tqln(lp); /* avail bytes */
627 if (nbytes) { /* >0? write */
628 if (lp->txbpr < lp->txbpi) /* no wrap? */
629 sbytes = tmxr_write (lp, nbytes); /* write all data */
630 else
631 sbytes = tmxr_write (lp, TMXR_MAXBUF - lp->txbpr); /* write to end buf */
632
633 if (sbytes > 0) { /* ok? */
634 lp->txbpr = (lp->txbpr + sbytes); /* update remove ptr */
635 if (lp->txbpr >= TMXR_MAXBUF) /* wrap? */
636 lp->txbpr = 0;
637 lp->txcnt = lp->txcnt + sbytes; /* update counts */
638 nbytes = nbytes - sbytes;
639 }
640 if (nbytes && (lp->txbpr == 0)) { /* more data and wrap? */
641 sbytes = tmxr_write (lp, nbytes);
642 if (sbytes > 0) { /* ok */
643 lp->txbpr = (lp->txbpr + sbytes); /* update remove ptr */
644 if (lp->txbpr >= TMXR_MAXBUF) /* wrap? */
645 lp->txbpr = 0;
646 lp->txcnt = lp->txcnt + sbytes; /* update counts */
647 nbytes = nbytes - sbytes;
648 }
649 }
650 } /* end if nbytes */
651 return nbytes;
652 }
653
654 /* Return count of buffered characters for line */
655
tmxr_tqln(TMLN * lp)656 int32 tmxr_tqln (TMLN *lp)
657 {
658 return (lp->txbpi - lp->txbpr + ((lp->txbpi < lp->txbpr)? TMXR_MAXBUF: 0));
659 }
660
661 /* Open master socket */
662
tmxr_open_master(TMXR * mp,char * cptr)663 t_stat tmxr_open_master (TMXR *mp, char *cptr)
664 {
665 int32 i, port;
666 SOCKET sock;
667 TMLN *lp;
668 t_stat r;
669
670 port = (int32) get_uint (cptr, 10, 65535, &r); /* get port */
671 if ((r != SCPE_OK) || (port == 0))
672 return SCPE_ARG;
673 sock = sim_master_sock (cptr, &r); /* make master socket */
674 if (sock == INVALID_SOCKET) /* open error */
675 return SCPE_OPENERR;
676 sim_printf ("Listening on port %d (socket %d)\n", port, sock);
677 mp->port = port; /* save port */
678 mp->master = sock; /* save master socket */
679 for (i = 0; i < mp->lines; i++) { /* initialize lines */
680 lp = mp->ldsc + i;
681
682 if (tmxr_is_extended == NULL /* if the line */
683 || tmxr_is_extended (lp) == FALSE) { /* is not extended */
684 tmxr_init_line (lp); /* then initialize the line */
685 lp->conn = 0; /* and clear the connection */
686 }
687 }
688 return SCPE_OK;
689 }
690
691 /* Attach unit to master socket */
692
tmxr_attach(TMXR * mp,UNIT * uptr,char * cptr)693 t_stat tmxr_attach (TMXR *mp, UNIT *uptr, char *cptr)
694 {
695 char* tptr;
696 t_stat r;
697
698 tptr = (char *) malloc (strlen (cptr) + 1); /* get string buf */
699 if (tptr == NULL) /* no more mem? */
700 return SCPE_MEM;
701 r = tmxr_open_master (mp, cptr); /* open master socket */
702 if (r != SCPE_OK) { /* error? */
703 free (tptr); /* release buf */
704 return SCPE_OPENERR;
705 }
706 strcpy (tptr, cptr); /* copy port */
707 uptr->filename = tptr; /* save */
708 uptr->flags = uptr->flags | UNIT_ATT; /* no more errors */
709
710 if (mp->dptr == NULL) /* has device been set? */
711 mp->dptr = find_dev_from_unit (uptr); /* no, so set device now */
712
713 return SCPE_OK;
714 }
715
716 /* Close master socket */
717
tmxr_close_master(TMXR * mp)718 t_stat tmxr_close_master (TMXR *mp)
719 {
720 int32 i;
721 TMLN *lp;
722
723 for (i = 0; i < mp->lines; i++) { /* loop thru conn */
724 lp = mp->ldsc + i;
725
726 if (lp->conn /* if the line is connected */
727 && (tmxr_is_extended == NULL /* and the line */
728 || tmxr_is_extended (lp) == FALSE)) /* is not extended */
729 tmxr_disconnect_line (lp); /* then disconnect it */
730 } /* end for */
731 sim_close_sock (mp->master); /* close master socket */
732 mp->master = 0;
733 return SCPE_OK;
734 }
735
736 /* Detach unit from master socket */
737
tmxr_detach(TMXR * mp,UNIT * uptr)738 t_stat tmxr_detach (TMXR *mp, UNIT *uptr)
739 {
740 if (!(uptr->flags & UNIT_ATT)) /* attached? */
741 return SCPE_OK;
742 tmxr_close_master (mp); /* close master socket */
743 free (uptr->filename); /* free port string */
744 uptr->filename = NULL;
745 uptr->flags = uptr->flags & ~UNIT_ATT; /* not attached */
746 return SCPE_OK;
747 }
748
749 /* Stub examine and deposit */
750
tmxr_ex(t_value * vptr,t_addr addr,UNIT * uptr,int32 sw)751 t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
752 {
753 return SCPE_NOFNC;
754 }
755
tmxr_dep(t_value val,t_addr addr,UNIT * uptr,int32 sw)756 t_stat tmxr_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw)
757 {
758 return SCPE_NOFNC;
759 }
760
761 /* Output message to socket or line descriptor */
762
tmxr_msg(SOCKET sock,const char * msg)763 void tmxr_msg (SOCKET sock, const char *msg)
764 {
765 if (sock)
766 sim_write_sock (sock, msg, strlen (msg));
767 return;
768 }
769
tmxr_linemsg(TMLN * lp,const char * msg)770 void tmxr_linemsg (TMLN *lp, const char *msg)
771 {
772 int32 len;
773
774 for (len = strlen (msg); len > 0; --len)
775 tmxr_putc_ln (lp, *msg++);
776 return;
777 }
778
779 /* Print connections - used only in named SHOW command */
780
tmxr_fconns(FILE * st,TMLN * lp,int32 ln)781 void tmxr_fconns (FILE *st, TMLN *lp, int32 ln)
782 {
783 if (ln >= 0)
784 fprintf (st, "line %d: ", ln);
785 if (lp->conn) {
786 int32 hr, mn, sc;
787 uint32 ctime;
788
789 ctime = (sim_os_msec () - lp->cnms) / 1000;
790 hr = ctime / 3600;
791 mn = (ctime / 60) % 60;
792 sc = ctime % 60;
793 tmxr_show (lp, st); /* display the port connection */
794 if (ctime)
795 fprintf (st, ", connected %02d:%02d:%02d\n", hr, mn, sc);
796 }
797 else fprintf (st, "line disconnected\n");
798 if (lp->txlog)
799 fprintf (st, "Logging to %s\n", lp->txlogname);
800 return;
801 }
802
803 /* Print statistics - used only in named SHOW command */
804
tmxr_fstats(FILE * st,TMLN * lp,int32 ln)805 void tmxr_fstats (FILE *st, TMLN *lp, int32 ln)
806 {
807 static const char *enab = "on";
808 static const char *dsab = "off";
809
810 if (ln >= 0)
811 fprintf (st, "line %d: ", ln);
812 if (lp->conn) {
813 fprintf (st, "input (%s) queued/total = %d/%d, ",
814 (lp->rcve? enab: dsab),
815 lp->rxbpi - lp->rxbpr, lp->rxcnt);
816 fprintf (st, "output (%s) queued/total = %d/%d\n",
817 (lp->xmte? enab: dsab),
818 lp->txbpi - lp->txbpr, lp->txcnt);
819 }
820 else fprintf (st, "line disconnected\n");
821 return;
822 }
823
824 /* Disconnect line */
825
tmxr_dscln(UNIT * uptr,int32 val,char * cptr,void * desc)826 t_stat tmxr_dscln (UNIT *uptr, int32 val, char *cptr, void *desc)
827 {
828 TMXR *mp = (TMXR *) desc;
829 TMLN *lp;
830 int32 ln;
831 t_stat r;
832
833 if (mp == NULL)
834 return SCPE_IERR;
835 if (val) { /* = n form */
836 if (cptr == NULL)
837 return SCPE_ARG;
838 ln = (int32) get_uint (cptr, 10, mp->lines - 1, &r);
839 if (r != SCPE_OK)
840 return SCPE_ARG;
841 lp = mp->ldsc + ln;
842 }
843 else {
844 lp = tmxr_find_ldsc (uptr, 0, mp);
845 if (lp == NULL)
846 return SCPE_IERR;
847 }
848 if (lp->conn) {
849 tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n");
850 tmxr_reset_ln (lp);
851 }
852 return SCPE_OK;
853 }
854
855 /* Enable logging for line.
856
857 This routine opens the log file whose name is specified by "cptr" for the
858 multiplexer line associated with "uptr" or, if NULL, for the line number
859 specified by "val". "desc" contains a pointer to the multiplexer descriptor.
860
861 If opening is successful, the routine then adds the descriptor pointer to the
862 table of descriptors, which is used to flush and eventually close the log
863 files.
864
865
866 Implementation notes:
867
868 1. Table entries are not removed, so a NULL entry indicates both the end of
869 the active list and the first available space.
870 */
871
tmxr_set_log(UNIT * uptr,int32 val,char * cptr,void * desc)872 t_stat tmxr_set_log (UNIT *uptr, int32 val, char *cptr, void *desc)
873 {
874 TMXR *mp = (TMXR *) desc;
875 TMLN *lp;
876 uint32 index;
877
878 if (cptr == NULL) /* no file name? */
879 return SCPE_2FARG;
880 lp = tmxr_find_ldsc (uptr, val, mp); /* find line desc */
881 if (lp == NULL)
882 return SCPE_IERR;
883 if (lp->txlog) /* close existing log */
884 tmxr_set_nolog (NULL, val, NULL, desc);
885 lp->txlogname = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc namebuf */
886 if (lp->txlogname == NULL) /* can't? */
887 return SCPE_MEM;
888 strncpy (lp->txlogname, cptr, CBUFSIZE); /* save file name */
889 if (sim_switches & SWMASK ('N')) /* if a new log file is requested */
890 lp->txlog = fopen (cptr, "wb"); /* then open an empty file for writing */
891 else /* otherwise */
892 lp->txlog = fopen (cptr, "ab"); /* open an existing file for appending */
893 if (lp->txlog == NULL) { /* error? */
894 free (lp->txlogname); /* free buffer */
895 return SCPE_OPENERR;
896 }
897 else { /* otherwise if the open succeeded */
898 for (index = 0; index < TABLE_COUNT; index++) /* loop through the table entries */
899 if (tmxr_table [index] == mp) /* if the mux descriptor is already present */
900 break; /* then we're done */
901
902 else if (tmxr_table [index] == NULL) { /* otherwise if the entry is empty */
903 tmxr_table [index] = mp; /* then store the new descriptor */
904 break; /* and we're done */
905 }
906
907 if (index == TABLE_COUNT) /* if there are no empty entries */
908 return SCPE_IERR; /* then report an internal error */
909 else /* otherwise the descriptor was */
910 return SCPE_OK; /* either already present or added */
911 }
912 }
913
914 /* Disable logging for line */
915
tmxr_set_nolog(UNIT * uptr,int32 val,char * cptr,void * desc)916 t_stat tmxr_set_nolog (UNIT *uptr, int32 val, char *cptr, void *desc)
917 {
918 TMXR *mp = (TMXR *) desc;
919 TMLN *lp;
920
921 if (cptr) /* no arguments */
922 return SCPE_2MARG;
923 lp = tmxr_find_ldsc (uptr, val, mp); /* find line desc */
924 if (lp == NULL)
925 return SCPE_IERR;
926 if (lp->txlog) { /* logging? */
927 fclose (lp->txlog); /* close log */
928 free (lp->txlogname); /* free namebuf */
929 lp->txlog = NULL;
930 lp->txlogname = NULL;
931 }
932 return SCPE_OK;
933 }
934
935 /* Flush and optionally close all log files.
936
937 This routine flushes or closes all active terminal multiplexer line logs,
938 depending on the value of the boolean parameter. The routine uses the table
939 of multiplexer descriptors constructed by the "tmxr_set_log" routine to
940 identify the active lines. The line structure array associated with each
941 descriptor is examined in sequence for active logs. Each one that is found
942 is flushed to post the contents to disc and left open, or closed and marked
943 inactive, as directed by the supplied parameter.
944
945
946 Implementation notes:
947
948 1. The table is organized so that a NULL entry indicates that no additional
949 descriptors will be found. That is, descriptors always appear at the
950 beginning of the table.
951 */
952
tmxr_post_logs(t_bool close_logs)953 void tmxr_post_logs (t_bool close_logs)
954 {
955 uint32 index;
956 int32 line, line_count;
957 TMXR *mptr;
958 TMLN *lptr;
959
960 for (index = 0; index < TABLE_COUNT; index++) /* look through the descriptor list */
961 if (tmxr_table [index] == NULL) /* until all entries */
962 break; /* have been seen */
963
964 else { /* for each active entry */
965 mptr = tmxr_table [index]; /* get the entry */
966 lptr = mptr->ldsc; /* and the line array */
967 line_count = mptr->lines; /* and the number of lines */
968
969 for (line = 0; line < line_count; line++, lptr++) /* for all lines in the array */
970 if (lptr->txlog != NULL) /* if the current line's log is active */
971 if (close_logs) /* then if closing is requested */
972 tmxr_set_nolog (NULL, line, NULL, /* then close the log */
973 (void *) mptr);
974 else /* otherwise */
975 fflush (lptr->txlog); /* flush the log and leave it open */
976 }
977
978 return;
979 }
980
981 /* Show logging status for line */
982
tmxr_show_log(FILE * st,UNIT * uptr,int32 val,void * desc)983 t_stat tmxr_show_log (FILE *st, UNIT *uptr, int32 val, void *desc)
984 {
985 TMXR *mp = (TMXR *) desc;
986 TMLN *lp;
987
988 lp = tmxr_find_ldsc (uptr, val, mp); /* find line desc */
989 if (lp == NULL)
990 return SCPE_IERR;
991 if (lp->txlog)
992 fprintf (st, "logging to %s", lp->txlogname);
993 else fprintf (st, "no logging");
994 return SCPE_OK;
995 }
996
997 /* Find line descriptor.
998
999 Note: This routine may be called with a UNIT that does not belong to the
1000 device indicated in the TMXR structure. That is, the multiplexer lines may
1001 belong to a device other than the one attached to the socket (the HP 2100 MUX
1002 device is one example). Therefore, we must look up the device from the unit
1003 at each call, rather than depending on the dptr stored in the TMXR.
1004 */
1005
tmxr_find_ldsc(UNIT * uptr,int32 val,TMXR * mp)1006 TMLN *tmxr_find_ldsc (UNIT *uptr, int32 val, TMXR *mp)
1007 {
1008 if (uptr) { /* called from SET? */
1009 DEVICE *dptr = find_dev_from_unit (uptr); /* find device */
1010 if (dptr == NULL) /* what?? */
1011 return NULL;
1012 val = (int32) (uptr - dptr->units); /* implicit line # */
1013 }
1014 if ((val < 0) || (val >= mp->lines)) /* invalid line? */
1015 return NULL;
1016 return mp->ldsc + val; /* line descriptor */
1017 }
1018
1019 /* Set the line connection order.
1020
1021 This validation routine is called to set the connection order for the
1022 multiplexer whose TMXR pointer is passed in the "desc" parameter. It parses
1023 the line order list, specified by the "cptr" parameter, of commands such as:
1024
1025 SET <dev> LINEORDER=4-7
1026 SET <dev> LINEORDER=1;5;2-4;7;ALL
1027 SET <dev> LINEORDER=ALL
1028
1029 Assuming an 8-channel multiplexer, the first form sets the connection order
1030 to line numbers 4, 5, 6, and 7. The remaining lines will not be connected; a
1031 connection attempt will be refused with "All connections busy." The second
1032 form sets the connection order to line 1, 5, 2, 3, 4, 7, 0, and 6. The
1033 trailing "ALL" parameter causes any unspecified lines to be added to the
1034 connection order in ascending value. The third form sets the order to lines
1035 0-7, which is the default order in the absence of a line connection order
1036 array.
1037
1038 The range of accepted line numbers, including those implied by "ALL", can be
1039 restricted by specifying a non-zero "val" parameter, with the upper 16 bits
1040 specifying the maximum line number, and the lower 16 bits specifying the
1041 minimum line number. If a minimum is specified but a maximum is not (i.e.,
1042 is zero), the maximum is the last line number defined by the multiplexer
1043 descriptor.
1044
1045 The "uptr" parameter is not used.
1046
1047 On entry, "cptr" points to the value portion of the command string, which may
1048 be either a semicolon-separated list of line ranges or the keyword "ALL". If
1049 "ALL" is specified, it must be the last (or only) item in the list.
1050
1051 If a line connection order array is not defined in the multiplexer
1052 descriptor, or a line range string is not present, or the optional minimum
1053 and maximum restrictions in the "val" parameter are not valid, the command is
1054 rejected. If the specified range encompasses all of the lines defined by the
1055 multiplexer, the first value of the connection order array is set to -1 to
1056 indicate sequential connection order. Otherwise, the line values in the
1057 array are set to the order specified by the command string. If fewer values
1058 are supplied than there are lines supported by the device, and the final
1059 parameter is not ALL, the remaining lines will be inaccessible and are
1060 indicated by a -1 value after the last specified value.
1061
1062 If an error occurs, the original line order is not disturbed.
1063 */
1064
tmxr_set_lnorder(UNIT * uptr,int32 val,char * cptr,void * desc)1065 t_stat tmxr_set_lnorder (UNIT *uptr, int32 val, char *cptr, void *desc)
1066 {
1067 TMXR *mp = (TMXR *) desc;
1068 char *tptr;
1069 t_addr low, high, min, max;
1070 int32 *list;
1071 t_bool *set;
1072 uint32 line, idx;
1073 t_addr lncount = (t_addr) (mp->lines - 1);
1074 t_stat result = SCPE_OK;
1075
1076 if (mp->lnorder == NULL) /* if the connection order array is not defined */
1077 return SCPE_NXPAR; /* then report a "Non-existent parameter" error */
1078
1079 else if ((cptr == NULL) || (*cptr == '\0')) /* otherwise if a line range was not supplied */
1080 return SCPE_MISVAL; /* then report a "Missing value" error */
1081
1082 else { /* otherwise */
1083 min = (t_addr) (val & 0xFFFF); /* split the restriction into */
1084 max = (t_addr) ((val >> 16) & 0xFFFF); /* minimum and maximum line numbers */
1085
1086 if (max == 0) /* if the maximum line number isn't specified */
1087 max = lncount; /* then use the defined maximum */
1088
1089 if (min > lncount || max > lncount || min > max) /* if the restriction isn't valid */
1090 return SCPE_IERR; /* then report an "Internal error" */
1091 }
1092
1093 list = (int32 *) calloc (mp->lines, sizeof (int32)); /* allocate the new line order array */
1094
1095 if (list == NULL) /* if the allocation failed */
1096 return SCPE_MEM; /* then report a "Memory exhausted" error */
1097
1098 set = (t_bool *) calloc (mp->lines, sizeof (t_bool)); /* allocate the line set tracking array */
1099
1100 if (set == NULL) { /* if the allocation failed */
1101 free (list); /* then free the successful list allocation */
1102 return SCPE_MEM; /* and report a "Memory exhausted" error */
1103 }
1104
1105 tptr = cptr + strlen (cptr); /* append a semicolon */
1106 *tptr++ = ';'; /* to the command string */
1107 *tptr = '\0'; /* to make parsing easier */
1108
1109 idx = 0; /* initialize the index of ordered values */
1110
1111 while (*cptr != '\0') { /* while characters remain in the command string */
1112 if (strncmp (cptr, "ALL;", 4) == 0) { /* if the parameter is "ALL" */
1113 if (val != 0 || idx > 0 && idx <= max) /* then if some lines are restrictied or unspecified */
1114 for (line = (uint32) min; line <= max; line++) /* then fill them in sequentially */
1115 if (set [line] == FALSE) /* setting each unspecified line */
1116 list [idx++] = line; /* into the line order */
1117
1118 cptr = cptr + 4; /* advance past "ALL" and the trailing semicolon */
1119
1120 if (*cptr != '\0') /* if "ALL" is not the last parameter */
1121 result = SCPE_2MARG; /* then report extraneous items */
1122
1123 break; /* "ALL" terminates the order list */
1124 }
1125
1126 cptr = get_range (NULL, cptr, &low, &high, 10, max, ';'); /* get a line range */
1127
1128 if (cptr == NULL) { /* if a parsing error occurred */
1129 result = SCPE_ARG; /* then report an invalid argument */
1130 break; /* and terminate the parse */
1131 }
1132
1133 else if (low < min || low > max || high > max) { /* otherwise if the line number is invalid */
1134 result = SCPE_SUB; /* then report the subscript is out of range */
1135 break; /* and terminate the parse */
1136 }
1137
1138 else /* otherwise it's a valid range */
1139 for (line = (uint32) low; line <= high; line++) /* so add the line(s) to the order */
1140 if (set [line] == FALSE) { /* if the line number has not been specified */
1141 set [line] = TRUE; /* then now it is */
1142 list [idx++] = line; /* and add it to the connection order */
1143 }
1144 }
1145
1146 if (result == SCPE_OK) { /* if the assignment succeeded */
1147 if (idx <= max) /* then if any lines were not specified */
1148 list [idx] = -1; /* then terminate the order list after the last one */
1149
1150 memcpy (mp->lnorder, list, /* copy the working array to the connection array */
1151 mp->lines * sizeof (int32));
1152 }
1153
1154 free (list); /* free the list allocation */
1155 free (set); /* and the set allocation */
1156
1157 return result; /* return the status */
1158 }
1159
1160 /* Show the line connection order.
1161
1162 Parameters:
1163 - st = stream on which output is to be written
1164 - uptr = (not used)
1165 - val = (not used)
1166 - desc = pointer to multiplexer's TMXR structure
1167
1168 If a connection order array is not defined in the multiplexer descriptor, the
1169 command is rejected. If the first value of the connection order array is set
1170 to -1, then the connection order is sequential. Otherwise, the line values
1171 in the array are printed as a semicolon-separated list. Ranges are printed
1172 where possible to shorten the output. A -1 value within the array indicates
1173 the end of the order list.
1174 */
1175
tmxr_show_lnorder(FILE * st,UNIT * uptr,int32 val,void * desc)1176 t_stat tmxr_show_lnorder (FILE *st, UNIT *uptr, int32 val, void *desc)
1177 {
1178 int32 i, j, low, last;
1179 TMXR *mp = (TMXR *) desc;
1180 int32 *iptr = mp->lnorder;
1181 t_bool first = TRUE;
1182
1183 if (iptr == NULL) /* connection order undefined? */
1184 return SCPE_NXPAR; /* "Non-existent parameter" error */
1185
1186 if (*iptr < 0) /* sequential order indicated? */
1187 fprintf (st, "Order=0-%d\n", mp->lines - 1); /* print full line range */
1188
1189 else {
1190 low = last = *iptr++; /* set first line value */
1191
1192 for (j = 1; last != -1; j++) { /* print the remaining lines in the order list */
1193 if (j < mp->lines) /* more lines to process? */
1194 i = *iptr++; /* get next line in list */
1195 else /* final iteration */
1196 i = -1; /* get "tie-off" value */
1197
1198 if (i != last + 1) { /* end of a range? */
1199 if (first) { /* first line to print? */
1200 fputs ("Order=", st); /* print header */
1201 first = FALSE;
1202 }
1203
1204 else /* not first line printed */
1205 fputc (';', st); /* print separator */
1206
1207 if (low == last) /* range null? */
1208 fprintf (st, "%d", last); /* print single line value */
1209
1210 else /* range established */
1211 fprintf (st, "%d-%d", low, last); /* print start and end line */
1212
1213 low = i; /* start new range */
1214 }
1215
1216 last = i; /* note value for range check */
1217 }
1218 }
1219
1220 if (first == FALSE) /* sanity check for lines == 0 */
1221 fputc ('\n', st);
1222
1223 return SCPE_OK;
1224 }
1225
1226 /* Show summary processor */
1227
tmxr_show_summ(FILE * st,UNIT * uptr,int32 val,void * desc)1228 t_stat tmxr_show_summ (FILE *st, UNIT *uptr, int32 val, void *desc)
1229 {
1230 TMXR *mp = (TMXR *) desc;
1231 int32 i, t;
1232
1233 if (mp == NULL)
1234 return SCPE_IERR;
1235 for (i = t = 0; i < mp->lines; i++)
1236 t = t + (mp->ldsc[i].conn != 0);
1237 if (t == 1)
1238 fprintf (st, "1 connection");
1239 else fprintf (st, "%d connections", t);
1240 return SCPE_OK;
1241 }
1242
1243 /* Show conn/stat processor */
1244
tmxr_show_cstat(FILE * st,UNIT * uptr,int32 val,void * desc)1245 t_stat tmxr_show_cstat (FILE *st, UNIT *uptr, int32 val, void *desc)
1246 {
1247 TMXR *mp = (TMXR *) desc;
1248 int32 i, any;
1249
1250 if (mp == NULL)
1251 return SCPE_IERR;
1252 for (i = any = 0; i < mp->lines; i++) {
1253 if (mp->ldsc[i].conn) {
1254 any++;
1255 if (val)
1256 tmxr_fconns (st, &mp->ldsc[i], i);
1257 else tmxr_fstats (st, &mp->ldsc[i], i);
1258 }
1259 }
1260 if (any == 0)
1261 fprintf (st, (mp->lines == 1? "disconnected\n": "all disconnected\n"));
1262 return SCPE_OK;
1263 }
1264
1265 /* Show number of lines */
1266
tmxr_show_lines(FILE * st,UNIT * uptr,int32 val,void * desc)1267 t_stat tmxr_show_lines (FILE *st, UNIT *uptr, int32 val, void *desc)
1268 {
1269 TMXR *mp = (TMXR *) desc;
1270
1271 if (mp == NULL)
1272 return SCPE_IERR;
1273 fprintf (st, "lines=%d", mp->lines);
1274 return SCPE_OK;
1275 }
1276
1277
1278
1279 /* Global utility routines */
1280
1281
1282 /* Initialize the line state.
1283
1284 Reset the line state to represent an idle line. Note that we do not clear
1285 all of the line structure members, so a connected line remains connected
1286 after this call.
1287
1288 Because a line break is represented by a flag in the "receive break status"
1289 array, we must zero that array in order to clear any leftover break
1290 indications.
1291 */
1292
tmxr_init_line(TMLN * lp)1293 void tmxr_init_line (TMLN *lp)
1294 {
1295 lp->tsta = 0; /* clear the Telnet negotiation state */
1296 lp->xmte = 1; /* enable transmission */
1297 lp->dstb = 0; /* default to binary data mode */
1298 lp->rxbpr = lp->rxbpi = lp->rxcnt = 0; /* clear the receive indexes */
1299 lp->txbpr = lp->txbpi = lp->txcnt = 0; /* clear the transmit indexes */
1300
1301 memset (lp->rbr, 0, TMXR_MAXBUF); /* clear the break status array */
1302
1303 return;
1304 }
1305
1306
1307 /* Report a connection to a line.
1308
1309 This routine sends a notification of the form:
1310
1311 Connected to the <sim> simulator <dev> device, line <n>
1312
1313 ...to the line number specified by the "line" parameter of the multiplexer
1314 associated with the line descriptor pointer "lp" and the mux descriptor
1315 pointer "mp". If the device has only one line, the "line <n>" part is
1316 omitted. If the device has not been defined, the "<dev> device" part is
1317 omitted.
1318 */
1319
tmxr_report_connection(TMXR * mp,TMLN * lp,int32 line)1320 void tmxr_report_connection (TMXR *mp, TMLN *lp, int32 line)
1321 {
1322 int32 buffer_count;
1323 char line_number [20];
1324
1325 tmxr_linemsg (lp, "\n\r\nConnected to the "); /* report the connection */
1326 tmxr_linemsg (lp, sim_name); /* to the simulator */
1327 tmxr_linemsg (lp, " simulator ");
1328
1329 if (mp->dptr) { /* if the device pointer has been set */
1330 tmxr_linemsg (lp, sim_dname (mp->dptr)); /* then report the name */
1331 tmxr_linemsg (lp, " device"); /* of the connected device */
1332
1333 if (mp->lines > 1) { /* if the multiplexer has more than one line */
1334 tmxr_linemsg (lp, ", line "); /* then report the line number */
1335 sprintf (line_number, "%i", line); /* of the connection */
1336 tmxr_linemsg (lp, line_number);
1337 }
1338 }
1339
1340 tmxr_linemsg (lp, "\r\n\n");
1341
1342 buffer_count = tmxr_send_buffered_data (lp); /* write the message */
1343
1344 if (buffer_count == 0) /* if the write buffer is now empty */
1345 lp->xmte = 1; /* then reenable transmission if it was paused */
1346
1347 return;
1348 }
1349
1350
1351 /* Report a disconnection from a line.
1352
1353 This routine sends a notification of the form:
1354
1355 Disconnected from the <sim> simulator
1356
1357 ...to the line number associated with the line descriptor pointer "lp" and
1358 then disconnects the line.
1359
1360
1361 Implementation notes:
1362
1363 1. We do not write the buffer here, because the disconnect routine will do
1364 that for us.
1365 */
1366
tmxr_disconnect_line(TMLN * lp)1367 void tmxr_disconnect_line (TMLN *lp)
1368 {
1369 tmxr_linemsg (lp, "\r\nDisconnected from the "); /* report the disconnection */
1370 tmxr_linemsg (lp, sim_name); /* from the simulator */
1371 tmxr_linemsg (lp, " simulator\r\n\n");
1372
1373 tmxr_reset_ln (lp); /* disconnect the line */
1374
1375 return;
1376 }
1377
1378
1379
1380 /* Local hook routines */
1381
1382
1383 /* Read from a line.
1384
1385 This routine reads up to "length" bytes into the buffer associated with line
1386 "lp". The actual number of bytes read is returned. If no bytes are
1387 available, 0 is returned. If an error occurred while reading, -1 is
1388 returned.
1389 */
1390
tmxr_local_read(TMLN * lp,int32 length)1391 static int32 tmxr_local_read (TMLN *lp, int32 length)
1392 {
1393 return sim_read_sock (lp->conn, &(lp->rxb [lp->rxbpi]), length);
1394 }
1395
1396
1397 /* Write to a line.
1398
1399 This routine writes up to "length" bytes from the buffer associated with
1400 "lp". The actual number of bytes written is returned. If an error occurred
1401 while writing, -1 is returned.
1402 */
1403
tmxr_local_write(TMLN * lp,int32 length)1404 static int32 tmxr_local_write (TMLN *lp, int32 length)
1405 {
1406 int32 written;
1407
1408 written = sim_write_sock (lp->conn, &(lp->txb [lp->txbpr]), length);
1409
1410 if (written == SOCKET_ERROR) /* did an error occur? */
1411 return -1; /* return error indication */
1412 else
1413 return written;
1414 }
1415
1416
1417 /* Show a line.
1418
1419 This routine writes the port description to the file indicated by the
1420 "stream" parameter.
1421 */
1422
tmxr_local_show(TMLN * lp,FILE * stream)1423 static void tmxr_local_show (TMLN *lp, FILE *stream)
1424 {
1425 fprintf (stream, "IP address %s", lp->ipad);
1426 return;
1427 }
1428
1429
1430 /* Close a line.
1431
1432 This routine closes the line indicated by the "lp" parameter.
1433 */
1434
tmxr_local_close(TMLN * lp)1435 static void tmxr_local_close (TMLN *lp)
1436 {
1437 sim_close_sock (lp->conn); /* reset conn */
1438 return;
1439 }
1440