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