1 /*
2 * Copyright (C) 2002-2010 The DOSBox Team
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 /* $Id: nullmodem.cpp,v 1.8 2009-09-25 23:40:47 h-a-l-9000 Exp $ */
20
21 #include "dosbox.h"
22
23 #if C_MODEM
24
25 #include "control.h"
26 #include "serialport.h"
27 #include "nullmodem.h"
28
CNullModem(Bitu id,CommandLine * cmd)29 CNullModem::CNullModem(Bitu id, CommandLine* cmd):CSerial (id, cmd) {
30 Bitu temptcpport=23;
31 memset(&telClient, 0, sizeof(telClient));
32 InstallationSuccessful = false;
33 serversocket = 0;
34 clientsocket = 0;
35 serverport = 0;
36 clientport = 0;
37
38 rx_retry = 0;
39 rx_retry_max = 20;
40 rx_state=N_RX_DISC;
41
42 tx_gather = 12;
43
44 dtrrespect=false;
45 tx_block=false;
46 receiveblock=false;
47 transparent=false;
48 telnet=false;
49
50 Bitu bool_temp=0;
51
52 // usedtr: The nullmodem will
53 // 1) when it is client connect to the server not immediately but
54 // as soon as a modem-aware application is started (DTR is switched on).
55 // 2) only receive data when DTR is on.
56 if(getBituSubstring("usedtr:", &bool_temp, cmd)) {
57 if(bool_temp==1) {
58 dtrrespect=true;
59 transparent=true;
60 }
61 }
62 // transparent: don't add additional handshake control.
63 if(getBituSubstring("transparent:", &bool_temp, cmd)) {
64 if(bool_temp==1) transparent=true;
65 else transparent=false;
66 }
67 // telnet: interpret telnet commands.
68 if(getBituSubstring("telnet:", &bool_temp, cmd)) {
69 if(bool_temp==1) {
70 transparent=true;
71 telnet=true;
72 }
73 }
74 // rxdelay: How many milliseconds to wait before causing an
75 // overflow when the application is unresponsive.
76 if(getBituSubstring("rxdelay:", &rx_retry_max, cmd)) {
77 if(!(rx_retry_max<=10000)) {
78 rx_retry_max=50;
79 }
80 }
81 // txdelay: How many milliseconds to wait before sending data.
82 // This reduces network overhead quite a lot.
83 if(getBituSubstring("txdelay:", &tx_gather, cmd)) {
84 if(!(tx_gather<=500)) {
85 tx_gather=12;
86 }
87 }
88 // port is for both server and client
89 if(getBituSubstring("port:", &temptcpport, cmd)) {
90 if(!(temptcpport>0&&temptcpport<65536)) {
91 temptcpport=23;
92 }
93 }
94 // socket inheritance
95 if(getBituSubstring("inhsocket:", &bool_temp, cmd)) {
96 #ifdef NATIVESOCKETS
97 if(Netwrapper_GetCapabilities()&NETWRAPPER_TCP_NATIVESOCKET) {
98 if(bool_temp==1) {
99 int sock;
100 if (control->cmdline->FindInt("-socket",sock,true)) {
101 dtrrespect=false;
102 transparent=true;
103 // custom connect
104 Bit8u peernamebuf[16];
105 LOG_MSG("inheritance port: %d",sock);
106 clientsocket = new TCPClientSocket(sock);
107 if(!clientsocket->isopen) {
108 LOG_MSG("Serial%d: Connection failed.",COMNUMBER);
109 #if SERIAL_DEBUG
110 log_ser(dbg_aux,"Nullmodem: Connection failed.");
111 #endif
112 delete clientsocket;
113 clientsocket=0;
114 return;
115 }
116 clientsocket->SetSendBufferSize(256);
117 clientsocket->GetRemoteAddressString(peernamebuf);
118 // transmit the line status
119 if(!transparent) setRTSDTR(getRTS(), getDTR());
120
121 LOG_MSG("Serial%d: Connected to %s",COMNUMBER,peernamebuf);
122 #if SERIAL_DEBUG
123 log_ser(dbg_aux,"Nullmodem: Connected to %s",peernamebuf);
124 #endif
125 setEvent(SERIAL_POLLING_EVENT, 1);
126
127 CSerial::Init_Registers ();
128 InstallationSuccessful = true;
129
130 setCTS(true);
131 setDSR(true);
132 setRI (false);
133 setCD (true);
134 return;
135 } else {
136 LOG_MSG("Serial%d: -socket start parameter missing.",COMNUMBER);
137 return;
138 }
139 }
140 } else {
141 LOG_MSG("Serial%d: socket inheritance not supported on this platform.",
142 COMNUMBER);
143 return;
144 }
145 #else
146 LOG_MSG("Serial%d: socket inheritance not available.", COMNUMBER);
147 #endif
148 }
149 std::string tmpstring;
150 if(cmd->FindStringBegin("server:",tmpstring,false)) {
151 // we are a client
152 const char* hostnamechar=tmpstring.c_str();
153 size_t hostlen=strlen(hostnamechar)+1;
154 if(hostlen>sizeof(hostnamebuffer)) {
155 hostlen=sizeof(hostnamebuffer);
156 hostnamebuffer[sizeof(hostnamebuffer)-1]=0;
157 }
158 memcpy(hostnamebuffer,hostnamechar,hostlen);
159 clientport=(Bit16u)temptcpport;
160 if(dtrrespect) {
161 // we connect as soon as DTR is switched on
162 setEvent(SERIAL_NULLMODEM_DTR_EVENT, 50);
163 LOG_MSG("Serial%d: Waiting for DTR...",COMNUMBER);
164 } else ClientConnect();
165 } else {
166 // we are a server
167 serverport = (Bit16u)temptcpport;
168 serversocket = new TCPServerSocket(serverport);
169 if(!serversocket->isopen) return;
170 LOG_MSG("Serial%d: Nullmodem server waiting for connection on port %d...",
171 COMNUMBER,serverport);
172 setEvent(SERIAL_SERVER_POLLING_EVENT, 50);
173 }
174
175 // ....
176
177 CSerial::Init_Registers ();
178 InstallationSuccessful = true;
179
180 setCTS(dtrrespect||transparent);
181 setDSR(dtrrespect||transparent);
182 setRI (false);
183 setCD (dtrrespect);
184 }
185
~CNullModem()186 CNullModem::~CNullModem () {
187 if(serversocket) delete serversocket;
188 if(clientsocket) delete clientsocket;
189 // remove events
190 for(Bit16u i = SERIAL_BASE_EVENT_COUNT+1;
191 i <= SERIAL_NULLMODEM_EVENT_COUNT; i++) {
192 removeEvent(i);
193 }
194 }
195
WriteChar(Bit8u data)196 void CNullModem::WriteChar(Bit8u data) {
197
198 if(clientsocket)clientsocket->SendByteBuffered(data);
199 if(!tx_block) {
200 //LOG_MSG("setevreduct");
201 setEvent(SERIAL_TX_REDUCTION, (float)tx_gather);
202 tx_block=true;
203 }
204 }
205
readChar()206 Bits CNullModem::readChar() {
207
208 Bits rxchar = clientsocket->GetcharNonBlock();
209 if(telnet && rxchar>=0) return TelnetEmulation((Bit8u)rxchar);
210 else if(rxchar==0xff && !transparent) {// escape char
211 // get the next char
212 Bits rxchar = clientsocket->GetcharNonBlock();
213 if(rxchar==0xff) return rxchar; // 0xff 0xff -> 0xff was meant
214 rxchar&0x1? setCTS(true) : setCTS(false);
215 rxchar&0x2? setDSR(true) : setDSR(false);
216 if(rxchar&0x4) receiveByteEx(0x0,0x10);
217 return -1; // no "payload" received
218 } else return rxchar;
219 }
220
ClientConnect()221 void CNullModem::ClientConnect(){
222 Bit8u peernamebuf[16];
223 clientsocket = new TCPClientSocket((char*)hostnamebuffer,
224 (Bit16u)clientport);
225 if(!clientsocket->isopen) {
226 LOG_MSG("Serial%d: Connection failed.",idnumber+1);
227 delete clientsocket;
228 clientsocket=0;
229 return;
230 }
231 clientsocket->SetSendBufferSize(256);
232 clientsocket->GetRemoteAddressString(peernamebuf);
233 // transmit the line status
234 if(!transparent) setRTSDTR(getRTS(), getDTR());
235 rx_state=N_RX_IDLE;
236 LOG_MSG("Serial%d: Connected to %s",idnumber+1,peernamebuf);
237 setEvent(SERIAL_POLLING_EVENT, 1);
238 }
239
Disconnect()240 void CNullModem::Disconnect() {
241 removeEvent(SERIAL_POLLING_EVENT);
242 removeEvent(SERIAL_RX_EVENT);
243 // it was disconnected; free the socket and restart the server socket
244 LOG_MSG("Serial%d: Disconnected.",COMNUMBER);
245 delete clientsocket;
246 clientsocket=0;
247 setDSR(false);
248 setCTS(false);
249 if(serverport) {
250 serversocket = new TCPServerSocket(serverport);
251 if(serversocket->isopen)
252 setEvent(SERIAL_SERVER_POLLING_EVENT, 50);
253 else delete serversocket;
254 }
255 }
256
handleUpperEvent(Bit16u type)257 void CNullModem::handleUpperEvent(Bit16u type) {
258
259 switch(type) {
260 case SERIAL_POLLING_EVENT: {
261 // periodically check if new data arrived, disconnect
262 // if required. Add it back.
263 setEvent(SERIAL_POLLING_EVENT, 1.0f);
264 // update Modem input line states
265 updateMSR();
266 switch(rx_state) {
267 case N_RX_IDLE:
268 if(CanReceiveByte()) {
269 if(doReceive()) {
270 // a byte was received
271 rx_state=N_RX_WAIT;
272 setEvent(SERIAL_RX_EVENT, bytetime*0.9f);
273 } // else still idle
274 } else {
275 #if SERIAL_DEBUG
276 log_ser(dbg_aux,"Nullmodem: block on polling.");
277 #endif
278 rx_state=N_RX_BLOCKED;
279 // have both delays (1ms + bytetime)
280 setEvent(SERIAL_RX_EVENT, bytetime*0.9f);
281 }
282 break;
283 case N_RX_BLOCKED:
284 // one timeout tick
285 if(!CanReceiveByte()) {
286 rx_retry++;
287 if(rx_retry>=rx_retry_max) {
288 // it has timed out:
289 rx_retry=0;
290 removeEvent(SERIAL_RX_EVENT);
291 if(doReceive()) {
292 // read away everything
293 while(doReceive());
294 rx_state=N_RX_WAIT;
295 setEvent(SERIAL_RX_EVENT, bytetime*0.9f);
296 } else {
297 // much trouble about nothing
298 rx_state=N_RX_IDLE;
299 #if SERIAL_DEBUG
300 log_ser(dbg_aux,"Nullmodem: unblock due to no more data",rx_retry);
301 #endif
302 }
303 } // else wait further
304 } else {
305 // good: we can receive again
306 removeEvent(SERIAL_RX_EVENT);
307 rx_retry=0;
308 if(doReceive()) {
309 rx_state=N_RX_FASTWAIT;
310 setEvent(SERIAL_RX_EVENT, bytetime*0.65f);
311 } else {
312 // much trouble about nothing
313 rx_state=N_RX_IDLE;
314 }
315 }
316 break;
317
318 case N_RX_WAIT:
319 case N_RX_FASTWAIT:
320 break;
321 }
322 break;
323 }
324 case SERIAL_RX_EVENT: {
325 switch(rx_state) {
326 case N_RX_IDLE:
327 LOG_MSG("internal error in nullmodem");
328 break;
329
330 case N_RX_BLOCKED: // try to receive
331 case N_RX_WAIT:
332 case N_RX_FASTWAIT:
333 if(CanReceiveByte()) {
334 // just works or unblocked
335 if(doReceive()) {
336 rx_retry=0; // not waiting anymore
337 if(rx_state==N_RX_WAIT) setEvent(SERIAL_RX_EVENT, bytetime*0.9f);
338 else {
339 // maybe unblocked
340 rx_state=N_RX_FASTWAIT;
341 setEvent(SERIAL_RX_EVENT, bytetime*0.65f);
342 }
343 } else {
344 // didn't receive anything
345 rx_retry=0;
346 rx_state=N_RX_IDLE;
347 }
348 } else {
349 // blocking now or still blocked
350 #if SERIAL_DEBUG
351 if(rx_state==N_RX_BLOCKED)
352 log_ser(dbg_aux,"Nullmodem: rx still blocked (retry=%d)",rx_retry);
353 else log_ser(dbg_aux,"Nullmodem: block on continued rx (retry=%d).",rx_retry);
354 #endif
355 setEvent(SERIAL_RX_EVENT, bytetime*0.65f);
356 rx_state=N_RX_BLOCKED;
357 }
358
359 break;
360 }
361 break;
362 }
363 case SERIAL_TX_EVENT: {
364 // Maybe echo cirquit works a bit better this way
365 if(rx_state==N_RX_IDLE && CanReceiveByte() && clientsocket) {
366 if(doReceive()) {
367 // a byte was received
368 rx_state=N_RX_WAIT;
369 setEvent(SERIAL_RX_EVENT, bytetime*0.9f);
370 }
371 }
372 ByteTransmitted();
373 break;
374 }
375 case SERIAL_THR_EVENT: {
376 ByteTransmitting();
377 // actually send it
378 setEvent(SERIAL_TX_EVENT,bytetime+0.01f);
379 break;
380 }
381 case SERIAL_SERVER_POLLING_EVENT: {
382 // As long as nothing is connected to our server poll the
383 // connection.
384 clientsocket=serversocket->Accept();
385 if(clientsocket) {
386 Bit8u peeripbuf[16];
387 clientsocket->GetRemoteAddressString(peeripbuf);
388 LOG_MSG("Serial%d: A client (%s) has connected.",COMNUMBER,peeripbuf);
389 #if SERIAL_DEBUG
390 log_ser(dbg_aux,"Nullmodem: A client (%s) has connected.", peeripbuf);
391 #endif// new socket found...
392 clientsocket->SetSendBufferSize(256);
393 rx_state=N_RX_IDLE;
394 setEvent(SERIAL_POLLING_EVENT, 1);
395
396 // we don't accept further connections
397 delete serversocket;
398 serversocket=0;
399
400 // transmit the line status
401 setRTSDTR(getRTS(), getDTR());
402 } else {
403 // continue looking
404 setEvent(SERIAL_SERVER_POLLING_EVENT, 50);
405 }
406 break;
407 }
408 case SERIAL_TX_REDUCTION: {
409 // Flush the data in the transmitting buffer.
410 if(clientsocket) clientsocket->FlushBuffer();
411 tx_block=false;
412 break;
413 }
414 case SERIAL_NULLMODEM_DTR_EVENT: {
415 if(getDTR()) ClientConnect();
416 else setEvent(SERIAL_NULLMODEM_DTR_EVENT,50);
417 break;
418 }
419 }
420 }
421
422 /*****************************************************************************/
423 /* updatePortConfig is called when emulated app changes the serial port **/
424 /* parameters baudrate, stopbits, number of databits, parity. **/
425 /*****************************************************************************/
updatePortConfig(Bit16u,Bit8u)426 void CNullModem::updatePortConfig (Bit16u /*divider*/, Bit8u /*lcr*/) {
427
428 }
429
updateMSR()430 void CNullModem::updateMSR () {
431
432 }
433
doReceive()434 bool CNullModem::doReceive () {
435 Bits rxchar = readChar();
436 if(rxchar>=0) {
437 receiveByteEx((Bit8u)rxchar,0);
438 return true;
439 }
440 else if(rxchar==-2) {
441 Disconnect();
442 }
443 return false;
444 }
445
transmitByte(Bit8u val,bool first)446 void CNullModem::transmitByte (Bit8u val, bool first) {
447 // transmit it later in THR_Event
448 if(first) setEvent(SERIAL_THR_EVENT, bytetime/8);
449 else setEvent(SERIAL_TX_EVENT, bytetime);
450
451 // disable 0xff escaping when transparent mode is enabled
452 if (!transparent && (val==0xff)) WriteChar(0xff);
453
454 WriteChar(val);
455 }
456
TelnetEmulation(Bit8u data)457 Bits CNullModem::TelnetEmulation(Bit8u data) {
458 Bit8u response[3];
459 if(telClient.inIAC) {
460 if(telClient.recCommand) {
461 if((data != 0) && (data != 1) && (data != 3)) {
462 LOG_MSG("Serial%d: Unrecognized telnet option %d",COMNUMBER, data);
463 if(telClient.command>250) {
464 /* Reject anything we don't recognize */
465 response[0]=0xff;
466 response[1]=252;
467 response[2]=data; /* We won't do crap! */
468 if(clientsocket) clientsocket->SendArray(response, 3);
469 }
470 }
471 switch(telClient.command) {
472 case 251: /* Will */
473 if(data == 0) telClient.binary[TEL_SERVER] = true;
474 if(data == 1) telClient.echo[TEL_SERVER] = true;
475 if(data == 3) telClient.supressGA[TEL_SERVER] = true;
476 break;
477 case 252: /* Won't */
478 if(data == 0) telClient.binary[TEL_SERVER] = false;
479 if(data == 1) telClient.echo[TEL_SERVER] = false;
480 if(data == 3) telClient.supressGA[TEL_SERVER] = false;
481 break;
482 case 253: /* Do */
483 if(data == 0) {
484 telClient.binary[TEL_CLIENT] = true;
485 response[0]=0xff;
486 response[1]=251;
487 response[2]=0; /* Will do binary transfer */
488 if(clientsocket) clientsocket->SendArray(response, 3);
489 }
490 if(data == 1) {
491 telClient.echo[TEL_CLIENT] = false;
492 response[0]=0xff;
493 response[1]=252;
494 response[2]=1; /* Won't echo (too lazy) */
495 if(clientsocket) clientsocket->SendArray(response, 3);
496 }
497 if(data == 3) {
498 telClient.supressGA[TEL_CLIENT] = true;
499 response[0]=0xff;
500 response[1]=251;
501 response[2]=3; /* Will Suppress GA */
502 if(clientsocket) clientsocket->SendArray(response, 3);
503 }
504 break;
505 case 254: /* Don't */
506 if(data == 0) {
507 telClient.binary[TEL_CLIENT] = false;
508 response[0]=0xff;
509 response[1]=252;
510 response[2]=0; /* Won't do binary transfer */
511 if(clientsocket) clientsocket->SendArray(response, 3);
512 }
513 if(data == 1) {
514 telClient.echo[TEL_CLIENT] = false;
515 response[0]=0xff;
516 response[1]=252;
517 response[2]=1; /* Won't echo (fine by me) */
518 if(clientsocket) clientsocket->SendArray(response, 3);
519 }
520 if(data == 3) {
521 telClient.supressGA[TEL_CLIENT] = true;
522 response[0]=0xff;
523 response[1]=251;
524 response[2]=3; /* Will Suppress GA (too lazy) */
525 if(clientsocket) clientsocket->SendArray(response, 3);
526 }
527 break;
528 default:
529 LOG_MSG("MODEM: Telnet client sent IAC %d", telClient.command);
530 break;
531 }
532 telClient.inIAC = false;
533 telClient.recCommand = false;
534 return -1; //continue;
535 } else {
536 if(data==249) {
537 /* Go Ahead received */
538 telClient.inIAC = false;
539 return -1; //continue;
540 }
541 telClient.command = data;
542 telClient.recCommand = true;
543
544 if((telClient.binary[TEL_SERVER]) && (data == 0xff)) {
545 /* Binary data with value of 255 */
546 telClient.inIAC = false;
547 telClient.recCommand = false;
548 return 0xff;
549 }
550 }
551 } else {
552 if(data == 0xff) {
553 telClient.inIAC = true;
554 return -1;
555 }
556 return data;
557 }
558 return -1; // ???
559 }
560
561
562 /*****************************************************************************/
563 /* setBreak(val) switches break on or off **/
564 /*****************************************************************************/
565
setBreak(bool)566 void CNullModem::setBreak (bool /*value*/) {
567 CNullModem::setRTSDTR(getRTS(), getDTR());
568 }
569
570 /*****************************************************************************/
571 /* updateModemControlLines(mcr) sets DTR and RTS. **/
572 /*****************************************************************************/
setRTSDTR(bool xrts,bool xdtr)573 void CNullModem::setRTSDTR(bool xrts, bool xdtr) {
574 if(!transparent) {
575 Bit8u control[2];
576 control[0]=0xff;
577 control[1]=0x0;
578 if(xrts) control[1]|=1;
579 if(xdtr) control[1]|=2;
580 if(LCR&LCR_BREAK_MASK) control[1]|=4;
581 if(clientsocket) clientsocket->SendArray(control, 2);
582 }
583 }
setRTS(bool val)584 void CNullModem::setRTS(bool val) {
585 setRTSDTR(val, getDTR());
586 }
setDTR(bool val)587 void CNullModem::setDTR(bool val) {
588 setRTSDTR(getRTS(), val);
589 }
590 #endif
591