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