1 /* sim_tmxr.c: Telnet terminal multiplexor library
2 
3    Copyright (c) 2001-2011, 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    16-Jan-11    MP      Made option negotiation more reliable
30    20-Nov-08    RMS     Added three new standardized SHOW routines
31    30-Sep-08    JDB     Reverted tmxr_find_ldsc to original implementation
32    27-May-08    JDB     Added line connection order to tmxr_poll_conn,
33                         added tmxr_set_lnorder and tmxr_show_lnorder
34    14-May-08    JDB     Print device and line to which connection was made
35    11-Apr-07    JDB     Worked around Telnet negotiation problem with QCTerm
36    16-Aug-05    RMS     Fixed C++ declaration and cast problems
37    29-Jun-05    RMS     Extended tmxr_dscln to support unit array devices
38                         Fixed bug in SET LOG/NOLOG
39    04-Jan-04    RMS     Changed TMXR ldsc to be pointer to linedesc array
40                         Added tmxr_linemsg, circular output pointers, logging
41                         (from Mark Pizzolato)
42    29-Dec-03    RMS     Added output stall support
43    01-Nov-03    RMS     Cleaned up attach routine
44    09-Mar-03    RMS     Fixed bug in SHOW CONN
45    22-Dec-02    RMS     Fixed bugs in IAC+IAC receive and transmit sequences
46                         Added support for received break (all from by Mark Pizzolato)
47                         Fixed bug in attach
48    31-Oct-02    RMS     Fixed bug in 8b (binary) support
49    22-Aug-02    RMS     Added tmxr_open_master, tmxr_close_master
50    30-Dec-01    RMS     Added tmxr_fstats, tmxr_dscln, renamed tmxr_fstatus
51    03-Dec-01    RMS     Changed tmxr_fconns for extended SET/SHOW
52    20-Oct-01    RMS     Fixed bugs in read logic (found by Thord Nilson).
53                         Added tmxr_rqln, tmxr_tqln
54 
55    This library includes:
56 
57    tmxr_poll_conn -     poll for connection
58    tmxr_reset_ln -      reset line
59    tmxr_getc_ln -       get character for line
60    tmxr_poll_rx -       poll receive
61    tmxr_putc_ln -       put character for line
62    tmxr_poll_tx -       poll transmit
63    tmxr_open_master -   open master connection
64    tmxr_close_master -  close master connection
65    tmxr_attach  -       attach terminal multiplexor
66    tmxr_detach  -       detach terminal multiplexor
67    tmxr_ex      -       (null) examine
68    tmxr_dep     -       (null) deposit
69    tmxr_msg     -       send message to socket
70    tmxr_linemsg -       send message to line
71    tmxr_fconns  -       output connection status
72    tmxr_fstats  -       output connection statistics
73    tmxr_dscln   -       disconnect line (SET routine)
74    tmxr_rqln    -       number of available characters for line
75    tmxr_tqln    -       number of buffered characters for line
76    tmxr_set_lnorder -   set line connection order
77    tmxr_show_lnorder -  show line connection order
78 
79    All routines are OS-independent.
80 */
81 
82 #include "sim_defs.h"
83 #include "sim_sock.h"
84 #include "sim_tmxr.h"
85 #include "scp.h"
86 #include <ctype.h>
87 
88 /* Telnet protocol constants - negatives are for init'ing signed char data */
89 
90 /* Commands */
91 #define TN_IAC          -1                              /* protocol delim */
92 #define TN_DONT         -2                              /* dont */
93 #define TN_DO           -3                              /* do */
94 #define TN_WONT         -4                              /* wont */
95 #define TN_WILL         -5                              /* will */
96 #define TN_SB           -6                              /* sub-option negotiation */
97 #define TN_GA           -7                              /* go ahead */
98 #define TN_EL           -8                              /* erase line */
99 #define TN_EC           -9                              /* erase character */
100 #define TN_AYT          -10                             /* are you there */
101 #define TN_AO           -11                             /* abort output */
102 #define TN_IP           -12                             /* interrupt process */
103 #define TN_BRK          -13                             /* break */
104 #define TN_DATAMK       -14                             /* data mark */
105 #define TN_NOP          -15                             /* no operation */
106 #define TN_SE           -16                             /* end sub-option negot */
107 
108 /* Options */
109 
110 #define TN_BIN          0                               /* bin */
111 #define TN_ECHO         1                               /* echo */
112 #define TN_SGA          3                               /* sga */
113 #define TN_LINE         34                              /* line mode */
114 #define TN_CR           015                             /* carriage return */
115 #define TN_LF           012                             /* line feed */
116 #define TN_NUL          000                             /* null */
117 
118 /* Telnet line states */
119 
120 #define TNS_NORM        000                             /* normal */
121 #define TNS_IAC         001                             /* IAC seen */
122 #define TNS_WILL        002                             /* WILL seen */
123 #define TNS_WONT        003                             /* WONT seen */
124 #define TNS_SKIP        004                             /* skip next cmd */
125 #define TNS_CRPAD       005                             /* CR padding */
126 #define TNS_DO          006                             /* DO request pending rejection */
127 
128 
129 void tmxr_rmvrc (TMLN *lp, int32 p);
130 int32 tmxr_send_buffered_data (TMLN *lp);
131 TMLN *tmxr_find_ldsc (UNIT *uptr, int32 val, TMXR *mp);
132 
133 extern int32 sim_switches;
134 extern char sim_name[];
135 extern FILE *sim_log;
136 extern uint32 sim_os_msec (void);
137 
138 /* Poll for new connection
139 
140    Called from unit service routine to test for new connection
141 
142    Inputs:
143         *mp     =       pointer to terminal multiplexor descriptor
144    Outputs:
145         line number activated, -1 if none
146 
147    If a connection order is defined for the descriptor, and the first value is
148    not -1 (indicating default order), then the order array is used to find an
149    open line.  Otherwise, a search is made of all lines in numerical sequence.
150 */
151 
tmxr_poll_conn(TMXR * mp)152 int32 tmxr_poll_conn (TMXR *mp)
153 {
154 SOCKET newsock;
155 TMLN *lp;
156 int32 *op;
157 int32 i, j;
158 uint32 ipaddr;
159 
160 static char mantra[] = {
161     TN_IAC, TN_WILL, TN_LINE,
162     TN_IAC, TN_WILL, TN_SGA,
163     TN_IAC, TN_WILL, TN_ECHO,
164     TN_IAC, TN_WILL, TN_BIN,
165     TN_IAC, TN_DO, TN_BIN
166     };
167 
168 newsock = sim_accept_conn (mp->master, &ipaddr);        /* poll connect */
169 if (newsock != INVALID_SOCKET) {                        /* got a live one? */
170     op = mp->lnorder;                                   /* get line connection order list pointer */
171     i = mp->lines;                                      /* play it safe in case lines == 0 */
172 
173     for (j = 0; j < mp->lines; j++, i++) {              /* find next avail line */
174         if (op && (*op >= 0) && (*op < mp->lines))      /* order list present and valid? */
175             i = *op++;                                  /* get next line in list to try */
176         else                                            /* no list or not used or range error */
177             i = j;                                      /* get next sequential line */
178 
179         lp = mp->ldsc + i;                              /* get pointer to line descriptor */
180         if (lp->conn == 0)                              /* is the line available? */
181             break;                                      /* yes, so stop search */
182         }
183 
184     if (i >= mp->lines) {                               /* all busy? */
185         tmxr_msg (newsock, "All connections busy\r\n");
186         sim_close_sock (newsock, 0);
187         }
188     else {
189         lp = mp->ldsc + i;                              /* get line desc */
190         lp->conn = newsock;                             /* record connection */
191         lp->ipad = ipaddr;                              /* ip address */
192         lp->cnms = sim_os_msec ();                      /* time of conn */
193         lp->rxbpr = lp->rxbpi = 0;                      /* init buf pointers */
194         lp->txbpr = lp->txbpi = 0;
195         lp->rxcnt = lp->txcnt = 0;                      /* init counters */
196         lp->tsta = 0;                                   /* init telnet state */
197         lp->xmte = 1;                                   /* enable transmit */
198         lp->dstb = 0;                                   /* default bin mode */
199         sim_write_sock (newsock, mantra, sizeof (mantra));
200         tmxr_linemsg (lp, "\n\r\nConnected to the ");
201         tmxr_linemsg (lp, sim_name);
202         tmxr_linemsg (lp, " simulator ");
203 
204         if (mp->dptr) {                                 /* device defined? */
205             tmxr_linemsg (lp, sim_dname (mp->dptr));    /* report device name */
206             tmxr_linemsg (lp, " device");
207 
208             if (mp->lines > 1) {                        /* more than one line? */
209                 char line[20];
210 
211                 tmxr_linemsg (lp, ", line ");           /* report the line number */
212                 sprintf (line, "%i", i);
213                 tmxr_linemsg (lp, line);
214                 }
215             }
216 
217         tmxr_linemsg (lp, "\r\n\n");
218 
219         tmxr_poll_tx (mp);                              /* flush output */
220         return i;
221         }
222     }                                                   /* end if newsock */
223 return -1;
224 }
225 
226 /* Reset line */
227 
tmxr_reset_ln(TMLN * lp)228 void tmxr_reset_ln (TMLN *lp)
229 {
230 if (lp->txlog)                                          /* dump log */
231     fflush (lp->txlog);
232 tmxr_send_buffered_data (lp);                           /* send buffered data */
233 sim_close_sock (lp->conn, 0);                           /* reset conn */
234 lp->conn = lp->tsta = 0;                                /* reset state */
235 lp->rxbpr = lp->rxbpi = 0;
236 lp->txbpr = lp->txbpi = 0;
237 lp->xmte = 1;
238 lp->dstb = 0;
239 return;
240 }
241 
242 /* Get character from specific line
243 
244    Inputs:
245         *lp     =       pointer to terminal line descriptor
246    Output:
247         valid + char, 0 if line
248 */
249 
tmxr_getc_ln(TMLN * lp)250 int32 tmxr_getc_ln (TMLN *lp)
251 {
252 int32 j, val = 0;
253 uint32 tmp;
254 
255 if (lp->conn && lp->rcve) {                             /* conn & enb? */
256     j = lp->rxbpi - lp->rxbpr;                          /* # input chrs */
257     if (j) {                                            /* any? */
258         tmp = lp->rxb[lp->rxbpr];                       /* get char */
259         val = TMXR_VALID | (tmp & 0377);                /* valid + chr */
260         if (lp->rbr[lp->rxbpr])                         /* break? */
261             val = val | SCPE_BREAK;
262         lp->rxbpr = lp->rxbpr + 1;                      /* adv pointer */
263         }
264     }                                                   /* end if conn */
265 if (lp->rxbpi == lp->rxbpr)                             /* empty? zero ptrs */
266     lp->rxbpi = lp->rxbpr = 0;
267 return val;
268 }
269 
270 /* Poll for input
271 
272    Inputs:
273         *mp     =       pointer to terminal multiplexor descriptor
274    Outputs:     none
275 */
276 
tmxr_poll_rx(TMXR * mp)277 void tmxr_poll_rx (TMXR *mp)
278 {
279 int32 i, nbytes, j;
280 TMLN *lp;
281 
282 for (i = 0; i < mp->lines; i++) {                       /* loop thru lines */
283     lp = mp->ldsc + i;                                  /* get line desc */
284     if (!lp->conn || !lp->rcve)                         /* skip if !conn */
285         continue;
286 
287     nbytes = 0;
288     if (lp->rxbpi == 0)                                 /* need input? */
289         nbytes = sim_read_sock (lp->conn,               /* yes, read */
290             &(lp->rxb[lp->rxbpi]),                      /* leave spc for */
291             TMXR_MAXBUF - TMXR_GUARD);                  /* Telnet cruft */
292     else if (lp->tsta)                                  /* in Telnet seq? */
293         nbytes = sim_read_sock (lp->conn,               /* yes, read to end */
294             &(lp->rxb[lp->rxbpi]),
295             TMXR_MAXBUF - lp->rxbpi);
296     if (nbytes < 0)                                     /* closed? reset ln */
297         tmxr_reset_ln (lp);
298     else if (nbytes > 0) {                              /* if data rcvd */
299         j = lp->rxbpi;                                  /* start of data */
300         memset (&lp->rbr[j], 0, nbytes);                /* clear status */
301         lp->rxbpi = lp->rxbpi + nbytes;                 /* adv pointers */
302         lp->rxcnt = lp->rxcnt + nbytes;
303 
304 /* Examine new data, remove TELNET cruft before making input available */
305 
306         for (; j < lp->rxbpi; ) {                       /* loop thru char */
307             signed char tmp = lp->rxb[j];               /* get char */
308             switch (lp->tsta) {                         /* case tlnt state */
309 
310             case TNS_NORM:                              /* normal */
311                 if (tmp == TN_IAC) {                    /* IAC? */
312                     lp->tsta = TNS_IAC;                 /* change state */
313                     tmxr_rmvrc (lp, j);                 /* remove char */
314                     break;
315                     }
316                 if ((tmp == TN_CR) && lp->dstb)         /* CR, no bin */
317                     lp->tsta = TNS_CRPAD;               /* skip pad char */
318                 j = j + 1;                              /* advance j */
319                 break;
320 
321             case TNS_IAC:                               /* IAC prev */
322                 if (tmp == TN_IAC) {                    /* IAC + IAC */
323                     lp->tsta = TNS_NORM;                /* treat as normal */
324                     j = j + 1;                          /* advance j */
325                     break;                              /* keep IAC */
326                     }
327                 if (tmp == TN_BRK) {                    /* IAC + BRK? */
328                     lp->tsta = TNS_NORM;                /* treat as normal */
329                     lp->rxb[j] = 0;                     /* char is null */
330                     lp->rbr[j] = 1;                     /* flag break */
331                     j = j + 1;                          /* advance j */
332                     break;
333                     }
334                 switch (tmp) {
335                 case TN_WILL:                           /* IAC + WILL? */
336                     lp->tsta = TNS_WILL;
337                     break;
338                 case TN_WONT:                           /* IAC + WONT? */
339                     lp->tsta = TNS_WONT;
340                     break;
341                 case TN_DO:                             /* IAC + DO? */
342                     lp->tsta = TNS_DO;
343                     break;
344                 case TN_DONT:                           /* IAC + DONT? */
345                     lp->tsta = TNS_SKIP;                /* IAC + other */
346                     break;
347                 case TN_GA: case TN_EL:                 /* IAC + other 2 byte types */
348                 case TN_EC: case TN_AYT:
349                 case TN_AO: case TN_IP:
350                 case TN_NOP:
351                     lp->tsta = TNS_NORM;                /* ignore */
352                     break;
353                 case TN_SB:                             /* IAC + SB sub-opt negotiation */
354                 case TN_DATAMK:                         /* IAC + data mark */
355                 case TN_SE:                             /* IAC + SE sub-opt end */
356                     lp->tsta = TNS_NORM;                /* ignore */
357                     break;
358                     }
359                 tmxr_rmvrc (lp, j);                     /* remove char */
360                 break;
361 
362             case TNS_WILL: case TNS_WONT:               /* IAC+WILL/WONT prev */
363                 if (tmp == TN_BIN) {                    /* BIN? */
364                     if (lp->tsta == TNS_WILL)
365                         lp->dstb = 0;
366                     else lp->dstb = 1;
367                     }
368                 tmxr_rmvrc (lp, j);                     /* remove it */
369                 lp->tsta = TNS_NORM;                    /* next normal */
370                 break;
371 
372             /* Negotiation with the HP terminal emulator "QCTerm" is not working.
373                QCTerm says "WONT BIN" but sends bare CRs.  RFC 854 says:
374 
375                  Note that "CR LF" or "CR NUL" is required in both directions
376                  (in the default ASCII mode), to preserve the symmetry of the
377                  NVT model.  ...The protocol requires that a NUL be inserted
378                  following a CR not followed by a LF in the data stream.
379 
380                Until full negotiation is implemented, we work around the problem
381                by checking the character following the CR in non-BIN mode and
382                strip it only if it is LF or NUL.  This should not affect
383                conforming clients.
384             */
385 
386             case TNS_CRPAD:                             /* only LF or NUL should follow CR */
387                 lp->tsta = TNS_NORM;                    /* next normal */
388                 if ((tmp == TN_LF) ||                   /* CR + LF ? */
389                     (tmp == TN_NUL))                    /* CR + NUL? */
390                     tmxr_rmvrc (lp, j);                 /* remove it */
391                 break;
392 
393             case TNS_DO:                                /* pending DO request */
394                 if (tmp == TN_BIN) {                    /* reject all but binary mode */
395                     char accept[] = {TN_IAC, TN_WILL, TN_BIN};
396                     sim_write_sock (lp->conn, accept, sizeof(accept));
397                     }
398                 tmxr_rmvrc (lp, j);                     /* remove it */
399                 lp->tsta = TNS_NORM;                    /* next normal */
400                 break;
401 
402             case TNS_SKIP: default:                     /* skip char */
403                 tmxr_rmvrc (lp, j);                     /* remove char */
404                 lp->tsta = TNS_NORM;                    /* next normal */
405                 break;
406                 }                                       /* end case state */
407             }                                           /* end for char */
408         }                                               /* end else nbytes */
409     }                                                   /* end for lines */
410 for (i = 0; i < mp->lines; i++) {                       /* loop thru lines */
411     lp = mp->ldsc + i;                                  /* get line desc */
412     if (lp->rxbpi == lp->rxbpr)                         /* if buf empty, */
413         lp->rxbpi = lp->rxbpr = 0;                      /* reset pointers */
414     }                                                   /* end for */
415 return;
416 }
417 
418 /* Return count of available characters for line */
419 
tmxr_rqln(TMLN * lp)420 int32 tmxr_rqln (TMLN *lp)
421 {
422 return (lp->rxbpi - lp->rxbpr);
423 }
424 
425 /* Remove character p (and matching status) from line l input buffer */
426 
tmxr_rmvrc(TMLN * lp,int32 p)427 void tmxr_rmvrc (TMLN *lp, int32 p)
428 {
429 for ( ; p < lp->rxbpi; p++) {
430     lp->rxb[p] = lp->rxb[p + 1];
431     lp->rbr[p] = lp->rbr[p + 1];
432     }
433 lp->rxbpi = lp->rxbpi - 1;
434 return;
435 }
436 
437 /* Store character in line buffer
438 
439    Inputs:
440         *lp     =       pointer to line descriptor
441         chr     =       characters
442    Outputs:
443         status  =       ok, connection lost, or stall
444 */
445 
tmxr_putc_ln(TMLN * lp,int32 chr)446 t_stat tmxr_putc_ln (TMLN *lp, int32 chr)
447 {
448 if (lp->txlog)                                          /* log if available */
449     fputc (chr, lp->txlog);
450 if (lp->conn == 0)                                      /* no conn? lost */
451     return SCPE_LOST;
452 if (tmxr_tqln (lp) < (TMXR_MAXBUF - 1)) {               /* room for char (+ IAC)? */
453     lp->txb[lp->txbpi] = (char) chr;                    /* buffer char */
454     lp->txbpi = lp->txbpi + 1;                          /* adv pointer */
455     if (lp->txbpi >= TMXR_MAXBUF)                       /* wrap? */
456         lp->txbpi = 0;
457     if ((char) chr == TN_IAC) {                         /* IAC? */
458         lp->txb[lp->txbpi] = (char) chr;                /* IAC + IAC */
459         lp->txbpi = lp->txbpi + 1;                      /* adv pointer */
460         if (lp->txbpi >= TMXR_MAXBUF)                   /* wrap? */
461             lp->txbpi = 0;
462         }
463     if (tmxr_tqln (lp) > (TMXR_MAXBUF - TMXR_GUARD))    /* near full? */
464         lp->xmte = 0;                                   /* disable line */
465     return SCPE_OK;                                     /* char sent */
466     }
467 lp->xmte = 0;                                           /* no room, dsbl line */
468 return SCPE_STALL;                                      /* char not sent */
469 }
470 
471 /* Poll for output
472 
473    Inputs:
474         *mp     =       pointer to terminal multiplexor descriptor
475    Outputs:
476         none
477 */
478 
tmxr_poll_tx(TMXR * mp)479 void tmxr_poll_tx (TMXR *mp)
480 {
481 int32 i, nbytes;
482 TMLN *lp;
483 
484 for (i = 0; i < mp->lines; i++) {                       /* loop thru lines */
485     lp = mp->ldsc + i;                                  /* get line desc */
486     if (lp->conn == 0)                                  /* skip if !conn */
487         continue;
488         nbytes = tmxr_send_buffered_data (lp);          /* buffered bytes */
489         if (nbytes == 0)                                /* buf empty? enab line */
490             lp->xmte = 1;
491         }                                               /* end for */
492 return;
493 }
494 
495 /* Send buffered data across network
496 
497    Inputs:
498         *lp     =       pointer to line descriptor
499    Outputs:
500         returns number of bytes still buffered
501 */
502 
tmxr_send_buffered_data(TMLN * lp)503 int32 tmxr_send_buffered_data (TMLN *lp)
504 {
505 int32 nbytes, sbytes;
506 
507 nbytes = tmxr_tqln(lp);                                 /* avail bytes */
508 if (nbytes) {                                           /* >0? write */
509     if (lp->txbpr < lp->txbpi)                          /* no wrap? */
510         sbytes = sim_write_sock (lp->conn,              /* write all data */
511             &(lp->txb[lp->txbpr]), nbytes);
512     else sbytes = sim_write_sock (lp->conn,             /* write to end buf */
513             &(lp->txb[lp->txbpr]), TMXR_MAXBUF - lp->txbpr);
514     if (sbytes != SOCKET_ERROR) {                       /* ok? */
515         lp->txbpr = (lp->txbpr + sbytes);               /* update remove ptr */
516         if (lp->txbpr >= TMXR_MAXBUF)                   /* wrap? */
517             lp->txbpr = 0;
518         lp->txcnt = lp->txcnt + sbytes;                 /* update counts */
519         nbytes = nbytes - sbytes;
520         }
521     if (nbytes && (lp->txbpr == 0))     {               /* more data and wrap? */
522         sbytes = sim_write_sock (lp->conn, lp->txb, nbytes);
523         if (sbytes != SOCKET_ERROR) {                   /* ok */
524             lp->txbpr = (lp->txbpr + sbytes);           /* update remove ptr */
525             if (lp->txbpr >= TMXR_MAXBUF)               /* wrap? */
526                 lp->txbpr = 0;
527             lp->txcnt = lp->txcnt + sbytes;             /* update counts */
528             nbytes = nbytes - sbytes;
529             }
530         }
531     }                                                   /* end if nbytes */
532 return nbytes;
533 }
534 
535 /* Return count of buffered characters for line */
536 
tmxr_tqln(TMLN * lp)537 int32 tmxr_tqln (TMLN *lp)
538 {
539 return (lp->txbpi - lp->txbpr + ((lp->txbpi < lp->txbpr)? TMXR_MAXBUF: 0));
540 }
541 
542 /* Open master socket */
543 
tmxr_open_master(TMXR * mp,char * cptr)544 t_stat tmxr_open_master (TMXR *mp, char *cptr)
545 {
546 int32 i, port;
547 SOCKET sock;
548 TMLN *lp;
549 t_stat r;
550 
551 port = (int32) get_uint (cptr, 10, 65535, &r);          /* get port */
552 if ((r != SCPE_OK) || (port == 0))
553     return SCPE_ARG;
554 sock = sim_master_sock (port);                          /* make master socket */
555 if (sock == INVALID_SOCKET)                             /* open error */
556     return SCPE_OPENERR;
557 printf ("Listening on port %d (socket %d)\n", port, sock);
558 if (sim_log)
559     fprintf (sim_log, "Listening on port %d (socket %d)\n", port, sock);
560 mp->port = port;                                        /* save port */
561 mp->master = sock;                                      /* save master socket */
562 for (i = 0; i < mp->lines; i++) {                       /* initialize lines */
563     lp = mp->ldsc + i;
564     lp->conn = lp->tsta = 0;
565     lp->rxbpi = lp->rxbpr = 0;
566     lp->txbpi = lp->txbpr = 0;
567     lp->rxcnt = lp->txcnt = 0;
568     lp->xmte = 1;
569     lp->dstb = 0;
570     }
571 return SCPE_OK;
572 }
573 
574 /* Attach unit to master socket */
575 
tmxr_attach(TMXR * mp,UNIT * uptr,char * cptr)576 t_stat tmxr_attach (TMXR *mp, UNIT *uptr, char *cptr)
577 {
578 char* tptr;
579 t_stat r;
580 
581 tptr = (char *) malloc (strlen (cptr) + 1);             /* get string buf */
582 if (tptr == NULL)                                       /* no more mem? */
583     return SCPE_MEM;
584 r = tmxr_open_master (mp, cptr);                        /* open master socket */
585 if (r != SCPE_OK) {                                     /* error? */
586     free (tptr);                                        /* release buf */
587     return SCPE_OPENERR;
588     }
589 strcpy (tptr, cptr);                                    /* copy port */
590 uptr->filename = tptr;                                  /* save */
591 uptr->flags = uptr->flags | UNIT_ATT;                   /* no more errors */
592 
593 if (mp->dptr == NULL)                                   /* has device been set? */
594     mp->dptr = find_dev_from_unit (uptr);               /* no, so set device now */
595 
596 return SCPE_OK;
597 }
598 
599 /* Close master socket */
600 
tmxr_close_master(TMXR * mp)601 t_stat tmxr_close_master (TMXR *mp)
602 {
603 int32 i;
604 TMLN *lp;
605 
606 for (i = 0; i < mp->lines; i++) {                       /* loop thru conn */
607     lp = mp->ldsc + i;
608     if (lp->conn) {
609         tmxr_linemsg (lp, "\r\nDisconnected from the ");
610         tmxr_linemsg (lp, sim_name);
611         tmxr_linemsg (lp, " simulator\r\n\n");
612         tmxr_reset_ln (lp);
613         }                                               /* end if conn */
614     }                                                   /* end for */
615 sim_close_sock (mp->master, 1);                         /* close master socket */
616 mp->master = 0;
617 return SCPE_OK;
618 }
619 
620 /* Detach unit from master socket */
621 
tmxr_detach(TMXR * mp,UNIT * uptr)622 t_stat tmxr_detach (TMXR *mp, UNIT *uptr)
623 {
624 if (!(uptr->flags & UNIT_ATT))                          /* attached? */
625     return SCPE_OK;
626 tmxr_close_master (mp);                                 /* close master socket */
627 free (uptr->filename);                                  /* free port string */
628 uptr->filename = NULL;
629 uptr->flags = uptr->flags & ~UNIT_ATT;                  /* not attached */
630 return SCPE_OK;
631 }
632 
633 /* Stub examine and deposit */
634 
tmxr_ex(t_value * vptr,t_addr addr,UNIT * uptr,int32 sw)635 t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
636 {
637 return SCPE_NOFNC;
638 }
639 
tmxr_dep(t_value val,t_addr addr,UNIT * uptr,int32 sw)640 t_stat tmxr_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw)
641 {
642 return SCPE_NOFNC;
643 }
644 
645 /* Output message to socket or line descriptor */
646 
tmxr_msg(SOCKET sock,char * msg)647 void tmxr_msg (SOCKET sock, char *msg)
648 {
649 if (sock)
650     sim_write_sock (sock, msg, strlen (msg));
651 return;
652 }
653 
tmxr_linemsg(TMLN * lp,char * msg)654 void tmxr_linemsg (TMLN *lp, char *msg)
655 {
656 int32 len;
657 
658 for (len = strlen (msg); len > 0; --len)
659     tmxr_putc_ln (lp, *msg++);
660 return;
661 }
662 
663 /* Print connections - used only in named SHOW command */
664 
tmxr_fconns(FILE * st,TMLN * lp,int32 ln)665 void tmxr_fconns (FILE *st, TMLN *lp, int32 ln)
666 {
667 if (ln >= 0)
668     fprintf (st, "line %d: ", ln);
669 if (lp->conn) {
670     int32 o1, o2, o3, o4, hr, mn, sc;
671     uint32 ctime;
672 
673     o1 = (lp->ipad >> 24) & 0xFF;
674     o2 = (lp->ipad >> 16) & 0xFF;
675     o3 = (lp->ipad >> 8) & 0xFF;
676     o4 = (lp->ipad) & 0xFF;
677     ctime = (sim_os_msec () - lp->cnms) / 1000;
678     hr = ctime / 3600;
679     mn = (ctime / 60) % 60;
680     sc = ctime % 60;
681     fprintf (st, "IP address %d.%d.%d.%d", o1, o2, o3, o4);
682     if (ctime)
683         fprintf (st, ", connected %02d:%02d:%02d\n", hr, mn, sc);
684     }
685 else fprintf (st, "line disconnected\n");
686 if (lp->txlog)
687     fprintf (st, "Logging to %s\n", lp->txlogname);
688 return;
689 }
690 
691 /* Print statistics - used only in named SHOW command */
692 
tmxr_fstats(FILE * st,TMLN * lp,int32 ln)693 void tmxr_fstats (FILE *st, TMLN *lp, int32 ln)
694 {
695 static const char *enab = "on";
696 static const char *dsab = "off";
697 
698 if (ln >= 0)
699     fprintf (st, "line %d: ", ln);
700 if (lp->conn) {
701     fprintf (st, "input (%s) queued/total = %d/%d, ",
702         (lp->rcve? enab: dsab),
703         lp->rxbpi - lp->rxbpr, lp->rxcnt);
704     fprintf (st, "output (%s) queued/total = %d/%d\n",
705         (lp->xmte? enab: dsab),
706         lp->txbpi - lp->txbpr, lp->txcnt);
707     }
708 else fprintf (st, "line disconnected\n");
709 return;
710 }
711 
712 /* Disconnect line */
713 
tmxr_dscln(UNIT * uptr,int32 val,char * cptr,void * desc)714 t_stat tmxr_dscln (UNIT *uptr, int32 val, char *cptr, void *desc)
715 {
716 TMXR *mp = (TMXR *) desc;
717 TMLN *lp;
718 int32 ln;
719 t_stat r;
720 
721 if (mp == NULL)
722     return SCPE_IERR;
723 if (val) {                                              /* = n form */
724     if (cptr == NULL)
725         return SCPE_ARG;
726     ln = (int32) get_uint (cptr, 10, mp->lines - 1, &r);
727     if (r != SCPE_OK)
728         return SCPE_ARG;
729     lp = mp->ldsc + ln;
730     }
731 else {
732     lp = tmxr_find_ldsc (uptr, 0, mp);
733     if (lp == NULL)
734         return SCPE_IERR;
735     }
736 if (lp->conn) {
737     tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n");
738     tmxr_reset_ln (lp);
739     }
740 return SCPE_OK;
741 }
742 
743 /* Enable logging for line */
744 
tmxr_set_log(UNIT * uptr,int32 val,char * cptr,void * desc)745 t_stat tmxr_set_log (UNIT *uptr, int32 val, char *cptr, void *desc)
746 {
747 TMXR *mp = (TMXR *) desc;
748 TMLN *lp;
749 
750 if (cptr == NULL)                                       /* no file name? */
751     return SCPE_2FARG;
752 lp = tmxr_find_ldsc (uptr, val, mp);                    /* find line desc */
753 if (lp == NULL)
754     return SCPE_IERR;
755 if (lp->txlog)                                          /* close existing log */
756     tmxr_set_nolog (NULL, val, NULL, desc);
757 lp->txlogname = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc namebuf */
758 if (lp->txlogname == NULL)                              /* can't? */
759     return SCPE_MEM;
760 strncpy (lp->txlogname, cptr, CBUFSIZE);                /* save file name */
761 lp->txlog = fopen (cptr, "ab");                         /* open log */
762 if (lp->txlog == NULL) {                                /* error? */
763     free (lp->txlogname);                               /* free buffer */
764     return SCPE_OPENERR;
765     }
766 return SCPE_OK;
767 }
768 
769 /* Disable logging for line */
770 
tmxr_set_nolog(UNIT * uptr,int32 val,char * cptr,void * desc)771 t_stat tmxr_set_nolog (UNIT *uptr, int32 val, char *cptr, void *desc)
772 {
773 TMXR *mp = (TMXR *) desc;
774 TMLN *lp;
775 
776 if (cptr)                                               /* no arguments */
777     return SCPE_2MARG;
778 lp = tmxr_find_ldsc (uptr, val, mp);                    /* find line desc */
779 if (lp == NULL)
780     return SCPE_IERR;
781 if (lp->txlog) {                                        /* logging? */
782     fclose (lp->txlog);                                 /* close log */
783     free (lp->txlogname);                               /* free namebuf */
784     lp->txlog = NULL;
785     lp->txlogname = NULL;
786     }
787 return SCPE_OK;
788 }
789 
790 /* Show logging status for line */
791 
tmxr_show_log(FILE * st,UNIT * uptr,int32 val,void * desc)792 t_stat tmxr_show_log (FILE *st, UNIT *uptr, int32 val, void *desc)
793 {
794 TMXR *mp = (TMXR *) desc;
795 TMLN *lp;
796 
797 lp = tmxr_find_ldsc (uptr, val, mp);                    /* find line desc */
798 if (lp == NULL)
799     return SCPE_IERR;
800 if (lp->txlog)
801     fprintf (st, "logging to %s", lp->txlogname);
802 else fprintf (st, "no logging");
803 return SCPE_OK;
804 }
805 
806 /* Find line descriptor.
807 
808    Note: This routine may be called with a UNIT that does not belong to the
809    device indicated in the TMXR structure.  That is, the multiplexer lines may
810    belong to a device other than the one attached to the socket (the HP 2100 MUX
811    device is one example).  Therefore, we must look up the device from the unit
812    at each call, rather than depending on the DPTR stored in the TMXR.
813 */
814 
tmxr_find_ldsc(UNIT * uptr,int32 val,TMXR * mp)815 TMLN *tmxr_find_ldsc (UNIT *uptr, int32 val, TMXR *mp)
816 {
817 if (uptr) {                                             /* called from SET? */
818     DEVICE *dptr = find_dev_from_unit (uptr);           /* find device */
819     if (dptr == NULL)                                   /* what?? */
820         return NULL;
821     val = (int32) (uptr - dptr->units);                 /* implicit line # */
822     }
823 if ((val < 0) || (val >= mp->lines))                    /* invalid line? */
824     return NULL;
825 return mp->ldsc + val;                                  /* line descriptor */
826 }
827 
828 /* Set the line connection order.
829 
830    Example command for eight-line multiplexer:
831 
832       SET <dev> LINEORDER=1;5;2-4;7
833 
834    Resulting connection order: 1,5,2,3,4,7,0,6.
835 
836    Parameters:
837     - uptr = (not used)
838     - val  = (not used)
839     - cptr = pointer to first character of range specification
840     - desc = pointer to multiplexer's TMXR structure
841 
842    On entry, cptr points to the value portion of the command string, which may
843    be either a semicolon-separated list of line ranges or the keyword ALL.
844 
845    If a line connection order array is not defined in the multiplexer
846    descriptor, the command is rejected.  If the specified range encompasses all
847    of the lines, the first value of the connection order array is set to -1 to
848    indicate sequential connection order.  Otherwise, the line values in the
849    array are set to the order specified by the command string.  All values are
850    populated, first with those explicitly specified in the command string, and
851    then in ascending sequence with those not specified.
852 
853    If an error occurs, the original line order is not disturbed.
854 */
855 
tmxr_set_lnorder(UNIT * uptr,int32 val,char * cptr,void * desc)856 t_stat tmxr_set_lnorder (UNIT *uptr, int32 val, char *cptr, void *desc)
857 {
858 TMXR *mp = (TMXR *) desc;
859 char *tptr;
860 t_addr low, high, max = (t_addr) mp->lines - 1;
861 int32 *list;
862 t_bool *set;
863 uint32 line, idx = 0;
864 t_stat result = SCPE_OK;
865 
866 if (mp->lnorder == NULL)                                /* line connection order undefined? */
867     return SCPE_NXPAR;                                  /* "Non-existent parameter" error */
868 
869 else if ((cptr == NULL) || (*cptr == '\0'))             /* line range not supplied? */
870     return SCPE_MISVAL;                                 /* "Missing value" error */
871 
872 list = (int32 *) calloc (mp->lines, sizeof (int32));    /* allocate new line order array */
873 
874 if (list == NULL)                                       /* allocation failed? */
875     return SCPE_MEM;                                    /* report it */
876 
877 set = (t_bool *) calloc (mp->lines, sizeof (t_bool));   /* allocate line set tracking array */
878 
879 if (set == NULL) {                                      /* allocation failed? */
880     free (list);                                        /* free successful list allocation */
881     return SCPE_MEM;                                    /* report it */
882     }
883 
884 tptr = cptr + strlen (cptr);                            /* append a semicolon */
885 *tptr++ = ';';                                          /*   to the command string */
886 *tptr = '\0';                                           /*   to make parsing easier for get_range */
887 
888 while (*cptr) {                                                 /* parse command string */
889     cptr = get_range (NULL, cptr, &low, &high, 10, max, ';');   /* get a line range */
890 
891     if (cptr == NULL) {                                 /* parsing error? */
892         result = SCPE_ARG;                              /* "Invalid argument" error */
893         break;
894         }
895 
896     else if ((low > max) || (high > max)) {             /* line out of range? */
897         result = SCPE_SUB;                              /* "Subscript out of range" error */
898         break;
899         }
900 
901     else if ((low == 0) && (high == max)) {             /* entire line range specified? */
902         list [0] = -1;                                  /* set sequential order flag */
903         idx = (uint32) max + 1;                         /* indicate no fill-in needed */
904         break;
905         }
906 
907     else
908         for (line = (uint32) low; line <= (uint32) high; line++) /* see if previously specified */
909             if (set [line] == FALSE) {                  /* not already specified? */
910                 set [line] = TRUE;                      /* now it is */
911                 list [idx] = line;                      /* add line to connection order */
912                 idx = idx + 1;                          /* bump "specified" count */
913                 }
914     }
915 
916 if (result == SCPE_OK) {                                /* assignment successful? */
917     if (idx <= max)                                     /* any lines not specified? */
918         for (line = 0; line <= max; line++)             /* fill them in sequentially */
919             if (set [line] == FALSE) {                  /* specified? */
920                 list [idx] = line;                      /* no, so add it */
921                 idx = idx + 1;
922                 }
923 
924     memcpy (mp->lnorder, list, mp->lines * sizeof (int32)); /* copy working array to connection array */
925     }
926 
927 free (list);                                            /* free list allocation */
928 free (set);                                             /* free set allocation */
929 
930 return result;
931 }
932 
933 /* Show line connection order.
934 
935    Parameters:
936     - st   = stream on which output is to be written
937     - uptr = (not used)
938     - val  = (not used)
939     - desc = pointer to multiplexer's TMXR structure
940 
941    If a connection order array is not defined in the multiplexer descriptor, the
942    command is rejected.  If the first value of the connection order array is set
943    to -1, then the connection order is sequential.  Otherwise, the line values
944    in the array are printed as a semicolon-separated list.  Ranges are printed
945    where possible to shorten the output.
946 */
947 
tmxr_show_lnorder(FILE * st,UNIT * uptr,int32 val,void * desc)948 t_stat tmxr_show_lnorder (FILE *st, UNIT *uptr, int32 val, void *desc)
949 {
950 int32 i, j, low, last;
951 TMXR *mp = (TMXR *) desc;
952 int32 *iptr = mp->lnorder;
953 t_bool first = TRUE;
954 
955 if (iptr == NULL)                                       /* connection order undefined? */
956     return SCPE_NXPAR;                                  /* "Non-existent parameter" error */
957 
958 if (*iptr < 0)                                          /* sequential order indicated? */
959     fprintf (st, "Order=0-%d\n", mp->lines - 1);        /* print full line range */
960 
961 else {
962     low = last = *iptr++;                               /* set first line value */
963 
964     for (j = 1; j <= mp->lines; j++) {                  /* print remaining lines in order list */
965         if (j < mp->lines)                              /* more lines to process? */
966             i = *iptr++;                                /* get next line in list */
967         else                                            /* final iteration */
968             i = -1;                                     /* get "tie-off" value */
969 
970         if (i != last + 1) {                            /* end of a range? */
971             if (first) {                                /* first line to print? */
972                 fputs ("Order=", st);                   /* print header */
973                 first = FALSE;
974                 }
975 
976             else                                        /* not first line printed */
977                 fputc (';', st);                        /* print separator */
978 
979             if (low == last)                            /* range null? */
980                 fprintf (st, "%d", last);               /* print single line value */
981 
982             else                                        /* range established */
983                 fprintf (st, "%d-%d", low, last);       /* print start and end line */
984 
985             low = i;                                    /* start new range */
986             }
987 
988         last = i;                                       /* note value for range check */
989         }
990     }
991 
992 if (first == FALSE)                                     /* sanity check for lines == 0 */
993     fputc ('\n', st);
994 
995 return SCPE_OK;
996 }
997 
998 /* Show summary processor */
999 
tmxr_show_summ(FILE * st,UNIT * uptr,int32 val,void * desc)1000 t_stat tmxr_show_summ (FILE *st, UNIT *uptr, int32 val, void *desc)
1001 {
1002 TMXR *mp = (TMXR *) desc;
1003 int32 i, t;
1004 
1005 if (mp == NULL)
1006     return SCPE_IERR;
1007 for (i = t = 0; i < mp->lines; i++)
1008     t = t + (mp->ldsc[i].conn != 0);
1009 if (t == 1)
1010     fprintf (st, "1 connection");
1011 else fprintf (st, "%d connections", t);
1012 return SCPE_OK;
1013 }
1014 
1015 /* Show conn/stat processor */
1016 
tmxr_show_cstat(FILE * st,UNIT * uptr,int32 val,void * desc)1017 t_stat tmxr_show_cstat (FILE *st, UNIT *uptr, int32 val, void *desc)
1018 {
1019 TMXR *mp = (TMXR *) desc;
1020 int32 i, any;
1021 
1022 if (mp == NULL)
1023     return SCPE_IERR;
1024 for (i = any = 0; i < mp->lines; i++) {
1025     if (mp->ldsc[i].conn) {
1026         any++;
1027         if (val)
1028             tmxr_fconns (st, &mp->ldsc[i], i);
1029         else tmxr_fstats (st, &mp->ldsc[i], i);
1030         }
1031     }
1032 if (any == 0)
1033     fprintf (st, (mp->lines == 1? "disconnected\n": "all disconnected\n"));
1034 return SCPE_OK;
1035 }
1036 
1037 /* Show number of lines */
1038 
tmxr_show_lines(FILE * st,UNIT * uptr,int32 val,void * desc)1039 t_stat tmxr_show_lines (FILE *st, UNIT *uptr, int32 val, void *desc)
1040 {
1041 TMXR *mp = (TMXR *) desc;
1042 
1043 if (mp == NULL)
1044     return SCPE_IERR;
1045 fprintf (st, "lines=%d", mp->lines);
1046 return SCPE_OK;
1047 }
1048 
1049