root/src/dps8/uvutil.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. alloc_buffer
  2. accessWriteCallback
  3. accessReadCallback
  4. accessReadStart
  5. accessStartWriteActual
  6. accessStartWrite
  7. accessPutCharForce
  8. accessPutStrForce
  9. accessStartWriteStr
  10. accessLogon
  11. accessCloseCallback
  12. accessCloseConnection
  13. accessProcessInput
  14. accessTelnetReadCallback
  15. evHandler
  16. accessTelnetConnect
  17. onNewAccess
  18. uv_open_access
  19. accessPutChar
  20. accessGetChar
  21. accessPutStr

   1 /*
   2  * vim: filetype=c:tabstop=4:ai:expandtab
   3  * SPDX-License-Identifier: ICU
   4  * scspell-id: 6315eaa3-f630-11ec-96fc-80ee73e9b8e7
   5  *
   6  * ---------------------------------------------------------------------------
   7  *
   8  * Copyright (c) 2013-2019 Charles Anthony
   9  * Copyright (c) 2021 Jeffrey H. Johnson <trnsz@pobox.com>
  10  * Copyright (c) 2021-2022 The DPS8M Development Team
  11  *
  12  * All rights reserved.
  13  *
  14  * This software is made available under the terms of the ICU
  15  * License, version 1.8.1 or later.  For more details, see the
  16  * LICENSE.md file at the top-level directory of this distribution.
  17  *
  18  * ---------------------------------------------------------------------------
  19  */
  20 
  21 #include <uv.h>
  22 #include <ctype.h>
  23 #include <signal.h>
  24 #include "dps8.h"
  25 #include "dps8_sys.h"
  26 #include "dps8_cpu.h"
  27 #include "dps8_utils.h"
  28 #include "libtelnet.h"
  29 #include "uvutil.h"
  30 
  31 #ifdef TESTING
  32 # undef realloc
  33 # undef FREE
  34 # define FREE(p) free(p)
  35 # define realloc trealloc
  36 #endif /* ifdef TESTING */
  37 
  38 #define USE_REQ_DATA
  39 
  40 static void accessTelnetReadCallback (uv_tcp_t * client,
  41                             ssize_t nread,
  42                             unsigned char * buf);
  43 
  44 //
  45 // alloc_buffer: libuv callback handler to allocate buffers for incoming data.
  46 //
  47 
  48 static void alloc_buffer (UNUSED uv_handle_t * handle, size_t suggested_size,
     /* [previous][next][first][last][top][bottom][index][help] */
  49                           uv_buf_t * buf)
  50   {
  51 /* Suppress Clang Analyzer's possible memory leak warning */
  52 #if !defined ( __clang_analyzer__ )
  53     * buf = uv_buf_init ((char *) malloc (suggested_size),
  54                          (uint) suggested_size);
  55 #endif /* if !defined ( __clang_analyzer__ ) */
  56   }
  57 
  58 static void accessWriteCallback (uv_write_t * req, int status)
     /* [previous][next][first][last][top][bottom][index][help] */
  59   {
  60     if (status < 0)
  61       {
  62         if (status == -ECONNRESET || status == -ECANCELED ||
  63             status == -EPIPE)
  64           {
  65             // This occurs when the other end disconnects; not an "error"
  66           }
  67         else
  68           {
  69             sim_warn ("accessWriteCallback status %d (%s)\n", -status,
  70                       strerror (-status));
  71           }
  72 
  73         // connection reset by peer
  74         accessCloseConnection (req->handle);
  75       }
  76 
  77 #ifdef USE_REQ_DATA
  78     FREE (req->data);
  79 #else
  80     unsigned int nbufs = req->nbufs;
  81     uv_buf_t * bufs = req->bufs;
  82     //if (! bufs)
  83 
  84 
  85 
  86 
  87     for (unsigned int i = 0; i < nbufs; i ++)
  88       {
  89         if (bufs && bufs[i].base)
  90           {
  91             FREE (bufs[i].base);
  92             //bufp->base = NULL;
  93           }
  94         if (req->bufsml[i].base)
  95           {
  96             FREE (req->bufsml[i].base);
  97           }
  98       }
  99 #endif
 100 
 101     // the buf structure is copied; do not free.
 102 //sim_printf ("freeing req %p\n", req);
 103     FREE (req);
 104   }
 105 
 106 //
 107 // accessReadCallback: libuv read complete callback
 108 //
 109 //   Cleanup on read error.
 110 //   Forward data to appropriate handler.
 111 
 112 static void accessReadCallback (uv_stream_t* stream,
     /* [previous][next][first][last][top][bottom][index][help] */
 113                              ssize_t nread,
 114                              const uv_buf_t* buf)
 115   {
 116     uv_access * access = (uv_access *) stream->data;
 117     if (nread < 0)
 118       {
 119         //if (nread == UV_EOF)
 120           {
 121             accessCloseConnection (stream);
 122           }
 123       }
 124     else if (nread > 0)
 125       {
 126         if (access->telnetp)
 127           {
 128             telnet_recv (access->telnetp, buf->base, (size_t) nread);
 129           }
 130         else
 131           {
 132             accessTelnetReadCallback ((uv_tcp_t *) stream,
 133                                       (ssize_t) nread,
 134                                       (unsigned char *) buf->base);
 135           }
 136       }
 137     if (buf->base)
 138         free (buf->base);
 139   }
 140 
 141 //
 142 // Enable reading on connection
 143 //
 144 
 145 static void accessReadStart (uv_tcp_t * client)
     /* [previous][next][first][last][top][bottom][index][help] */
 146   {
 147     if (! client || uv_is_closing ((uv_handle_t *) client))
 148       return;
 149     uv_read_start ((uv_stream_t *) client, alloc_buffer, accessReadCallback);
 150   }
 151 
 152 // Create and start a write request
 153 
 154 static void accessStartWriteActual (uv_tcp_t * client, char * data,
     /* [previous][next][first][last][top][bottom][index][help] */
 155                                         ssize_t datalen)
 156   {
 157     if (! client || uv_is_closing ((uv_handle_t *) client))
 158       return;
 159 /* Suppress Clang Analyzer's possible memory leak warning */
 160 #if !defined ( __clang_analyzer__ )
 161     uv_write_t * req = (uv_write_t *) malloc (sizeof (uv_write_t));
 162     if (!req)
 163       {
 164         fprintf (stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 165                  __func__, __FILE__, __LINE__);
 166 # if defined(USE_BACKTRACE)
 167 #  ifdef SIGUSR2
 168         (void)raise(SIGUSR2);
 169         /*NOTREACHED*/ /* unreachable */
 170 #  endif /* ifdef SIGUSR2 */
 171 # endif /* if defined(USE_BACKTRACE) */
 172         abort();
 173       }
 174     // This makes sure that bufs*.base and bufsml*.base are NULL
 175     memset (req, 0, sizeof (uv_write_t));
 176     uv_buf_t buf = uv_buf_init ((char *) malloc ((unsigned long) datalen),
 177                                                  (uint) datalen);
 178 # ifdef USE_REQ_DATA
 179     req->data = buf.base;
 180 # endif
 181     memcpy (buf.base, data, (unsigned long) datalen);
 182 //sim_printf ("write %u<%s>\n", datalen, data);
 183     int ret = uv_write (req, (uv_stream_t *) client, & buf, 1,
 184                         accessWriteCallback);
 185 // There seems to be a race condition when Multics signals a disconnect_line;
 186 // We close the socket, but Multics is still writing its goodbye text trailing
 187 // NULs.
 188 // If the socket has been closed, write will return BADF; just ignore it.
 189     if (ret < 0 && ret != -EBADF)
 190       sim_printf ("uv_write returns %d\n", ret);
 191 #endif /* if !defined ( __clang_analyzer__ ) */
 192   }
 193 
 194 void accessStartWrite (uv_tcp_t * client, char * data, ssize_t datalen)
     /* [previous][next][first][last][top][bottom][index][help] */
 195   {
 196     if ((client == NULL) || uv_is_closing ((uv_handle_t *) client))
 197       return;
 198     uv_access * access = (uv_access *) client->data;
 199     if (access->telnetp)
 200       telnet_send (access->telnetp, data, (size_t) datalen);
 201     else
 202       accessStartWriteActual (client, data, (ssize_t) datalen);
 203   }
 204 
 205 static void accessPutCharForce (uv_access * access, char ch)
     /* [previous][next][first][last][top][bottom][index][help] */
 206   {
 207     //sim_putchar (ch);
 208     accessStartWrite (access->client, & ch, 1);
 209   }
 210 
 211 static void accessPutStrForce (uv_access * access, char * str)
     /* [previous][next][first][last][top][bottom][index][help] */
 212   {
 213     size_t l = strlen (str);
 214     accessStartWrite (access->client, str, (ssize_t) l);
 215   }
 216 
 217 void accessStartWriteStr (uv_tcp_t * client, char * data)
     /* [previous][next][first][last][top][bottom][index][help] */
 218   {
 219     accessStartWrite (client, data, (ssize_t) strlen (data));
 220   }
 221 
 222 static void accessLogon (uv_access * access, unsigned char * buf, ssize_t nread)
     /* [previous][next][first][last][top][bottom][index][help] */
 223   {
 224     for (ssize_t nchar = 0; nchar < nread; nchar ++)
 225       {
 226         unsigned char kar = buf[nchar];
 227         // buffer too full for anything more?
 228         if ((unsigned long) access->pwPos >= sizeof (access->pwBuffer) - 1)
 229           {
 230             // yes. Only allow \n, \r, ^H, ^R
 231             switch (kar)
 232               {
 233                 case '\b':  // backspace
 234                 case 127:   // delete
 235                   {
 236                     // remove char from line
 237                     accessPutStrForce (access, "\b \b");
 238                     access->pwBuffer[access->pwPos] = 0;     // remove char from buffer
 239                     if (access->pwPos > 0)
 240                       access->pwPos -= 1;             // back up buffer pointer
 241                   }
 242                   break;
 243 
 244                 case '\n':
 245                 case '\r':
 246                   {
 247                     access->pwBuffer[access->pwPos] = 0;
 248                     goto check;
 249                   }
 250 
 251                 case 0x12:  // ^R
 252                   {
 253                     accessPutStrForce (access, "^R\r\n"); // echo ^R
 254                     access->connectPrompt (access->client);
 255                     accessPutStrForce (access, access->pwBuffer);
 256                   }
 257                  break;
 258 
 259                 default:
 260                   break;
 261               } // switch kar
 262             continue; // process next character in buffer
 263           } // if buffer full
 264 
 265         if (isprint (kar))   // printable?
 266           {
 267             accessPutCharForce (access, '*');
 268             access->pwBuffer[access->pwPos++] = (char) kar;
 269             access->pwBuffer[access->pwPos] = 0;
 270           }
 271         else
 272           {
 273             switch (kar)
 274               {
 275                 case '\b':  // backspace
 276                 case 127:   // delete
 277                   {
 278                     // remove char from line
 279                     accessPutStrForce (access, "\b \b");
 280                    // remove char from buffer
 281                     access->pwBuffer[access->pwPos] = 0;
 282                     if (access->pwPos > 0)
 283                       access->pwPos -= 1;   // back up buffer pointer
 284                   }
 285                   break;
 286 
 287                 case '\n':
 288                 case '\r':
 289                   {
 290                     access->pwBuffer[access->pwPos] = 0;
 291                     goto check;
 292                   }
 293 
 294                 case 0x12:  // ^R
 295                   {
 296                     accessPutStrForce (access, "^R\r\n"); // echo ^R
 297                     access->connectPrompt (access->client);
 298                     accessPutStrForce (access, access->pwBuffer);
 299                   }
 300                   break;
 301 
 302                 default:
 303                   break;
 304               } // switch kar
 305           } // not printable
 306       } // for nchar
 307     return;
 308 
 309 check:;
 310     char cpy[access->pwPos + 1];
 311     memcpy (cpy, access->pwBuffer, (unsigned long) access->pwPos);
 312     cpy[access->pwPos] = 0;
 313     trim (cpy);
 314     //sim_printf ("<%s>", cpy);
 315     access->pwPos = 0;
 316     accessPutStrForce (access, "\r\n");
 317 
 318     if (strcmp (cpy, access->pw) == 0)
 319       {
 320         accessPutStrForce (access, "ok\r\n");
 321         sim_printf ("\r[OPC emulation: ACCESS GRANTED]\r\n");
 322         goto associate;
 323       }
 324     else
 325       {
 326         //accessPutStrForce ("<");
 327         //accessPutStrForce (access->pwBuffer);
 328         //accessPutStrForce (">\r\n");
 329         accessPutStrForce (access, "nope\r\n");
 330         sim_printf ("\r[OPC emulation: INVALID PASSWORD]\r\n");
 331         goto reprompt;
 332       }
 333 
 334 reprompt:;
 335     access->connectPrompt (access->client);
 336     return;
 337 
 338 associate:;
 339     access->loggedOn = true;
 340     if (access->connected)
 341       access->connected (access->client);
 342   }
 343 
 344 static void accessCloseCallback (uv_handle_t * stream)
     /* [previous][next][first][last][top][bottom][index][help] */
 345   {
 346     FREE (stream);
 347     //access->client = NULL;
 348   }
 349 
 350 void accessCloseConnection (uv_stream_t* stream)
     /* [previous][next][first][last][top][bottom][index][help] */
 351   {
 352     uv_access * access = (uv_access *) stream->data;
 353     sim_printf ("\r[OPC emulation: DISCONNECT]\r\n");
 354     // Clean up allocated data
 355     if (access->telnetp)
 356       {
 357         telnet_free (access->telnetp);
 358         access->telnetp = NULL;
 359       }
 360     if (! uv_is_closing ((uv_handle_t *) stream))
 361       uv_close ((uv_handle_t *) stream, accessCloseCallback);
 362     access->client = NULL;
 363   }
 364 
 365 static void accessProcessInput (uv_access * access, unsigned char * buf,
     /* [previous][next][first][last][top][bottom][index][help] */
 366                                  ssize_t nread)
 367   {
 368     if (access->inBuffer)
 369       {
 370         //sim_warn ("inBuffer overrun\n");
 371         unsigned char * new =
 372           realloc (access->inBuffer,
 373                    (unsigned long) (access->inSize + nread));
 374         if (! new)
 375           {
 376             fprintf (stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 377                      __func__, __FILE__, __LINE__);
 378 #if defined(USE_BACKTRACE)
 379 # ifdef SIGUSR2
 380             (void)raise(SIGUSR2);
 381             /*NOTREACHED*/ /* unreachable */
 382 # endif /* ifdef SIGUSR2 */
 383 #endif /* if defined(USE_BACKTRACE) */
 384             abort();
 385           }
 386         memcpy (new + access->inSize, buf, (unsigned long) nread);
 387         access->inSize += nread;
 388         access->inBuffer = new;
 389       }
 390     else
 391       {
 392         access->inBuffer = malloc ((unsigned long) nread);
 393         if (! access->inBuffer)
 394           {
 395             fprintf (stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 396                     __func__, __FILE__, __LINE__);
 397 #if defined(USE_BACKTRACE)
 398 # ifdef SIGUSR2
 399             (void)raise(SIGUSR2);
 400             /*NOTREACHED*/ /* unreachable */
 401 # endif /* ifdef SIGUSR2 */
 402 #endif /* if defined(USE_BACKTRACE) */
 403             abort();
 404           }
 405         memcpy (access->inBuffer, buf, (unsigned long) nread);
 406         access->inSize = (uint) nread;
 407         access->inUsed = 0;
 408       }
 409 
 410     // Prevent further reading until this buffer is consumed
 411     //fnpuv_read_stop (client);
 412     //if (! client || uv_is_closing ((uv_handle_t *) client))
 413       //return;
 414     //uv_read_stop ((uv_stream_t *) client);
 415   }
 416 
 417 static void accessTelnetReadCallback (uv_tcp_t * client,
     /* [previous][next][first][last][top][bottom][index][help] */
 418                             ssize_t nread,
 419                             unsigned char * buf)
 420   {
 421     uv_access * access = (uv_access *) client->data;
 422     if (access->loggedOn)
 423       accessProcessInput (access, buf, nread);
 424     else
 425       accessLogon (access, buf, nread);
 426   }
 427 
 428 static void evHandler (UNUSED telnet_t *telnet, telnet_event_t *event,
     /* [previous][next][first][last][top][bottom][index][help] */
 429                        void *user_data)
 430   {
 431     uv_tcp_t * client = (uv_tcp_t *) user_data;
 432 
 433     switch (event->type)
 434       {
 435         case TELNET_EV_DATA:
 436           {
 437             accessTelnetReadCallback (client, (ssize_t) event->data.size,
 438                             (unsigned char *)event->data.buffer);
 439           }
 440           break;
 441 
 442         case TELNET_EV_SEND:
 443           {
 444             accessStartWriteActual (client, (char *) event->data.buffer,
 445                                         (ssize_t) event->data.size);
 446           }
 447           break;
 448 
 449         case TELNET_EV_DO:
 450           {
 451             if (event->neg.telopt == TELNET_TELOPT_BINARY)
 452               {
 453                 // DO Binary
 454               }
 455             else if (event->neg.telopt == TELNET_TELOPT_SGA)
 456               {
 457                 // DO Suppress Go Ahead
 458               }
 459             else if (event->neg.telopt == TELNET_TELOPT_ECHO)
 460               {
 461                 // DO Suppress Echo
 462               }
 463             else
 464               {
 465                 sim_printf ("evHandler DO %d\n", event->neg.telopt);
 466               }
 467           }
 468           break;
 469 
 470         case TELNET_EV_DONT:
 471           {
 472             sim_printf ("evHandler DONT %d\n", event->neg.telopt);
 473           }
 474           break;
 475 
 476         case TELNET_EV_WILL:
 477           {
 478             if (event->neg.telopt != 3)
 479               sim_printf ("evHandler WILL %d\n", event->neg.telopt);
 480           }
 481           break;
 482 
 483         case TELNET_EV_WONT:
 484           {
 485             sim_printf ("evHandler WONT %d\n", event->neg.telopt);
 486           }
 487           break;
 488 
 489         case TELNET_EV_ERROR:
 490           {
 491             sim_warn ("libtelnet evHandler error <%s>\n", event->error.msg);
 492           }
 493           break;
 494 
 495         case TELNET_EV_IAC:
 496           {
 497             if (event->iac.cmd == 243 || // BRK
 498                 event->iac.cmd == 244)   // IP
 499               {
 500                 sim_warn ("libtelnet dropping unassociated BRK/IP\n");
 501               }
 502             else
 503               if ((!sim_quiet) || (event->iac.cmd != 241))
 504                 sim_warn ("libtelnet unhandled IAC event %d\n", event->iac.cmd);
 505           }
 506           break;
 507 
 508         default:
 509           sim_printf ("evHandler: unhandled event %d\n", event->type);
 510           break;
 511       }
 512 
 513   }
 514 
 515 static const telnet_telopt_t my_telopts[] =
 516   {
 517     { TELNET_TELOPT_SGA,       TELNET_WILL, TELNET_DO   },
 518     { TELNET_TELOPT_ECHO,      TELNET_WILL, TELNET_DONT },
 519   //{ TELNET_TELOPT_TTYPE,     TELNET_WONT, TELNET_DONT },
 520  //   { TELNET_TELOPT_BINARY,    TELNET_WILL, TELNET_DO   },
 521     { TELNET_TELOPT_BINARY,    TELNET_WONT, TELNET_DONT   },
 522   //{ TELNET_TELOPT_NAWS,      TELNET_WONT, TELNET_DONT },
 523     { -1, 0, 0 }
 524   };
 525 
 526 static void * accessTelnetConnect (uv_tcp_t * client)
     /* [previous][next][first][last][top][bottom][index][help] */
 527   {
 528     void * p = (void *) telnet_init (my_telopts, evHandler, 0, client);
 529     if (!p)
 530       {
 531         fprintf(stderr, "\rtelnet_init failed at %s[%s:%d]\r\n",
 532                 __func__, __FILE__, __LINE__);
 533         return NULL;
 534       }
 535     const telnet_telopt_t * q = my_telopts;
 536     while (q->telopt != -1)
 537       {
 538         telnet_negotiate (p, q->us, (unsigned char) q->telopt);
 539         q ++;
 540       }
 541     return p;
 542   }
 543 
 544 //
 545 // Connection callback handler
 546 //
 547 
 548 static void onNewAccess (uv_stream_t * server, int status)
     /* [previous][next][first][last][top][bottom][index][help] */
 549   {
 550     if (status < 0)
 551       {
 552         fprintf (stderr, "\r[OPC emulation: new connection error %s]\r\n", uv_strerror (status));
 553         // error!
 554         return;
 555       }
 556 
 557     uv_access * access = (uv_access *) server->data;
 558 
 559     uv_tcp_t * client = (uv_tcp_t *) malloc (sizeof (uv_tcp_t));
 560     if (!client)
 561       {
 562         fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 563                 __func__, __FILE__, __LINE__);
 564 #if defined(USE_BACKTRACE)
 565 # ifdef SIGUSR2
 566         (void)raise(SIGUSR2);
 567         /*NOTREACHED*/ /* unreachable */
 568 # endif /* ifdef SIGUSR2 */
 569 #endif /* if defined(USE_BACKTRACE) */
 570         abort();
 571       }
 572     uv_tcp_init (access->loop, client);
 573     client->data = server->data;
 574     if (uv_accept (server, (uv_stream_t *) client) == 0)
 575       {
 576         // Only a single connection at a time
 577         if (access->client)
 578           {
 579             sim_printf ("\r\n[OPC emulation: BUMPED]\r\n\r\n");
 580             accessPutStrForce (access, "\r[OPC emulation: BUMPED]\r\n");
 581             access->loggedOn = false;
 582             sim_printf ("\rMultics has disconnected you\r\n");
 583             accessCloseConnection ((uv_stream_t *) access->client);
 584           }
 585         access->client = client;
 586         uv_tcp_nodelay (client, 1);
 587         struct sockaddr name;
 588         int namelen = sizeof (name);
 589         int ret = uv_tcp_getpeername (access->client, & name, & namelen);
 590         if (ret < 0)
 591           {
 592             sim_printf ("\r[OPC emulation: CONNECT; addr err %d]\r\n", ret);
 593           }
 594         else
 595           {
 596             struct sockaddr_in * p = (struct sockaddr_in *) & name;
 597             sim_printf ("\r[OPC emulation: CONNECT %s]\r\n", inet_ntoa (p -> sin_addr));
 598           }
 599 
 600         if (access->useTelnet)
 601           {
 602             access->telnetp = accessTelnetConnect (access->client);
 603             if (!access->telnetp)
 604               {
 605                  sim_warn ("ltnConnect failed\n");
 606                  return;
 607               }
 608           }
 609         else
 610           {
 611             access->telnetp = NULL;
 612           }
 613         access->loggedOn =
 614           ! strlen (access->pw);
 615         if (access->loggedOn)
 616           access->connected (access->client);
 617         else
 618           access->connectPrompt (access->client);
 619         accessReadStart (access->client);
 620       }
 621     else
 622       {
 623         uv_close ((uv_handle_t *) client, accessCloseCallback);
 624       }
 625   }
 626 
 627 void uv_open_access (uv_access * access)
     /* [previous][next][first][last][top][bottom][index][help] */
 628   {
 629     if (access->open == true)
 630       {
 631         sim_printf ("\r[OPC emulation: OPC already initialized]\r\n");
 632       }
 633 
 634     if (! access->port)
 635       {
 636         //sim_printf ("access port disabled\n");
 637         return;
 638       }
 639 
 640     if (! access->loop)
 641       access->loop = uv_default_loop ();
 642 
 643     // Do we already have a listening port (ie not first time)?
 644     if (access->open)
 645       return;
 646 
 647     uv_tcp_init (access->loop, & access->server);
 648     access->server.data = (void *) access;
 649     struct sockaddr_in addr;
 650     uv_ip4_addr (access->address, access->port, & addr);
 651     uv_tcp_bind (& access->server, (const struct sockaddr *) & addr, 0);
 652 #define DEFAULT_BACKLOG 1024
 653     int r = uv_listen ((uv_stream_t *) & access->server,
 654                        DEFAULT_BACKLOG,
 655                        onNewAccess);
 656     if (r)
 657      {
 658         fprintf (stderr, "\r[OPC emulation: listen error: %s:%ld: %s]\r\n", access->address, (long) access->port, uv_strerror (r));
 659       }
 660     access->open = true;
 661     if (access->address != NULL)
 662       sim_printf ("\r[OPC emulation: TELNET server listening on %s:%ld]\r\n", access->address, (long) access->port);
 663     else
 664       sim_printf ("\r[OPC emulation: TELNET server listening on 0.0.0.0:%ld]\r\n", (long) access->port);
 665   }
 666 
 667 #ifndef QUIET_UNUSED
 668 void accessPutChar (uv_access * access, char ch)
     /* [previous][next][first][last][top][bottom][index][help] */
 669   {
 670     //sim_putchar (ch);
 671     if (access->loggedOn)
 672       accessStartWrite (access->client, & ch, 1);
 673   }
 674 #endif
 675 
 676 int accessGetChar (uv_access * access)
     /* [previous][next][first][last][top][bottom][index][help] */
 677   {
 678     // The connection could have closed when we were not looking
 679     if (! access->client)
 680       {
 681         if (access->inBuffer)
 682           FREE (access->inBuffer);
 683         access->inBuffer = NULL;
 684         access->inSize = 0;
 685         access->inUsed = 0;
 686         return SCPE_OK;
 687       }
 688 
 689     if (access->inBuffer && access->inUsed < access->inSize)
 690        {
 691          unsigned char c = access->inBuffer[access->inUsed ++];
 692          if (access->inUsed >= access->inSize)
 693            {
 694              FREE (access->inBuffer);
 695              access->inBuffer = NULL;
 696              access->inSize = 0;
 697              access->inUsed = 0;
 698              // The connection could have been closed when we weren't looking
 699              //if (access->client)
 700                //fnpuv_read_start (access->client);
 701            }
 702          return (int) c + SCPE_KFLAG;
 703        }
 704     return SCPE_OK;
 705 
 706   }
 707 
 708 #ifndef QUIET_UNUSED
 709 void accessPutStr (uv_access * access, char * str)
     /* [previous][next][first][last][top][bottom][index][help] */
 710   {
 711     size_t l = strlen (str);
 712     //for (size_t i = 0; i < l; i ++)
 713       //sim_putchar (str[i]);
 714     if (access->loggedOn)
 715       accessStartWrite (access->client, str, (ssize_t) l);
 716   }
 717 #endif

/* [previous][next][first][last][top][bottom][index][help] */