/* * Copyright (C) 2002-2007 The DOSBox Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* $Id: softmodem.cpp,v 1.8 2007/07/19 18:58:39 c2woody Exp $ */ #include "dosbox.h" #if C_MODEM #include #include #include #include "support.h" #include "serialport.h" #include "softmodem.h" #include "misc_util.h" //#include "mixer.h" CSerialModem::CSerialModem(Bitu id, CommandLine* cmd):CSerial(id, cmd) { InstallationSuccessful=false; connected=false; rqueue=new CFifo(MODEM_BUFFER_QUEUE_SIZE); tqueue=new CFifo(MODEM_BUFFER_QUEUE_SIZE); // Default to direct null modem connection. Telnet mode interprets IAC codes telnetmode = false; // Initialize the sockets and setup the listening port listenport = 23; waitingclientsocket=0; clientsocket = 0; serversocket = 0; getBituSubstring("listenport:", &listenport, cmd); // TODO: Fix dialtones if requested //mhd.chan=MIXER_AddChannel((MIXER_MixHandler)this->MODEM_CallBack,8000,"MODEM"); //MIXER_Enable(mhd.chan,false); //MIXER_SetMode(mhd.chan,MIXER_16MONO); CSerial::Init_Registers(); Reset(); // reset calls EnterIdleState setEvent(SERIAL_POLLING_EVENT,1); InstallationSuccessful=true; } CSerialModem::~CSerialModem() { if(serversocket) delete serversocket; if(clientsocket) delete clientsocket; if(waitingclientsocket) delete waitingclientsocket; delete rqueue; delete tqueue; // remove events for(Bitu i = SERIAL_BASE_EVENT_COUNT+1; i <= SERIAL_MODEM_EVENT_COUNT; i++) removeEvent(i); } void CSerialModem::handleUpperEvent(Bit16u type) { switch(type) { case SERIAL_RX_EVENT: { break; } case MODEM_TX_EVENT: { if(tqueue->left()) { tqueue->addb(waiting_tx_character); if(tqueue->left() < 2) { CSerial::setCTS(false); } } else { static Bits lcount=0; if (lcount<1000) { lcount++; LOG_MSG("MODEM: TX Buffer overflow!"); } } ByteTransmitted(); break; } case SERIAL_POLLING_EVENT: { Timer2(); setEvent(SERIAL_POLLING_EVENT,1); break; } case MODEM_RING_EVENT: { break; } } } void CSerialModem::SendLine(const char *line) { rqueue->addb(0xd); rqueue->addb(0xa); rqueue->adds((Bit8u *)line,strlen(line)); rqueue->addb(0xd); rqueue->addb(0xa); } // only for numbers < 1000... void CSerialModem::SendNumber(Bitu val) { rqueue->addb(0xd); rqueue->addb(0xa); rqueue->addb(val/100+'0'); val = val%100; rqueue->addb(val/10+'0'); val = val%10; rqueue->addb(val+'0'); rqueue->addb(0xd); rqueue->addb(0xa); } void CSerialModem::SendRes(ResTypes response) { char * string;Bitu code; switch (response) { case ResNONE: return; case ResOK: string="OK"; code=0; break; case ResERROR: string="ERROR"; code=4; break; case ResRING: string="RING"; code=2; break; case ResNODIALTONE: string="NO DIALTONE"; code=6; break; case ResNOCARRIER: string="NO CARRIER" ;code=3; break; case ResCONNECT: string="CONNECT 57600"; code=1; break; } if(doresponse!=1) { if(doresponse==2 && (response==ResRING || response == ResCONNECT || response==ResNOCARRIER)) return; if(numericresponse) SendNumber(code); else SendLine(string); //if(CSerial::CanReceiveByte()) // very fast response // if(rqueue->inuse() && CSerial::getRTS()) // { Bit8u rbyte =rqueue->getb(); // CSerial::receiveByte(rbyte); // LOG_MSG("Modem: sending byte %2x back to UART2",rbyte); // } LOG_MSG("Modem response: %s", string); } } bool CSerialModem::Dial(char * host) { // Scan host for port Bit16u port; char * hasport=strrchr(host,':'); if (hasport) { *hasport++=0; port=(Bit16u)atoi(hasport); } else port=MODEM_DEFAULT_PORT; // Resolve host we're gonna dial LOG_MSG("Connecting to host %s port %d",host,port); clientsocket = new TCPClientSocket(host, port); if(!clientsocket->isopen) { delete clientsocket; clientsocket=0; LOG_MSG("Failed to connect."); SendRes(ResNOCARRIER); EnterIdleState(); return false; } else { EnterConnectedState(); return true; } } void CSerialModem::AcceptIncomingCall(void) { if(waitingclientsocket) { clientsocket=waitingclientsocket; waitingclientsocket=0; EnterConnectedState(); } else { EnterIdleState(); } } Bitu CSerialModem::ScanNumber(char * & scan) { Bitu ret=0; while (char c=*scan) { if (c>='0' && c<='9') { ret*=10; ret+=c-'0'; scan++; } else break; } return ret; } void CSerialModem::Reset(){ EnterIdleState(); cmdpos = 0; cmdbuf[0]=0; oldDTRstate = getDTR(); flowcontrol = 0; plusinc = 0; if(clientsocket) { delete clientsocket; clientsocket=0; } memset(®,0,sizeof(reg)); reg[MREG_AUTOANSWER_COUNT]=0; // no autoanswer reg[MREG_RING_COUNT] = 1; reg[MREG_ESCAPE_CHAR]='+'; reg[MREG_CR_CHAR]='\r'; reg[MREG_LF_CHAR]='\n'; reg[MREG_BACKSPACE_CHAR]='\b'; cmdpause = 0; echo = true; doresponse = 0; numericresponse = false; /* Default to direct null modem connection. Telnet mode interprets IAC codes */ telnetmode = false; } void CSerialModem::EnterIdleState(void){ connected=false; ringing=false; if(clientsocket) { delete clientsocket; clientsocket=0; } if(waitingclientsocket) { // clear current incoming socket delete waitingclientsocket; waitingclientsocket=0; } // get rid of everything if(serversocket) { while(waitingclientsocket=serversocket->Accept()) delete waitingclientsocket; } else if (listenport) { serversocket=new TCPServerSocket(listenport); if(!serversocket->isopen) { LOG_MSG("Serial%d: Modem could not open TCP port %d.",COMNUMBER,listenport); delete serversocket; serversocket=0; } else LOG_MSG("Serial%d: Modem listening on port %d...",COMNUMBER,listenport); } waitingclientsocket=0; commandmode = true; CSerial::setCD(false); CSerial::setRI(false); CSerial::setDSR(true); CSerial::setCTS(true); tqueue->clear(); } void CSerialModem::EnterConnectedState(void) { if(serversocket) { // we don't accept further calls delete serversocket; serversocket=0; } SendRes(ResCONNECT); commandmode = false; memset(&telClient, 0, sizeof(telClient)); connected = true; ringing = false; CSerial::setCD(true); CSerial::setRI(false); } void CSerialModem::DoCommand() { cmdbuf[cmdpos] = 0; cmdpos = 0; //Reset for next command upcase(cmdbuf); LOG_MSG("Command sent to modem: ->%s<-\n", cmdbuf); /* Check for empty line, stops dialing and autoanswer */ if (!cmdbuf[0]) { reg[0]=0; // autoanswer off return; // } //else { //MIXER_Enable(mhd.chan,false); // dialing = false; // SendRes(ResNOCARRIER); // goto ret_none; // } } /* AT command set interpretation */ if ((cmdbuf[0] != 'A') || (cmdbuf[1] != 'T')) { SendRes(ResERROR); return; } if (strstr(cmdbuf,"NET0")) { telnetmode = false; SendRes(ResOK); return; } else if (strstr(cmdbuf,"NET1")) { telnetmode = true; SendRes(ResOK); return; } /* Check for dial command */ if(strncmp(cmdbuf,"ATD3",3)==0) { char * foundstr=&cmdbuf[3]; if (*foundstr=='T' || *foundstr=='P') foundstr++; /* Small protection against empty line */ if (!foundstr[0]) { SendRes(ResERROR); return; } char* helper; // scan for and remove spaces; weird bug: with leading spaces in the string, // SDLNet_ResolveHost will return no error but not work anyway (win) while(foundstr[0]==' ') foundstr++; helper=foundstr; helper+=strlen(foundstr); while(helper[0]==' ') { helper[0]=0; helper--; } if (strlen(foundstr) >= 12) { // Check if supplied parameter only consists of digits bool isNum = true; for (Bitu i=0; i '9') isNum = false; if (isNum) { // Parameter is a number with at least 12 digits => this cannot be a valid IP/name // Transform by adding dots char buffer[128]; Bitu j = 0; for (Bitu i=0; i12) buffer[j++] = ':'; } buffer[j] = 0; foundstr = buffer; } } Dial(foundstr); return; } char * scanbuf; scanbuf=&cmdbuf[2]; char chr; Bitu num; while (chr=*scanbuf++) { switch (chr) { case 'I': //Some strings about firmware switch (num=ScanNumber(scanbuf)) { case 3:SendLine("DosBox Emulated Modem Firmware V1.00");break; case 4:SendLine("Modem compiled for DosBox version " VERSION);break; };break; case 'E': //Echo on/off switch (num=ScanNumber(scanbuf)) { case 0:echo = false;break; case 1:echo = true;break; };break; case 'V': switch (num=ScanNumber(scanbuf)) { case 0:numericresponse = true;break; case 1:numericresponse = false;break; };break; case 'H': //Hang up switch (num=ScanNumber(scanbuf)) { case 0: if (connected) { SendRes(ResNOCARRIER); EnterIdleState(); return; } //Else return ok };break; case 'O': //Return to data mode switch (num=ScanNumber(scanbuf)) { case 0: if (clientsocket) { commandmode = false; return; } else { SendRes(ResERROR); return; } };break; case 'T': //Tone Dial case 'P': //Pulse Dial break; case 'M': //Monitor case 'L': //Volume ScanNumber(scanbuf); break; case 'A': //Answer call if (waitingclientsocket) { AcceptIncomingCall(); } else { SendRes(ResERROR); return; } return; case 'Z': //Reset and load profiles { // scan the number away, if any ScanNumber(scanbuf); if (clientsocket/*socket*/) SendRes(ResNOCARRIER); Reset(); break; } case ' ': //Space just skip break; case 'Q': // Response options { // 0 = all on, 1 = all off, // 2 = no ring and no connect/carrier in answermode Bitu val = ScanNumber(scanbuf); if(!(val>2)) { doresponse=val; break; } else { SendRes(ResERROR); return; } } case 'S': //Registers { Bitu index=ScanNumber(scanbuf); if(index>=SREGS) { SendRes(ResERROR); return; //goto ret_none; } while(scanbuf[0]==' ') scanbuf++; // skip spaces if(scanbuf[0]=='=') { // set register scanbuf++; while(scanbuf[0]==' ') scanbuf++; // skip spaces Bitu val = ScanNumber(scanbuf); reg[index]=val; break; } else if(scanbuf[0]=='?') { // get register SendNumber(reg[index]); scanbuf++; break; } //else LOG_MSG("print reg %d with %d",index,reg[index]); } break; case '&': { if(scanbuf[0]!=0) { char ch = scanbuf[0]; scanbuf++; switch(ch) { case 'K': { Bitu val = ScanNumber(scanbuf); if(val<5) flowcontrol=val; else { SendRes(ResERROR); return; } break; } default: { scanbuf++; LOG_MSG("Modem: Unhandled command: &%c%d",ch,ScanNumber(scanbuf)); break; } } } else { SendRes(ResERROR); return; } } break; default: LOG_MSG("Modem: Unhandled command: %c%d",chr,ScanNumber(scanbuf)); } } SendRes(ResOK); return; } void CSerialModem::TelnetEmulation(Bit8u * data, Bitu size) { Bitu i; Bit8u c; for(i=0;i250) { /* Reject anything we don't recognize */ tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(c); /* We won't do crap! */ } } switch(telClient.command) { case 251: /* Will */ if(c == 0) telClient.binary[TEL_SERVER] = true; if(c == 1) telClient.echo[TEL_SERVER] = true; if(c == 3) telClient.supressGA[TEL_SERVER] = true; break; case 252: /* Won't */ if(c == 0) telClient.binary[TEL_SERVER] = false; if(c == 1) telClient.echo[TEL_SERVER] = false; if(c == 3) telClient.supressGA[TEL_SERVER] = false; break; case 253: /* Do */ if(c == 0) { telClient.binary[TEL_CLIENT] = true; tqueue->addb(0xff); tqueue->addb(251); tqueue->addb(0); /* Will do binary transfer */ } if(c == 1) { telClient.echo[TEL_CLIENT] = false; tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(1); /* Won't echo (too lazy) */ } if(c == 3) { telClient.supressGA[TEL_CLIENT] = true; tqueue->addb(0xff); tqueue->addb(251); tqueue->addb(3); /* Will Suppress GA */ } break; case 254: /* Don't */ if(c == 0) { telClient.binary[TEL_CLIENT] = false; tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(0); /* Won't do binary transfer */ } if(c == 1) { telClient.echo[TEL_CLIENT] = false; tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(1); /* Won't echo (fine by me) */ } if(c == 3) { telClient.supressGA[TEL_CLIENT] = true; tqueue->addb(0xff); tqueue->addb(251); tqueue->addb(3); /* Will Suppress GA (too lazy) */ } break; default: LOG_MSG("MODEM: Telnet client sent IAC %d", telClient.command); break; } telClient.inIAC = false; telClient.recCommand = false; continue; } else { if(c==249) { /* Go Ahead received */ telClient.inIAC = false; continue; } telClient.command = c; telClient.recCommand = true; if((telClient.binary[TEL_SERVER]) && (c == 0xff)) { /* Binary data with value of 255 */ telClient.inIAC = false; telClient.recCommand = false; rqueue->addb(0xff); continue; } } } else { if(c == 0xff) { telClient.inIAC = true; continue; } rqueue->addb(c); } } } void CSerialModem::Timer2(void) { unsigned long args = 1; bool sendbyte = true; Bitu usesize; Bit8u txval; Bitu txbuffersize =0; // check for bytes to be sent to port if(CSerial::CanReceiveByte()) if(rqueue->inuse() && (CSerial::getRTS()||(flowcontrol!=3))) { Bit8u rbyte = rqueue->getb(); //LOG_MSG("Modem: sending byte %2x back to UART3",rbyte); CSerial::receiveByte(rbyte); } // Check for eventual break command if (!commandmode) cmdpause++; // Handle incoming data from serial port, read as much as available CSerial::setCTS(true); // buffer will get 'emptier', new data can be received while (tqueue->inuse()) { txval = tqueue->getb(); if (commandmode) { if (echo) { rqueue->addb(txval); //LOG_MSG("Echo back to queue: %x",txval); } if (txval==0xa) continue; //Real modem doesn't seem to skip this? else if (txval==0x8 && (cmdpos > 0)) --cmdpos; // backspace else if (txval==0xd) DoCommand(); // return else if (txval != '+') { if(cmdpos<99) { cmdbuf[cmdpos] = txval; cmdpos++; } } } else {// + character // 1000 ticks have passed, can check for pause command if (cmdpause > 1000) { if(txval ==reg[MREG_ESCAPE_CHAR]) // + { plusinc++; if(plusinc>=3) { LOG_MSG("Modem: Entering command mode(escape sequence)"); commandmode = true; SendRes(ResOK); plusinc = 0; } sendbyte=false; } else { plusinc=0; } // If not a special pause command, should go for bigger blocks to send } tmpbuf[txbuffersize] = txval; txbuffersize++; } } // while loop if (clientsocket && sendbyte && txbuffersize) { // down here it saves a lot of network traffic clientsocket->SendArray(tmpbuf,txbuffersize); //TODO error testing } // Handle incoming to the serial port if(!commandmode && clientsocket && rqueue->left()) { usesize = rqueue->left(); if (usesize>16) usesize=16; if(!clientsocket->ReceiveArray(tmpbuf, &usesize)) { SendRes(ResNOCARRIER); EnterIdleState(); } else if(usesize) { // LOG_MSG("rcv:%d", result); // Filter telnet commands if(telnetmode) TelnetEmulation(tmpbuf, usesize); else rqueue->adds(tmpbuf,usesize); cmdpause = 0; } } // Check for incoming calls if (!connected && !waitingclientsocket && serversocket) { waitingclientsocket=serversocket->Accept(); if(waitingclientsocket) { if(!CSerial::getDTR()) { // accept no calls with DTR off; TODO: AT &Dn EnterIdleState(); } else { ringing=true; SendRes(ResRING); CSerial::setRI(!CSerial::getRI()); //MIXER_Enable(mhd.chan,true); ringtimer = 3000; reg[1] = 0; //Reset ring counter reg } } } if (ringing) { if (ringtimer <= 0) { reg[1]++; if ((reg[0]>0) && (reg[0]>=reg[1])) { AcceptIncomingCall(); return; } SendRes(ResRING); CSerial::setRI(!CSerial::getRI()); //MIXER_Enable(mhd.chan,true); ringtimer = 3000; } --ringtimer; } } //TODO void CSerialModem::RXBufferEmpty() { // see if rqueue has some more bytes if(rqueue->inuse() && (CSerial::getRTS()||(flowcontrol!=3))){ Bit8u rbyte = rqueue->getb(); //LOG_MSG("Modem: sending byte %2x back to UART1",rbyte); CSerial::receiveByte(rbyte); } } void CSerialModem::transmitByte(Bit8u val, bool first) { waiting_tx_character=val; setEvent(MODEM_TX_EVENT, bytetime); // TX event if(first) ByteTransmitting(); //LOG_MSG("MODEM: Byte %x to be transmitted",val); } void CSerialModem::updatePortConfig(Bit16u, Bit8u lcr) { // nothing to do here right? } void CSerialModem::updateMSR() { // think it is not needed } void CSerialModem::setBreak(bool) { // TODO: handle this } void CSerialModem::setRTSDTR(bool rts, bool dtr) { setDTR(dtr); } void CSerialModem::setRTS(bool val) { } void CSerialModem::setDTR(bool val) { if(!val && connected) { // If DTR goes low, hang up. SendRes(ResNOCARRIER); EnterIdleState(); LOG_MSG("Modem: Hang up due to dropped DTR."); } } /* void CSerialModem::updateModemControlLines() { //bool txrdy=tqueue->left(); //if(CSerial::getRTS() && txrdy) CSerial::setCTS(true); //else CSerial::setCTS(tqueue->left()); // If DTR goes low, hang up. if(connected) if(oldDTRstate) if(!getDTR()) { SendRes(ResNOCARRIER); EnterIdleState(); LOG_MSG("Modem: Hang up due to dropped DTR."); } oldDTRstate = getDTR(); } */ #endif