| /* sim_tmxr.c: Telnet terminal multiplexor library | |
| Copyright (c) 2001, Robert M Supnik | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| Except as contained in this notice, the name of Robert M Supnik shall not | |
| be used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from Robert M Supnik. | |
| Based on the original DZ11 simulator by Thord Nilson, as updated by | |
| Arthur Krewat. | |
| 20-Oct-01 RMS Fixed bugs in read logic (found by Thord Nilson). | |
| added tmxr_rqln, tmxr_tqln | |
| */ | |
| #include "sim_defs.h" | |
| #include "sim_sock.h" | |
| #include "sim_tmxr.h" | |
| /* Telnet protocol constants - negatives are for init'ing signed char data */ | |
| #define TN_IAC -1 /* protocol delim */ | |
| #define TN_DONT -2 /* dont */ | |
| #define TN_DO -3 /* do */ | |
| #define TN_WONT -4 /* wont */ | |
| #define TN_WILL -5 /* will */ | |
| #define TN_BIN 0 /* bin */ | |
| #define TN_ECHO 1 /* echo */ | |
| #define TN_SGA 3 /* sga */ | |
| #define TN_LINE 34 /* line mode */ | |
| #define TN_CR 015 /* carriage return */ | |
| /* Telnet line states */ | |
| #define TNS_NORM 000 /* normal */ | |
| #define TNS_IAC 001 /* IAC seen */ | |
| #define TNS_WILL 002 /* WILL seen */ | |
| #define TNS_WONT 003 /* WONT seen */ | |
| #define TNS_SKIP 004 /* skip next */ | |
| void tmxr_rmvrc (TMLN *lp, int32 p); | |
| extern int32 sim_switches; | |
| extern char sim_name[]; | |
| extern FILE *sim_log; | |
| extern uint32 sim_os_msec (void); | |
| /* Poll for new connection | |
| Called from unit service routine to test for new connection | |
| Inputs: | |
| *mp = pointer to terminal multiplexor descriptor | |
| Outputs: | |
| line number activated, -1 if none | |
| */ | |
| int32 tmxr_poll_conn (TMXR *mp, UNIT *uptr) | |
| { | |
| SOCKET newsock; | |
| TMLN *lp; | |
| int32 i; | |
| uint32 ipaddr; | |
| static char mantra[] = { | |
| TN_IAC, TN_WILL, TN_LINE, | |
| TN_IAC, TN_WILL, TN_SGA, | |
| TN_IAC, TN_WILL, TN_ECHO, | |
| TN_IAC, TN_WILL, TN_BIN, | |
| TN_IAC, TN_DO, TN_BIN }; | |
| newsock = sim_accept_conn (mp -> master, uptr, &ipaddr);/* poll connect */ | |
| if (newsock != INVALID_SOCKET) { /* got a live one? */ | |
| for (i = 0; i < mp -> lines; i++) { /* find avail line */ | |
| lp = mp -> ldsc[i]; /* ptr to ln desc */ | |
| if (lp -> conn == 0) break; } /* available? */ | |
| if (i >= mp -> lines) { /* all busy? */ | |
| tmxr_msg (newsock, "All connections busy... please try later\r\n"); | |
| sim_close_sock (newsock, 0); } | |
| else { lp = mp -> ldsc[i]; /* get line desc */ | |
| lp -> conn = newsock; /* record connection */ | |
| lp -> ipad = ipaddr; /* ip address */ | |
| lp -> cnms = sim_os_msec (); /* time of conn */ | |
| lp -> rxbpr = lp -> rxbpi = 0; /* init buf pointers */ | |
| lp -> txbpr = lp -> txbpi = 0; | |
| lp -> rxcnt = lp -> txcnt = 0; /* init counters */ | |
| lp -> tsta = 0; /* init telnet state */ | |
| lp -> xmte = 1; /* enable transmit */ | |
| lp -> dstb = 0; /* default bin mode */ | |
| sim_write_sock (newsock, mantra, 15); | |
| tmxr_msg (newsock, "\n\r\nWelcome to the "); | |
| tmxr_msg (newsock, sim_name); | |
| tmxr_msg (newsock, " simulator\r\n\n"); | |
| return i; } | |
| } /* end if newsock */ | |
| return -1; | |
| } | |
| /* Reset line */ | |
| void tmxr_reset_ln (TMLN *lp) | |
| { | |
| sim_close_sock (lp -> conn, 0); /* reset conn */ | |
| lp -> conn = lp -> tsta = 0; /* reset state */ | |
| lp -> rxbpr = lp -> rxbpi = 0; | |
| lp -> txbpr = lp -> txbpi = 0; | |
| lp -> xmte = 1; | |
| lp -> dstb = 0; | |
| return; | |
| } | |
| /* Get character from specific line | |
| Inputs: | |
| *lp = pointer to terminal line descriptor | |
| Output: | |
| valid + char, 0 if line | |
| */ | |
| int32 tmxr_getc_ln (TMLN *lp) | |
| { | |
| int32 j, val = 0; | |
| uint32 tmp; | |
| if (lp -> conn && lp -> rcve) { /* conn & enb? */ | |
| j = lp -> rxbpi - lp -> rxbpr; /* # input chrs */ | |
| if (j) { /* any? */ | |
| tmp = lp -> rxb[lp -> rxbpr]; /* get char */ | |
| lp -> rxbpr = lp -> rxbpr + 1; /* adv pointer */ | |
| val = TMXR_VALID | (tmp & 0377); } /* valid + chr */ | |
| } /* end if conn */ | |
| if (lp -> rxbpi == lp -> rxbpr) /* empty? zero ptrs */ | |
| lp -> rxbpi = lp -> rxbpr = 0; | |
| return val; | |
| } | |
| /* Poll for input | |
| Inputs: | |
| *mp = pointer to terminal multiplexor descriptor | |
| Outputs: none | |
| */ | |
| void tmxr_poll_rx (TMXR *mp) | |
| { | |
| int32 i, nbytes, j; | |
| TMLN *lp; | |
| for (i = 0; i < mp -> lines; i++) { /* loop thru lines */ | |
| lp = mp -> ldsc[i]; /* get line desc */ | |
| if (!lp -> conn || !lp -> rcve) continue; /* skip if !conn */ | |
| nbytes = 0; | |
| if (lp -> rxbpi == 0) /* need input? */ | |
| nbytes = sim_read_sock (lp -> conn, /* yes, read */ | |
| &(lp -> rxb[lp -> rxbpi]), /* leave spc for */ | |
| TMXR_MAXBUF - TMXR_GUARD); /* Telnet cruft */ | |
| else if (lp -> tsta) /* in Telnet seq? */ | |
| nbytes = sim_read_sock (lp -> conn, /* yes, read to end */ | |
| &(lp -> rxb[lp -> rxbpi]), | |
| TMXR_MAXBUF - lp -> rxbpi); | |
| if (nbytes < 0) tmxr_reset_ln (lp); /* closed? reset ln */ | |
| else if (nbytes > 0) { /* if data rcvd */ | |
| j = lp -> rxbpi; /* start of data */ | |
| lp -> rxbpi = lp -> rxbpi + nbytes; /* adv pointers */ | |
| lp -> rxcnt = lp -> rxcnt + nbytes; | |
| /* Examine new data, remove TELNET cruft before making input available */ | |
| for (; j < lp -> rxbpi; ) { /* loop thru char */ | |
| char tmp = lp -> rxb[j]; /* get char */ | |
| switch (lp -> tsta) { /* case tlnt state */ | |
| case TNS_NORM: /* normal */ | |
| if (tmp == TN_IAC) { /* IAC? */ | |
| lp -> tsta = TNS_IAC; /* change state */ | |
| tmxr_rmvrc (lp, j); /* remove char */ | |
| break; } | |
| if ((tmp == TN_CR) && lp -> dstb) /* CR, no bin */ | |
| lp -> tsta = TNS_SKIP; /* skip next */ | |
| j = j + 1; /* advance j */ | |
| break; | |
| case TNS_IAC: /* IAC prev */ | |
| if (tmp == TN_WILL) /* IAC + WILL? */ | |
| lp -> tsta = TNS_WILL; | |
| else if (tmp == TN_WONT) /* IAC + WONT? */ | |
| lp -> tsta = TNS_WONT; | |
| else lp -> tsta = TNS_SKIP; /* IAC + other */ | |
| tmxr_rmvrc (lp, j); /* remove char */ | |
| break; | |
| case TNS_WILL: case TNS_WONT: /* IAC+WILL/WONT prev */ | |
| if (tmp == TN_BIN) { /* BIN? */ | |
| if (lp -> tsta == TNS_WILL) lp -> dstb = 0; | |
| else lp -> dstb = 1; } | |
| case TNS_SKIP: default: /* skip char */ | |
| lp -> tsta = TNS_NORM; /* next normal */ | |
| tmxr_rmvrc (lp, j); /* remove char */ | |
| break; } /* end case state */ | |
| } /* end for char */ | |
| } /* end else nbytes */ | |
| } /* end for lines */ | |
| for (i = 0; i < mp -> lines; i++) { /* loop thru lines */ | |
| lp = mp -> ldsc[i]; /* get line desc */ | |
| if (lp -> rxbpi == lp -> rxbpr) /* if buf empty, */ | |
| lp -> rxbpi = lp -> rxbpr = 0; /* reset pointers */ | |
| } /* end for */ | |
| return; | |
| } | |
| /* Return count of available characters for line */ | |
| int32 tmxr_rqln (TMLN *lp) | |
| { | |
| return (lp -> rxbpi - lp -> rxbpr); | |
| } | |
| /* Remove character p from line l input buffer */ | |
| void tmxr_rmvrc (TMLN *lp, int32 p) | |
| { | |
| for ( ; p < lp -> rxbpi; p++) lp -> rxb[p] = lp -> rxb[p + 1]; | |
| lp -> rxbpi = lp -> rxbpi - 1; | |
| return; | |
| } | |
| /* Store character in line buffer | |
| Inputs: | |
| *lp = pointer to line descriptor | |
| chr = characters | |
| Outputs: | |
| none | |
| */ | |
| void tmxr_putc_ln (TMLN *lp, int32 chr) | |
| { | |
| if (lp -> conn == 0) return; /* no conn? done */ | |
| if (lp -> txbpi < TMXR_MAXBUF) { /* room for char? */ | |
| lp -> txb[lp -> txbpi] = (char) chr; /* buffer char */ | |
| lp -> txbpi = lp -> txbpi + 1; /* adv pointer */ | |
| if (lp -> txbpi > (TMXR_MAXBUF - TMXR_GUARD)) /* near full? */ | |
| lp -> xmte = 0; } /* disable line */ | |
| else lp -> xmte = 0; /* disable line */ | |
| return; | |
| } | |
| /* Poll for output | |
| Inputs: | |
| *mp = pointer to terminal multiplexor descriptor | |
| Outputs: | |
| none | |
| */ | |
| void tmxr_poll_tx (TMXR *mp) | |
| { | |
| int32 i, nbytes, sbytes; | |
| TMLN *lp; | |
| for (i = 0; i < mp -> lines; i++) { /* loop thru lines */ | |
| lp = mp -> ldsc[i]; /* get line desc */ | |
| if (lp -> conn == 0) continue; /* skip if !conn */ | |
| nbytes = lp -> txbpi - lp -> txbpr; /* avail bytes */ | |
| if (nbytes) { /* >0? write */ | |
| sbytes = sim_write_sock (lp -> conn, | |
| &(lp -> txb[lp -> txbpr]), nbytes); | |
| if (sbytes != SOCKET_ERROR) { /* update ptrs */ | |
| lp -> txbpr = lp -> txbpr + sbytes; | |
| lp -> txcnt = lp -> txcnt + sbytes; | |
| nbytes = nbytes - sbytes; } | |
| } | |
| if (nbytes == 0) { /* buf empty? */ | |
| lp -> xmte = 1; /* enable this line */ | |
| lp -> txbpr = lp -> txbpi = 0; } | |
| } /* end for */ | |
| return; | |
| } | |
| /* Return count of buffered characters for line */ | |
| int32 tmxr_tqln (TMLN *lp) | |
| { | |
| return (lp -> txbpi - lp -> txbpr); | |
| } | |
| /* Attach */ | |
| t_stat tmxr_attach (TMXR *mp, UNIT *uptr, char *cptr) | |
| { | |
| char* tptr; | |
| int32 i, port; | |
| SOCKET sock; | |
| TMLN *lp; | |
| t_stat r; | |
| extern int32 sim_switches; | |
| port = (int32) get_uint (cptr, 10, 65535, &r); /* get port */ | |
| if ((r != SCPE_OK) || (port == 0)) return SCPE_ARG; | |
| tptr = malloc (strlen (cptr) + 1); /* get string buf */ | |
| if (tptr == NULL) return SCPE_MEM; /* no more mem? */ | |
| sock = sim_master_sock (port); /* make master socket */ | |
| if (sock == INVALID_SOCKET) { /* open error */ | |
| free (tptr); /* release buf */ | |
| return SCPE_OPENERR; } | |
| printf ("Listening on socket %d\n", sock); | |
| if (sim_log) fprintf (sim_log, "Listening on socket %d\n", sock); | |
| mp -> master = sock; /* save master socket */ | |
| strcpy (tptr, cptr); /* copy port */ | |
| uptr -> filename = tptr; /* save */ | |
| uptr -> flags = uptr -> flags | UNIT_ATT; /* no more errors */ | |
| for (i = 0; i < mp -> lines; i++) { /* initialize lines */ | |
| lp = mp -> ldsc[i]; | |
| lp -> conn = lp -> tsta = 0; | |
| lp -> rxbpi = lp -> rxbpr = 0; | |
| lp -> txbpi = lp -> txbpr = 0; | |
| lp -> rxcnt = lp -> txcnt = 0; | |
| lp -> xmte = 1; | |
| lp -> dstb = 0; } | |
| return SCPE_OK; | |
| } | |
| /* Detach */ | |
| t_stat tmxr_detach (TMXR *mp, UNIT *uptr) | |
| { | |
| int32 i; | |
| TMLN *lp; | |
| if ((uptr -> flags & UNIT_ATT) == 0) return SCPE_OK; /* attached? */ | |
| for (i = 0; i < mp -> lines; i++) { /* loop thru conn */ | |
| lp = mp -> ldsc[i]; | |
| if (lp -> conn) { | |
| tmxr_msg (lp -> conn, "\r\n"); | |
| tmxr_msg (lp -> conn, sim_name); | |
| tmxr_msg (lp -> conn, " simulator shutting down... please come back later\r\n\n"); | |
| tmxr_reset_ln (lp); } /* end if conn */ | |
| } /* end for */ | |
| sim_close_sock (mp -> master, 1); /* close master socket */ | |
| mp -> master = 0; | |
| free (uptr -> filename); /* free port string */ | |
| uptr -> filename = NULL; | |
| uptr -> flags = uptr -> flags & ~UNIT_ATT; /* not attached */ | |
| return SCPE_OK; | |
| } | |
| /* Stub examine and deposit */ | |
| t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) | |
| { | |
| return SCPE_NOFNC; | |
| } | |
| t_stat tmxr_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw) | |
| { | |
| return SCPE_NOFNC; | |
| } | |
| /* Output message */ | |
| void tmxr_msg (SOCKET sock, char *msg) | |
| { | |
| if (sock) sim_write_sock (sock, msg, strlen (msg)); | |
| return; | |
| } | |
| /* Print line status */ | |
| void tmxr_fstatus (FILE *st, TMLN *lp, int32 ln) | |
| { | |
| if (ln >= 0) fprintf (st, "\n line %d", ln); | |
| if (lp -> conn) { | |
| int32 o1, o2, o3, o4, hr, mn, sc; | |
| uint32 ctime; | |
| o1 = (lp -> ipad >> 24) & 0xFF; | |
| o2 = (lp -> ipad >> 16) & 0xFF; | |
| o3 = (lp -> ipad >> 8) & 0xFF; | |
| o4 = (lp -> ipad) & 0xFF; | |
| ctime = (sim_os_msec () - lp -> cnms) / 1000; | |
| hr = ctime / 3600; | |
| mn = (ctime / 60) % 60; | |
| sc = ctime % 3600; | |
| fprintf (st, ": IP address %d.%d.%d.%d", o1, o2, o3, o4); | |
| if (ctime) fprintf (st, ", connected %02d:%02d:%02d", hr, mn, sc); } | |
| else fprintf (st, ": disconnected"); | |
| return; | |
| } |