Logo Search packages:      
Sourcecode: dosbox version File versions  Download package

softmodem.cpp

/*
 *  Copyright (C) 2002-2004  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.
 */

#include "dosbox.h"

#if C_MODEM

#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "SDL_net.h"

#include "inout.h"
#include "mixer.h"
#include "pic.h"
#include "setup.h"
#include "programs.h"
#include "debug.h"
#include "timer.h"
#include "callback.h"
#include "math.h"
#include "regs.h"
#include "serialport.h"

#define MODEMSPD 57600
#define SREGS 100


static Bit8u tmpbuf[QUEUE_SIZE];

struct ModemHd {
      char cmdbuf[QUEUE_SIZE];
      bool commandmode;
      bool answermode;
      bool echo,response,numericresponse;
      bool telnetmode;
      Bitu cmdpause;
      Bits ringtimer;
      Bits ringcount;
      Bitu plusinc;
      Bitu cmdpos;

      Bit8u reg[SREGS];
      TCPsocket incomingsocket;
      TCPsocket socket; 
      TCPsocket listensocket;
      SDLNet_SocketSet socketset;

      IPaddress openip;

      Bitu comport;
      Bitu listenport;

      char remotestr[4096];

      bool dialing;
      double f1, f2;
      Bitu diallen;
      Bitu dialpos;
      char dialstr[256];
      // TODO: Re-enable dialtons
      //MIXER_Channel * chan;
};

enum ResTypes {
      ResNONE,
      ResOK,ResERROR,
      ResCONNECT,ResRING,
      ResBUSY,ResNODIALTONE,ResNOCARRIER,
};

#define TEL_CLIENT 0
#define TEL_SERVER 1

struct telnetClient {
      bool binary[2];
      bool echo[2];
      bool supressGA[2];
      bool timingMark[2];

      bool inIAC;
      bool recCommand;
      Bit8u command;
};


#if 1

static void toUpcase(char *buffer) {
      Bitu i=0;
      while (buffer[i] != 0) {
            buffer[i] = toupper(buffer[i]);
            i++;
      }
}



class CSerialModem : public CSerial {
public:
      ModemHd mhd;

      CSerialModem(Bit16u baseAddr, Bit8u initIrq, Bit32u initBps, const char *remotestr = NULL, Bit16u lport = 27)
            : CSerial(baseAddr, initIrq, initBps)
      {
            
            
            mhd.cmdpos = 0;
            mhd.commandmode = true;
            mhd.plusinc = 0;
            mhd.incomingsocket = 0;
            mhd.answermode = false;
            memset(&mhd.reg,0,sizeof(mhd.reg));
            mhd.cmdpause = 0;
            mhd.echo = true;
            mhd.response = true;
            mhd.numericresponse = false;

            /* Default to direct null modem connection.  Telnet mode interprets IAC codes */
            mhd.telnetmode = false;

            /* Bind the modem to the correct serial port */
            //strcpy(mhd.remotestr, remotestr);

            /* Initialize the sockets and setup the listening port */
            mhd.socketset = SDLNet_AllocSocketSet(1);
            if (!mhd.socketset) {
                  LOG_MSG("MODEM:Can't open socketset:%s",SDLNet_GetError());
      //TODO Should probably just exit
                  return;
            }
            mhd.socket=0;
            mhd.listenport=lport;
            if (mhd.listenport) {
                  IPaddress listen_ip;
                  SDLNet_ResolveHost(&listen_ip, NULL, mhd.listenport);
                  mhd.listensocket=SDLNet_TCP_Open(&listen_ip);
                  if (!mhd.listensocket) LOG_MSG("MODEM:Can't open listen port:%s",SDLNet_GetError());
            } else mhd.listensocket=0;

            // 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);

      }



      void SendLine(const char *line) {
            rqueue->addb(0xd);
            rqueue->addb(0xa);
            rqueue->adds((Bit8u *)line,strlen(line));
            rqueue->addb(0xd);
            rqueue->addb(0xa);

            }

      void SendRes(ResTypes response) {
            char * string;char * 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";
            case ResNODIALTONE:string="NO DIALTONE";code="6";break;
            case ResNOCARRIER:string="NO CARRIER";code="3";break;
            case ResCONNECT:string="CONNECT 57600";code="1";break;
      }

            rqueue->addb(0xd);rqueue->addb(0xa);
            rqueue->adds((Bit8u *)string,strlen(string));
            rqueue->addb(0xd);rqueue->addb(0xa);
      }

      void Send(Bit8u val) {
            tqueue->addb(val);
      }

      Bit8u Recv(Bit8u val) {
            return rqueue->getb();

      }

      void openConnection(void) {
      if (mhd.socket) {
            LOG_MSG("Huh? already connected");
            SDLNet_TCP_DelSocket(mhd.socketset,mhd.socket);
            SDLNet_TCP_Close(mhd.socket);
      }
            mhd.socket = SDLNet_TCP_Open(&openip);
      if (mhd.socket) {
            SDLNet_TCP_AddSocket(mhd.socketset,mhd.socket);
                  SendRes(ResCONNECT);
            mhd.commandmode = false;
                  memset(&telClient, 0, sizeof(telClient));
                  updatemstatus();
      } else {
                  SendRes(ResNODIALTONE);
            }
      }

      void updatemstatus(void) {
            Bit8u ms=0;
            //Check for data carrier, a connection that is
            if (mhd.incomingsocket) ms|=MS_RI;
            if (mhd.socket) ms|=MS_DCD;
            if (!mhd.commandmode) ms|=MS_DSR;
            //Check for DTR reply with DSR
      //    if (cport->mctrl & MC_DTR) ms|=MS_DSR;
            //Check for RTS reply with CTS
            if (mctrl & MC_RTS) ms|=MS_CTS;
            SetModemStatus(ms);
      }
      
      bool Dial(char * host) {
      /* Scan host for port */
      Bit16u port;
      char * hasport=strrchr(host,':');
      if (hasport) {
            *hasport++=0;
            port=(Bit16u)atoi(hasport);
      } else port=23;
      /* Resolve host we're gonna dial */
      LOG_MSG("host %s port %x",host,port);
            if (!SDLNet_ResolveHost(&openip,host,port)) {
                  openConnection();
            return true;
      } else {
            LOG_MSG("Failed to resolve host %s:%s",host,SDLNet_GetError());
                  SendRes(ResNOCARRIER);
            return false;
      }
            }

      void AcceptIncomingCall(void) {
            assert(!mhd.socket);
            mhd.socket=mhd.incomingsocket;
            SDLNet_TCP_AddSocket(mhd.socketset,mhd.socket);
            SendRes(ResCONNECT);
            LOG_MSG("Connected!\n");

            mhd.incomingsocket = 0;
            mhd.commandmode = false;
            }

      Bitu 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 HangUp(void) {
            SendRes(ResNOCARRIER);
                        SDLNet_TCP_DelSocket(mhd.socketset,mhd.socket);
                        SDLNet_TCP_Close(mhd.socket);
                        mhd.socket=0;
                        mhd.commandmode = true;
            updatemstatus();
            }

      void DoCommand() {
            mhd.cmdbuf[mhd.cmdpos] = 0;
            mhd.cmdpos = 0;               //Reset for next command
            toUpcase(mhd.cmdbuf);
            LOG_MSG("Modem Sent Command: %s\n", mhd.cmdbuf);
            /* Check for empty line, stops dialing and autoanswer */
            if (!mhd.cmdbuf[0]) {
                  if(!mhd.dialing) {
                        mhd.answermode = false;
                        goto ret_none;
                  } else {
                        //MIXER_Enable(mhd.chan,false);
                        mhd.dialing = false;
                        SendRes(ResNOCARRIER);
                        goto ret_none;
                  }
            }
            /* AT command set interpretation */
            if ((mhd.cmdbuf[0] != 'A') || (mhd.cmdbuf[1] != 'T')) goto ret_error;
            /* Check for dial command */
            if(strncmp(mhd.cmdbuf,"ATD3",3)==0) {
                  char * foundstr=&mhd.cmdbuf[3];
                  if (*foundstr=='T' || *foundstr=='P') foundstr++;
                  /* Small protection against empty line */
                  if (!foundstr[0]) goto ret_error;
                  if (strlen(foundstr) >= 12){
                              // Check if supplied parameter only consists of digits
                              bool isNum = true;
                        for (Bitu i=0; i<strlen(foundstr); i++)
                                    if (foundstr[i] < '0' || foundstr[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; i<strlen(foundstr); i++) {
                                          buffer[j++] = foundstr[i];
                                          // Add a dot after the third, sixth and ninth number
                                          if (i == 2 || i == 5 || i == 8)
                                                buffer[j++] = '.';
                                          // If the string is longer than 12 digits, interpret the rest as port
                                          if (i == 11 && strlen(foundstr)>12)
                                                buffer[j++] = ':';
                                    }
                                    buffer[j] = 0;
                                    foundstr = buffer;
                              }
                        }
                  Dial(foundstr);
                  goto ret_none;
                  }
            char * scanbuf;
            scanbuf=&mhd.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:mhd.echo = false;break;
                        case 1:mhd.echo = true;break;
                        };break;
                  case 'V':
                        switch (num=ScanNumber(scanbuf)) {
                        case 0:mhd.numericresponse = true;break;
                        case 1:mhd.numericresponse = false;break;
                        };break;
                  case 'H':   //Hang up
                        switch (num=ScanNumber(scanbuf)) {
                        case 0:
                              if (mhd.socket) {
                                    HangUp();
                                    goto ret_none;
            }
                              //Else return ok
                        };break;
                  case 'O':   //Return to data mode
                        switch (num=ScanNumber(scanbuf)) {
                        case 0:
                              if (mhd.socket) {
                        mhd.commandmode = false;
                                    goto ret_none;
                  } else {
                                    goto ret_error;
                              }
                        };break;
                  case 'T':   //Tone Dial
                  case 'P':   //Pulse Dial
                        break;
                  case 'M':   //Monitor
                  case 'L':   //Volume
                        ScanNumber(scanbuf);
                        break;
                  case 'A':   //Answer call
                        if (mhd.incomingsocket) {
                              AcceptIncomingCall();
                        } else {
                              mhd.answermode = true;
                        }
                        goto ret_none;
                  case 'Z':   //Reset and load profiles
                        num=ScanNumber(scanbuf);
                        break;
                  case ' ':   //Space just skip
                        break;
                  case 'S':   //Registers 
                  {
                        Bitu index=ScanNumber(scanbuf);
                        bool hasequal=(*scanbuf == '=');
                        if (hasequal) scanbuf++;                  
                        Bitu val=ScanNumber(scanbuf);
                        if (index>=SREGS) goto ret_error;
                        if (hasequal) mhd.reg[index]=val;
                        else LOG_MSG("print reg %d with %d",index,mhd.reg[index]);
                  };break;
                  default:
                        LOG_MSG("Unhandled cmd %c%d",chr,ScanNumber(scanbuf));
                        }
                  }
      #if 0
                  if (strstr(mhd.cmdbuf,"S0=1")) {
                        mhd.autoanswer = true;
                  }
                  if (strstr(mhd.cmdbuf,"S0=0")) {
                        mhd.autoanswer = false;
            }

                  if (strstr(mhd.cmdbuf,"NET0")) {
                        mhd.telnetmode = false;
            }
                  if (strstr(mhd.cmdbuf,"NET1")) {
                        mhd.telnetmode = true;
      }
      #endif
            LOG_MSG("Sending OK");
            SendRes(ResOK);
            return;
      ret_error:
            LOG_MSG("Sending ERROR");
            SendRes(ResERROR);
      ret_none:
            return;

      }
      
      void MC_Changed(Bitu new_mc) {
            LOG_MSG("New MC %x",new_mc );
            if (!(new_mc & 1) && mhd.socket) HangUp();
            updatemstatus();
      }

      void TelnetEmulation(Bit8u * data, Bitu size) {
            Bitu i;
      Bit8u c;
      for(i=0;i<size;i++) {
            c = data[i];
            if(telClient.inIAC) {
                  if(telClient.recCommand) {
                        if((c != 0) && (c != 1) && (c != 3)) {
                              LOG_MSG("MODEM: Unrecognized option %d", c);
                              if(telClient.command>250) {
                                    /* 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 Timer(void) {
      int result =0;
      unsigned long args = 1;
      bool sendbyte = true;
      Bitu usesize;
      Bit8u txval;

      /* Check for eventual break command */
      if (!mhd.commandmode) mhd.cmdpause++;
      /* Handle incoming data from serial port, read as much as available */
            Bitu tx_size=tqueue->inuse();
      while (tx_size--) {
                  txval = tqueue->getb();
            if (mhd.commandmode) {
                        if (mhd.echo) rqueue->addb(txval);
                        if (txval==0xa) continue;           //Real modem doesn't seem to skip this?
                        else if (txval==0x8 && (mhd.cmdpos > 0)) --mhd.cmdpos;
                        else if (txval==0xd) DoCommand();
                        else if (txval != '+') {
                              if(mhd.cmdpos<QUEUE_SIZE) {
                                          mhd.cmdbuf[mhd.cmdpos] = txval;
                                          mhd.cmdpos++;
                                    }
                              }
            } else {
                  /* 1000 ticks have passed, can check for pause command */
                  if (mhd.cmdpause > 1000) {
                        if(txval == '+') {
                              mhd.plusinc++;
                              if(mhd.plusinc>=3) {
                                    LOG_MSG("Entering command mode");
                                    mhd.commandmode = true;
                                          SendRes(ResOK);
                                    mhd.plusinc = 0;
                              }
                              sendbyte=false;
                        } else {
                              mhd.plusinc=0;
                        }
      //If not a special pause command, should go for bigger blocks to send 
                  }
                  
                  tmpbuf[0] = txval;
                  tmpbuf[1] = 0x0;

                  if (mhd.socket && sendbyte) {
                        SDLNet_TCP_Send(mhd.socket, tmpbuf,1);
                        //TODO error testing
                  }
            }
      } 

      SDLNet_CheckSockets(mhd.socketset,0);
            /* Handle incoming to the serial port */
            if(!mhd.commandmode && mhd.socket) {
                  if(rqueue->left() && SDLNet_SocketReady(mhd.socket) && (mctrl & MC_RTS)) {
                  usesize = rqueue->left();
                  if (usesize>16) usesize=16;
            result = SDLNet_TCP_Recv(mhd.socket, tmpbuf, usesize);
            if (result>0) {
                  if(mhd.telnetmode) {
                        /* Filter telnet commands */
                        TelnetEmulation(tmpbuf, result);
                  } else {
                              rqueue->adds(tmpbuf,result);
                  }
                  mhd.cmdpause = 0;
                  } else HangUp();
            }
      }
      /* Check for incoming calls */
            if (!mhd.socket && !mhd.incomingsocket && mhd.listensocket) {
                  mhd.incomingsocket = SDLNet_TCP_Accept(mhd.listensocket);
                  if (mhd.incomingsocket) {
                  mhd.diallen = 12000;
                  mhd.dialpos = 0;
                        SendRes(ResRING);
                        //MIXER_Enable(mhd.chan,true);
                        mhd.ringtimer = 3000;
                        mhd.reg[1] = 0;         //Reset ring counter reg
            }
      }
            if (mhd.incomingsocket) {
                  if (mhd.ringtimer <= 0) {
                        mhd.reg[1]++;
                        if (mhd.answermode || (mhd.reg[0]==mhd.reg[1])) {
                              AcceptIncomingCall();
                        return;
                  }
                        SendRes(ResRING);
                  mhd.diallen = 12000;
                  mhd.dialpos = 0;
                        //MIXER_Enable(mhd.chan,true);
                        mhd.ringtimer = 3000;
            }
                  --mhd.ringtimer;
            }
            updatemstatus();
      }

      bool CanSend(void) {
            return true;
            }

      bool CanRecv(void) {
            return true;
      }


protected:
      char cmdbuf[QUEUE_SIZE];
      bool commandmode;
      bool answermode;
      bool echo;
      bool telnetmode;
      Bitu cmdpause;
      Bits ringtimer;
      Bits ringcount;
      Bitu plusinc;
      Bitu cmdpos;
            
      Bit8u reg[SREGS];
      IPaddress openip;
      TCPsocket incomingsocket;
      TCPsocket socket;
      TCPsocket listensocket;
      SDLNet_SocketSet socketset;

      struct {
            bool binary[2];
            bool echo[2];
            bool supressGA[2];
            bool timingMark[2];
                              
            bool inIAC;
            bool recCommand;
            Bit8u command;
      } telClient;
      struct {
            bool active;
            double f1, f2;
            Bitu len,pos;
            char str[256];
      } dial;
};

#endif 



CSerialModem *csm;


void MODEM_Init(Section* sec) {

      unsigned long args = 1;
      Section_prop * section=static_cast<Section_prop *>(sec);

      if(!section->Get_bool("modem")) return;

      if(SDLNet_Init()==-1) {
            LOG_MSG("SDLNet_Init failed: %s\n", SDLNet_GetError());
            return;
      }

      if(!SDLNetInited) {
            if(SDLNet_Init()==-1) {
                  LOG_MSG("SDLNet_Init failed: %s\n", SDLNet_GetError());
                  return;
            }
            SDLNetInited = true;
      }

      Bit16u comport = section->Get_int("comport");

      csm = NULL;

      switch (comport) {
            case 1:
                  csm = new CSerialModem(0x3f0, 4, 57600, section->Get_string("remote"), section->Get_int("listenport"));
                  break;
            case 2:
                  csm = new CSerialModem(0x2f0, 3, 57600, section->Get_string("remote"), section->Get_int("listenport"));
                  break;
            case 3:
                  csm = new CSerialModem(0x3e0, 4, 57600, section->Get_string("remote"), section->Get_int("listenport"));
                  break;
            case 4:
                  csm = new CSerialModem(0x2e0, 3, 57600, section->Get_string("remote"), section->Get_int("listenport"));
                  break;
            default:
                  // Default to COM2
                  csm = new CSerialModem(0x2f0, 3, 57600, section->Get_string("remote"), section->Get_int("listenport"));
                  break;

      }

      if(csm != NULL) seriallist.push_back(csm);
}


#endif


Generated by  Doxygen 1.6.0   Back to index