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