DOSBox-X
|
00001 /* 00002 * Copyright (C) 2002-2020 The DOSBox Team 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; either version 2 of the License, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License along 00015 * with this program; if not, write to the Free Software Foundation, Inc., 00016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 */ 00018 00019 00020 #include "dosbox.h" 00021 00022 #if C_MODEM 00023 00024 #include "control.h" 00025 #include "serialport.h" 00026 #include "nullmodem.h" 00027 00028 CNullModem::CNullModem(Bitu id, CommandLine* cmd):CSerial (id, cmd) { 00029 Bitu temptcpport=23; 00030 memset(&telClient, 0, sizeof(telClient)); 00031 InstallationSuccessful = false; 00032 serversocket = 0; 00033 clientsocket = 0; 00034 serverport = 0; 00035 clientport = 0; 00036 00037 rx_retry = 0; 00038 rx_retry_max = 20; 00039 rx_state=N_RX_DISC; 00040 00041 tx_gather = 12; 00042 00043 dtrrespect=false; 00044 tx_block=false; 00045 receiveblock=false; 00046 transparent=false; 00047 nonlocal=false; 00048 telnet=false; 00049 00050 Bitu bool_temp=0; 00051 00052 // usedtr: The nullmodem will 00053 // 1) when it is client connect to the server not immediately but 00054 // as soon as a modem-aware application is started (DTR is switched on). 00055 // 2) only receive data when DTR is on. 00056 if (getBituSubstring("usedtr:", &bool_temp, cmd)) { 00057 if (bool_temp==1) { 00058 dtrrespect=true; 00059 transparent=true; 00060 DTR_delta=false; // connect immediately when DTR is already 1 00061 } 00062 } 00063 // transparent: don't add additional handshake control. 00064 if (getBituSubstring("transparent:", &bool_temp, cmd)) { 00065 if (bool_temp==1) transparent=true; 00066 else transparent=false; 00067 } 00068 // nonlocal: enable connections not originating from localhost. 00069 // otherwise, connections not coming from localhost are rejected for security reasons. 00070 if (getBituSubstring("nonlocal:", &bool_temp, cmd)) { 00071 if (bool_temp==1) { 00072 nonlocal=true; 00073 } 00074 } 00075 // telnet: interpret telnet commands. 00076 if (getBituSubstring("telnet:", &bool_temp, cmd)) { 00077 if (bool_temp==1) { 00078 transparent=true; 00079 telnet=true; 00080 } 00081 } 00082 // rxdelay: How many milliseconds to wait before causing an 00083 // overflow when the application is unresponsive. 00084 if (getBituSubstring("rxdelay:", &rx_retry_max, cmd)) { 00085 if (!(rx_retry_max<=10000)) { 00086 rx_retry_max=50; 00087 } 00088 } 00089 // txdelay: How many milliseconds to wait before sending data. 00090 // This reduces network overhead quite a lot. 00091 if (getBituSubstring("txdelay:", &tx_gather, cmd)) { 00092 if (!(tx_gather<=500)) { 00093 tx_gather=12; 00094 } 00095 } 00096 // port is for both server and client 00097 if (getBituSubstring("port:", &temptcpport, cmd)) { 00098 if (!(temptcpport>0&&temptcpport<65536)) { 00099 temptcpport=23; 00100 } 00101 } 00102 // socket inheritance (client-alike) 00103 if (getBituSubstring("inhsocket:", &bool_temp, cmd)) { 00104 #ifdef NATIVESOCKETS 00105 if (Netwrapper_GetCapabilities()&NETWRAPPER_TCP_NATIVESOCKET) { 00106 if (bool_temp==1) { 00107 int sock; 00108 if (control->cmdline->FindInt("-socket",sock,true)) { 00109 dtrrespect=false; 00110 transparent=true; 00111 LOG_MSG("Inheritance socket handle: %d",sock); 00112 if (!ClientConnect(new TCPClientSocket(sock))) 00113 return; 00114 } else { 00115 LOG_MSG("Serial%d: -socket parameter missing.",(int)COMNUMBER); 00116 return; 00117 } 00118 } 00119 } else { 00120 LOG_MSG("Serial%d: socket inheritance not supported on this platform.",(int)COMNUMBER); 00121 return; 00122 } 00123 #else 00124 LOG_MSG("Serial%d: socket inheritance not available.",(int)COMNUMBER); 00125 #endif 00126 } else { 00127 // normal server/client 00128 std::string tmpstring; 00129 if (cmd->FindStringBegin("server:",tmpstring,false)) { 00130 // we are a client 00131 const char* hostnamechar=tmpstring.c_str(); 00132 size_t hostlen=strlen(hostnamechar)+1; 00133 if (hostlen>sizeof(hostnamebuffer)) { 00134 hostlen=sizeof(hostnamebuffer); 00135 hostnamebuffer[sizeof(hostnamebuffer)-1]=0; 00136 } 00137 memcpy(hostnamebuffer,hostnamechar,hostlen); 00138 clientport=(Bit16u)temptcpport; 00139 if (dtrrespect) { 00140 // we connect as soon as DTR is switched on 00141 setEvent(SERIAL_NULLMODEM_DTR_EVENT, 50); 00142 LOG_MSG("Serial%d: Waiting for DTR...",(int)COMNUMBER); 00143 } else if (!ClientConnect( 00144 new TCPClientSocket((char*)hostnamebuffer,(Bit16u)clientport))) 00145 return; 00146 } else { 00147 // we are a server 00148 serverport = (Bit16u)temptcpport; 00149 if (!ServerListen()) return; 00150 } 00151 } 00152 CSerial::Init_Registers(); 00153 InstallationSuccessful = true; 00154 00155 setCTS(dtrrespect||transparent); 00156 setDSR(dtrrespect||transparent); 00157 setRI(false); 00158 setCD(clientsocket != 0); // CD on if connection established 00159 } 00160 00161 CNullModem::~CNullModem() { 00162 if (serversocket) delete serversocket; 00163 if (clientsocket) delete clientsocket; 00164 // remove events 00165 for(Bit16u i = SERIAL_BASE_EVENT_COUNT+1; 00166 i <= SERIAL_NULLMODEM_EVENT_COUNT; i++) { 00167 removeEvent(i); 00168 } 00169 } 00170 00171 void CNullModem::WriteChar(Bit8u data) { 00172 if (clientsocket)clientsocket->SendByteBuffered(data); 00173 if (!tx_block) { 00174 //LOG_MSG("setevreduct"); 00175 setEvent(SERIAL_TX_REDUCTION, (float)tx_gather); 00176 tx_block=true; 00177 } 00178 } 00179 00180 Bits CNullModem::readChar() { 00181 Bits rxchar = clientsocket->GetcharNonBlock(); 00182 if (telnet && rxchar>=0) return TelnetEmulation((Bit8u)rxchar); 00183 else if (rxchar==0xff && !transparent) {// escape char 00184 // get the next char 00185 Bits rxchar = clientsocket->GetcharNonBlock(); 00186 if (rxchar==0xff) return rxchar; // 0xff 0xff -> 0xff was meant 00187 rxchar&0x1? setCTS(true) : setCTS(false); 00188 rxchar&0x2? setDSR(true) : setDSR(false); 00189 if (rxchar&0x4) receiveByteEx(0x0,0x10); 00190 return -1; // no "payload" received 00191 } else return rxchar; 00192 } 00193 00194 bool CNullModem::ClientConnect(TCPClientSocket* newsocket) { 00195 Bit8u peernamebuf[16]; 00196 clientsocket = newsocket; 00197 00198 if (!clientsocket->isopen) { 00199 LOG_MSG("Serial%d: Connection failed.",(int)COMNUMBER); 00200 delete clientsocket; 00201 clientsocket=0; 00202 setCD(false); 00203 return false; 00204 } 00205 clientsocket->SetSendBufferSize(256); 00206 clientsocket->GetRemoteAddressString(peernamebuf); 00207 // transmit the line status 00208 if (!transparent) setRTSDTR(getRTS(), getDTR()); 00209 rx_state=N_RX_IDLE; 00210 LOG_MSG("Serial%d: Connected to %s",(int)COMNUMBER,peernamebuf); 00211 setEvent(SERIAL_POLLING_EVENT, 1); 00212 setCD(true); 00213 return true; 00214 } 00215 00216 bool CNullModem::ServerListen() { 00217 // Start the server listen port. 00218 serversocket = new TCPServerSocket(serverport); 00219 if (!serversocket->isopen) return false; 00220 LOG_MSG("Serial%d: Nullmodem server waiting for connection on port %d...", 00221 (int)COMNUMBER,serverport); 00222 setEvent(SERIAL_SERVER_POLLING_EVENT, 50); 00223 setCD(false); 00224 return true; 00225 } 00226 00227 bool CNullModem::ServerConnect() { 00228 // check if a connection is available. 00229 clientsocket=serversocket->Accept(); 00230 if (!clientsocket) return false; 00231 00232 Bit8u peeripbuf[16]; 00233 clientsocket->GetRemoteAddressString(peeripbuf); 00234 LOG_MSG("Serial%d: A client (%s) has connected.",(int)COMNUMBER,peeripbuf); 00235 #if SERIAL_DEBUG 00236 log_ser(dbg_aux,"Nullmodem: A client (%s) has connected.", peeripbuf); 00237 #endif 00238 00239 /* FIXME: It would be nice if the SDL net library had a bind() call to bind only to a specific interface. 00240 * Or maybe it does... what am I missing? */ 00241 if (!nonlocal && strcmp((char*)peeripbuf,"127.0.0.1") != 0) { 00242 LOG_MSG("Serial%d: Non-localhost client (%s) dropped by nonlocal:0 policy. To accept connections from network, set nonlocal:1",(int)COMNUMBER,peeripbuf); 00243 delete clientsocket; 00244 clientsocket = NULL; 00245 return false; 00246 } 00247 00248 clientsocket->SetSendBufferSize(256); 00249 rx_state=N_RX_IDLE; 00250 setEvent(SERIAL_POLLING_EVENT, 1); 00251 00252 // we don't accept further connections 00253 delete serversocket; 00254 serversocket=0; 00255 00256 // transmit the line status 00257 setRTSDTR(getRTS(), getDTR()); 00258 if (transparent) setCD(true); 00259 return true; 00260 } 00261 00262 void CNullModem::Disconnect() { 00263 removeEvent(SERIAL_POLLING_EVENT); 00264 removeEvent(SERIAL_RX_EVENT); 00265 // it was disconnected; free the socket and restart the server socket 00266 LOG_MSG("Serial%d: Disconnected.",(int)COMNUMBER); 00267 delete clientsocket; 00268 clientsocket=0; 00269 setDSR(false); 00270 setCTS(false); 00271 setCD(false); 00272 00273 if (serverport) { 00274 serversocket = new TCPServerSocket(serverport); 00275 if (serversocket->isopen) 00276 setEvent(SERIAL_SERVER_POLLING_EVENT, 50); 00277 else delete serversocket; 00278 } else if (dtrrespect) { 00279 setEvent(SERIAL_NULLMODEM_DTR_EVENT,50); 00280 DTR_delta = getDTR(); // try to reconnect the next time DTR is set 00281 } 00282 } 00283 00284 void CNullModem::handleUpperEvent(Bit16u type) { 00285 00286 switch(type) { 00287 case SERIAL_POLLING_EVENT: { 00288 // periodically check if new data arrived, disconnect 00289 // if required. Add it back. 00290 setEvent(SERIAL_POLLING_EVENT, 1.0f); 00291 // update Modem input line states 00292 updateMSR(); 00293 switch(rx_state) { 00294 case N_RX_IDLE: 00295 if (CanReceiveByte()) { 00296 if (doReceive()) { 00297 // a byte was received 00298 rx_state=N_RX_WAIT; 00299 setEvent(SERIAL_RX_EVENT, bytetime*0.9f); 00300 } // else still idle 00301 } else { 00302 #if SERIAL_DEBUG 00303 log_ser(dbg_aux,"Nullmodem: block on polling."); 00304 #endif 00305 rx_state=N_RX_BLOCKED; 00306 // have both delays (1ms + bytetime) 00307 setEvent(SERIAL_RX_EVENT, bytetime*0.9f); 00308 } 00309 break; 00310 case N_RX_BLOCKED: 00311 // one timeout tick 00312 if (!CanReceiveByte()) { 00313 rx_retry++; 00314 if (rx_retry>=rx_retry_max) { 00315 // it has timed out: 00316 rx_retry=0; 00317 removeEvent(SERIAL_RX_EVENT); 00318 if (doReceive()) { 00319 // read away everything 00320 while(doReceive()); 00321 rx_state=N_RX_WAIT; 00322 setEvent(SERIAL_RX_EVENT, bytetime*0.9f); 00323 } else { 00324 // much trouble about nothing 00325 rx_state=N_RX_IDLE; 00326 #if SERIAL_DEBUG 00327 log_ser(dbg_aux,"Nullmodem: unblock due to no more data",rx_retry); 00328 #endif 00329 } 00330 } // else wait further 00331 } else { 00332 // good: we can receive again 00333 removeEvent(SERIAL_RX_EVENT); 00334 rx_retry=0; 00335 if (doReceive()) { 00336 rx_state=N_RX_FASTWAIT; 00337 setEvent(SERIAL_RX_EVENT, bytetime*0.65f); 00338 } else { 00339 // much trouble about nothing 00340 rx_state=N_RX_IDLE; 00341 } 00342 } 00343 break; 00344 00345 case N_RX_WAIT: 00346 case N_RX_FASTWAIT: 00347 break; 00348 } 00349 break; 00350 } 00351 case SERIAL_RX_EVENT: { 00352 switch(rx_state) { 00353 case N_RX_IDLE: 00354 LOG_MSG("internal error in nullmodem"); 00355 break; 00356 00357 case N_RX_BLOCKED: // try to receive 00358 case N_RX_WAIT: 00359 case N_RX_FASTWAIT: 00360 if (CanReceiveByte()) { 00361 // just works or unblocked 00362 if (doReceive()) { 00363 rx_retry=0; // not waiting anymore 00364 if (rx_state==N_RX_WAIT) setEvent(SERIAL_RX_EVENT, bytetime*0.9f); 00365 else { 00366 // maybe unblocked 00367 rx_state=N_RX_FASTWAIT; 00368 setEvent(SERIAL_RX_EVENT, bytetime*0.65f); 00369 } 00370 } else { 00371 // didn't receive anything 00372 rx_retry=0; 00373 rx_state=N_RX_IDLE; 00374 } 00375 } else { 00376 // blocking now or still blocked 00377 #if SERIAL_DEBUG 00378 if (rx_state==N_RX_BLOCKED) 00379 log_ser(dbg_aux,"Nullmodem: rx still blocked (retry=%d)",rx_retry); 00380 else log_ser(dbg_aux,"Nullmodem: block on continued rx (retry=%d).",rx_retry); 00381 #endif 00382 setEvent(SERIAL_RX_EVENT, bytetime*0.65f); 00383 rx_state=N_RX_BLOCKED; 00384 } 00385 00386 break; 00387 } 00388 break; 00389 } 00390 case SERIAL_TX_EVENT: { 00391 // Maybe echo cirquit works a bit better this way 00392 if (rx_state==N_RX_IDLE && CanReceiveByte() && clientsocket) { 00393 if (doReceive()) { 00394 // a byte was received 00395 rx_state=N_RX_WAIT; 00396 setEvent(SERIAL_RX_EVENT, bytetime*0.9f); 00397 } 00398 } 00399 ByteTransmitted(); 00400 break; 00401 } 00402 case SERIAL_THR_EVENT: { 00403 ByteTransmitting(); 00404 // actually send it 00405 setEvent(SERIAL_TX_EVENT,bytetime+0.01f); 00406 break; 00407 } 00408 case SERIAL_SERVER_POLLING_EVENT: { 00409 // As long as nothing is connected to our server poll the 00410 // connection. 00411 if (!ServerConnect()) { 00412 // continue looking 00413 setEvent(SERIAL_SERVER_POLLING_EVENT, 50); 00414 } 00415 break; 00416 } 00417 case SERIAL_TX_REDUCTION: { 00418 // Flush the data in the transmitting buffer. 00419 if (clientsocket) clientsocket->FlushBuffer(); 00420 tx_block=false; 00421 break; 00422 } 00423 case SERIAL_NULLMODEM_DTR_EVENT: { 00424 if ((!DTR_delta) && getDTR()) { 00425 // DTR went positive. Try to connect. 00426 if (ClientConnect(new TCPClientSocket((char*)hostnamebuffer, 00427 (Bit16u)clientport))) 00428 break; // no more DTR wait event when connected 00429 } 00430 DTR_delta = getDTR(); 00431 setEvent(SERIAL_NULLMODEM_DTR_EVENT,50); 00432 break; 00433 } 00434 } 00435 } 00436 00437 /*****************************************************************************/ 00438 /* updatePortConfig is called when emulated app changes the serial port **/ 00439 /* parameters baudrate, stopbits, number of databits, parity. **/ 00440 /*****************************************************************************/ 00441 void CNullModem::updatePortConfig (Bit16u /*divider*/, Bit8u /*lcr*/) { 00442 00443 } 00444 00445 void CNullModem::updateMSR () { 00446 00447 } 00448 00449 bool CNullModem::doReceive () { 00450 Bits rxchar = readChar(); 00451 if (rxchar>=0) { 00452 receiveByteEx((Bit8u)rxchar,0); 00453 return true; 00454 } 00455 else if (rxchar==-2) { 00456 Disconnect(); 00457 } 00458 return false; 00459 } 00460 00461 void CNullModem::transmitByte (Bit8u val, bool first) { 00462 // transmit it later in THR_Event 00463 if (first) setEvent(SERIAL_THR_EVENT, bytetime/8); 00464 else setEvent(SERIAL_TX_EVENT, bytetime); 00465 00466 // disable 0xff escaping when transparent mode is enabled 00467 if (!transparent && (val==0xff)) WriteChar(0xff); 00468 00469 WriteChar(val); 00470 } 00471 00472 Bits CNullModem::TelnetEmulation(Bit8u data) { 00473 Bit8u response[3]; 00474 if (telClient.inIAC) { 00475 if (telClient.recCommand) { 00476 if ((data != 0) && (data != 1) && (data != 3)) { 00477 LOG_MSG("Serial%d: Unrecognized telnet option %d",(int)COMNUMBER, data); 00478 if (telClient.command>250) { 00479 /* Reject anything we don't recognize */ 00480 response[0]=0xff; 00481 response[1]=252; 00482 response[2]=data; /* We won't do crap! */ 00483 if (clientsocket) clientsocket->SendArray(response, 3); 00484 } 00485 } 00486 switch(telClient.command) { 00487 case 251: /* Will */ 00488 if (data == 0) telClient.binary[TEL_SERVER] = true; 00489 if (data == 1) telClient.echo[TEL_SERVER] = true; 00490 if (data == 3) telClient.supressGA[TEL_SERVER] = true; 00491 break; 00492 case 252: /* Won't */ 00493 if (data == 0) telClient.binary[TEL_SERVER] = false; 00494 if (data == 1) telClient.echo[TEL_SERVER] = false; 00495 if (data == 3) telClient.supressGA[TEL_SERVER] = false; 00496 break; 00497 case 253: /* Do */ 00498 if (data == 0) { 00499 telClient.binary[TEL_CLIENT] = true; 00500 response[0]=0xff; 00501 response[1]=251; 00502 response[2]=0; /* Will do binary transfer */ 00503 if (clientsocket) clientsocket->SendArray(response, 3); 00504 } 00505 if (data == 1) { 00506 telClient.echo[TEL_CLIENT] = false; 00507 response[0]=0xff; 00508 response[1]=252; 00509 response[2]=1; /* Won't echo (too lazy) */ 00510 if (clientsocket) clientsocket->SendArray(response, 3); 00511 } 00512 if (data == 3) { 00513 telClient.supressGA[TEL_CLIENT] = true; 00514 response[0]=0xff; 00515 response[1]=251; 00516 response[2]=3; /* Will Suppress GA */ 00517 if (clientsocket) clientsocket->SendArray(response, 3); 00518 } 00519 break; 00520 case 254: /* Don't */ 00521 if (data == 0) { 00522 telClient.binary[TEL_CLIENT] = false; 00523 response[0]=0xff; 00524 response[1]=252; 00525 response[2]=0; /* Won't do binary transfer */ 00526 if (clientsocket) clientsocket->SendArray(response, 3); 00527 } 00528 if (data == 1) { 00529 telClient.echo[TEL_CLIENT] = false; 00530 response[0]=0xff; 00531 response[1]=252; 00532 response[2]=1; /* Won't echo (fine by me) */ 00533 if (clientsocket) clientsocket->SendArray(response, 3); 00534 } 00535 if (data == 3) { 00536 telClient.supressGA[TEL_CLIENT] = true; 00537 response[0]=0xff; 00538 response[1]=251; 00539 response[2]=3; /* Will Suppress GA (too lazy) */ 00540 if (clientsocket) clientsocket->SendArray(response, 3); 00541 } 00542 break; 00543 default: 00544 LOG_MSG("MODEM: Telnet client sent IAC %d", telClient.command); 00545 break; 00546 } 00547 telClient.inIAC = false; 00548 telClient.recCommand = false; 00549 return -1; //continue; 00550 } else { 00551 if (data==249) { 00552 /* Go Ahead received */ 00553 telClient.inIAC = false; 00554 return -1; //continue; 00555 } 00556 telClient.command = data; 00557 telClient.recCommand = true; 00558 00559 if ((telClient.binary[TEL_SERVER]) && (data == 0xff)) { 00560 /* Binary data with value of 255 */ 00561 telClient.inIAC = false; 00562 telClient.recCommand = false; 00563 return 0xff; 00564 } 00565 } 00566 } else { 00567 if (data == 0xff) { 00568 telClient.inIAC = true; 00569 return -1; 00570 } 00571 return data; 00572 } 00573 return -1; // ??? 00574 } 00575 00576 00577 /*****************************************************************************/ 00578 /* setBreak(val) switches break on or off **/ 00579 /*****************************************************************************/ 00580 00581 void CNullModem::setBreak (bool /*value*/) { 00582 CNullModem::setRTSDTR(getRTS(), getDTR()); 00583 } 00584 00585 /*****************************************************************************/ 00586 /* updateModemControlLines(mcr) sets DTR and RTS. **/ 00587 /*****************************************************************************/ 00588 void CNullModem::setRTSDTR(bool xrts, bool xdtr) { 00589 if (!transparent) { 00590 Bit8u control[2]; 00591 control[0]=0xff; 00592 control[1]=0x0; 00593 if (xrts) control[1]|=1; 00594 if (xdtr) control[1]|=2; 00595 if (LCR&LCR_BREAK_MASK) control[1]|=4; 00596 if (clientsocket) clientsocket->SendArray(control, 2); 00597 } 00598 } 00599 void CNullModem::setRTS(bool val) { 00600 setRTSDTR(val, getDTR()); 00601 } 00602 void CNullModem::setDTR(bool val) { 00603 setRTSDTR(getRTS(), val); 00604 } 00605 #endif