/******
 *
 *	H A Y E S . C                by Jan Ceuleers, The Experimenter Board, 1992
 *	                                           part of the ISDN FOSSIL project
 *
 ******/

#define HAYES

#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fos_isdn.h"

// hayesState values:
//
//	High bit:
//		0: data mode
//		1: command mode
//
//	Other bits if in command mode:
//		0: at beginning of line
//		1: 'a' or 'A' received at beginning of line
//		2: "AT" received at beginning of line in any case combination;
//		   accumulating commands into buffer
//		3: something other than "AT" at the beginning of the line was received;
//		   not a valid command; ignoring rest of line

#define NUMBER_OF_RESPONSE_CODES 30
#define NUMBER_OF_RESPONSE_QUALIFIERS 15

const char *modemResponseCodes[NUMBER_OF_RESPONSE_CODES] =
{
	"OK"
,	"CONNECT"
,	"RING"
,	"NO CARRIER"
,	"ERROR"
,	""				// Connect 1200; for compatibility with old numeric result codes only
,	"NO DIAL TONE"
,	"BUSY"
,	"NO ANSWER"
,	"CONNECT 50"
,	"CONNECT 75"
,	"CONNECT 110"
,	"CONNECT 150"
,	"CONNECT 200"
,	"CONNECT 300"
,	"CONNECT 600"
,	"CONNECT 1200"
,	"CONNECT 2400"
,	"CONNECT 3600"
,	"CONNECT 4800"
,	"CONNECT 7200"
,	"CONNECT 9600"
,	"CONNECT 12000"
,	"CONNECT 14400"
,	"CONNECT 19200"
,	"CONNECT 38400"
,	"CONNECT 48000"
,	"CONNECT 56000"
,	"CONNECT 64000"
,	""
};

const char *modemResponseQualifiers[NUMBER_OF_RESPONSE_QUALIFIERS] =
{
	""
,	"/V110/ASYN"
,	"/V110/SYN"
,	"/V110/SYN/X25/DTE"
,	"/V110/SYN/X25/DCE"
,	"/V110/SYN/X25/PAD/DTE"
,	"/V110/SYN/X25/PAD/DCE"
,	"/V120/ASYN"
,	"/V120/SYN"
,	"/X25/DTE/CSW"
,	"/X25/DCE/CSW"
,	"/X25/PAD/DTE/CSW"
,	"/X25/PAD/DCE/CSW"
,	"/X25/DTE/PSW"
,	"/X25/PAD/DTE/PSW"
};

const char *productInfo = "\r\nISDN FOSSIL modem emulation v1.0 -- by Jan Ceuleers 2:292/857\r\n\r\n";

/* S Registers:
 *     00: Number of rings after which to answer (0: don't answer)
 *     01: Rings so far
 *     02: Escape character
 *     03: Carriage return character
 *     04: Line feed character
 *     05: Backspace character
 *     06: Wait time before dialing (ignored)
 *     07: Wait for carrier (ignored)
 *     08: Pause time for the comma command (ignored)
 *     09: Carrier detect time (ignored)
 *     10: Carrier lost time (ignored)
 *     11: Speed of tone dialing (ignored)
 *     12: Escape sequence guard time (unit: 20ms) (not implemented)
 *     13: HLC to use on outgoing calls
 *     14: HLC coding standard to use on outgoing calls
 *     15: channel usage on outgoing calls
 *     16: speed of outgoing calls
 *     17: asynchronous communications parameters
 *     18: ISDN protocol to use (see APIDEF.INC for values)
 *     19: Protocol override for outgoing calls
 */

const unsigned char registerMaxValues[NUMBER_OF_REGISTERS] =
{//   0    1    2    3    4    5    6    7    8    9
	255, 255, 127, 127, 127, 127, 255, 255, 255, 255	// 0
,	255, 255,  93, 255,   2,   2,  98,   7,  93, 255	// 1
};


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ newlineRx
//
// This function sends a CR/LF sequence to the receive buffer.
//
void newlineRx(port_t *pport)
{
	charIntoRxBuf(pport, P(modem.S[3]));
	charIntoRxBuf(pport, P(modem.S[4]));
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ emitResponseCode
//
// This function sends a response code to the receive buffer. This response
// code, if response codes are enabled (Q1), can be either numeric (V0) or
// text (V1). The text response codes can be basic (X0) or verbose (X1).
//
void emitResponseCode(port_t *pport)
{
	if (P(modem.Q) == 0)		// Result codes disabled?
	{
		newlineRx(pport);

		if (P(modem.V) == 0)
		{
			// Short form result codes
			char outbuf[3];

			itoa(P(modem.responseCode), outbuf, 10);
			blockIntoRxBuf(pport, (charFarPtr_t)outbuf, strlen(outbuf));
		}
		else
		{
			// Long form result codes
			blockIntoRxBuf(pport,
			               (charFarPtr_t)modemResponseCodes[P(modem.responseCode)],
			               strlen(modemResponseCodes[P(modem.responseCode)]));

			if ((P(modem.X) != 0)
			    && ((P(modem.responseCode)==RSP_CONNECT)
			        || ((P(modem.responseCode) >= RSP_CONNECT_50)
			            && (P(modem.responseCode) <= RSP_CONNECT_64000))))
			{
				blockIntoRxBuf(pport,
				               (charFarPtr_t)modemResponseQualifiers[P(modem.extraResponseInfo)],
				               strlen(modemResponseQualifiers[P(modem.extraResponseInfo)]));
			}

			newlineRx(pport);
		}
	}
}

void hayesCONNECT(port_t *pport)
{
	P(call.state) = 2;				// Call proceeding
	P(status) |= 0x0080;			// Set Carrier Detect bit
	LED_ON(LT_STAT_DCD);
	P(modem.hayesState) = 0x80;		// Reset command mode

	// Only from API version 2.1 can the application ask the driver for the
	// actual speed of the call.
//	switch (P(call.speed))
//	{
//		case  0: P(modem.responseCode) = RSP_CONNECT_64000; break;
//		case  1:
//		case  2: P(modem.responseCode) = RSP_CONNECT_56000; break;
//		case  3: P(modem.responseCode) = RSP_CONNECT_48000; break;
//		case  4: P(modem.responseCode) = RSP_CONNECT_19200; break;
//		case  5: P(modem.responseCode) = RSP_CONNECT_14400; break;
//		case  6: P(modem.responseCode) = RSP_CONNECT_12000; break;
//		case  7: P(modem.responseCode) = RSP_CONNECT_9600;  break;
//		case  8: P(modem.responseCode) = RSP_CONNECT_7200;  break;
//		case  9: P(modem.responseCode) = RSP_CONNECT_4800;  break;
//		case 10: P(modem.responseCode) = RSP_CONNECT_3600;  break;
//		case 11: P(modem.responseCode) = RSP_CONNECT_2400;  break;
//		case 12: P(modem.responseCode) = RSP_CONNECT_1200;  break;
//		case 13: P(modem.responseCode) = RSP_CONNECT_600;   break;
//		case 14: P(modem.responseCode) = RSP_CONNECT_300;   break;
//		case 15: P(modem.responseCode) = RSP_CONNECT_200;   break;
//		case 16: P(modem.responseCode) = RSP_CONNECT_150;   break;
//		case 17: P(modem.responseCode) = RSP_CONNECT_110;   break;
//		case 18: P(modem.responseCode) = RSP_CONNECT_75;    break;
//		case 19: P(modem.responseCode) = RSP_CONNECT_50;    break;
//		case 98: P(modem.responseCode) = RSP_CONNECT_38400; break;
//		default: P(modem.responseCode) = RSP_NO_CARRIER;
//	}

/* Instead, we just report CONNECT. */
	P(modem.responseCode) = RSP_CONNECT;

//	if ((P(modem.X) == 0)
//	    && ((P(modem.responseCode) >= RSP_CONNECT_50)
//	        && (P(modem.responseCode) <= RSP_CONNECT_64000)))
//	{
//		P(modem.responseCode) = RSP_CONNECT;
//	}

	switch (P(call.protocol))
	{
		case  0: P(modem.extraResponseInfo) = ERSP_NONE;			 break;
		case  2: P(modem.extraResponseInfo) = ERSP_V110ASYN;		 break;
		case  3: P(modem.extraResponseInfo) = ERSP_V110SYN; 		 break;
		case  4: P(modem.extraResponseInfo) = ERSP_V110SYNX25DTE;	 break;
		case  5: P(modem.extraResponseInfo) = ERSP_V110SYNX25DCE;	 break;
		case  6: P(modem.extraResponseInfo) = ERSP_V110SYNX25PADDTE; break;
		case  7: P(modem.extraResponseInfo) = ERSP_V110SYNX25PADDCE; break;
		case  8: P(modem.extraResponseInfo) = ERSP_V120ASYN;		 break;
		case  9: P(modem.extraResponseInfo) = ERSP_V120SYN; 		 break;
		case 10: P(modem.extraResponseInfo) = ERSP_X25DTECSW;		 break;
		case 11: P(modem.extraResponseInfo) = ERSP_X25DCECSW;		 break;
		case 12: P(modem.extraResponseInfo) = ERSP_X25PADDTECSW;	 break;
		case 13: P(modem.extraResponseInfo) = ERSP_X25PADDCECSW;	 break;
		case 20: P(modem.extraResponseInfo) = ERSP_X25DTEPSW;		 break;
		case 22: P(modem.extraResponseInfo) = ERSP_X25PADDTECSW;	 break;
		default: P(modem.extraResponseInfo) = -1;
	}

	if ((P(modem.responseCode) == RSP_NO_CARRIER)
		|| (P(modem.extraResponseInfo) == -1))
	{
		// Error: Unknown connect rate or unknown protocol.
		hangup(pport);
		P(modem.responseCode) = RSP_NO_CARRIER;
	}

	emitResponseCode(pport);

	P(modem.hayesState) = 0x00;		// Switch to data mode
}


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesAnswerCall
//
// This function answers an incoming call from the 'modem' point of view. It
// calls the call handling connect function (answerCall) and checks the call
// state to determine what to do next: NO CARRIER, ERROR, CONNECT,...
//
void hayesAnswerCall(port_t *pport)
{
	answerCall(pport);

	switch (P(call.state))
	{
		case 0:		// Call wasn't answered --> NO CARRIER
			P(modem.responseCode) = RSP_NO_CARRIER;
			emitResponseCode(pport);
			break;

		case 1:		// Call still ringing --> program error ?...?
		case 3:		// Outgoing call pending --> ???
		default:
			P(modem.responseCode) = RSP_ERROR;
			emitResponseCode(pport);
			break;

		case 2:		// Call proceeding --> CALLER ID, CONNECT
			newlineRx(pport);
			blockIntoRxBuf(pport, (charFarPtr_t)"CALLER ID: ", 11);
			blockIntoRxBuf(pport, (charFarPtr_t)P(call.address), strlen(P(call.address)));

			if (strlen(P(call.subaddress)) > 0)
			{
				charIntoRxBuf(pport, '(');
				blockIntoRxBuf(pport, (charFarPtr_t)P(call.subaddress), strlen(P(call.subaddress)));
				charIntoRxBuf(pport, ')');
			}

//			newlineRx(pport);

			hayesCONNECT(pport);
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATA
//
// This function implements the Answer AT command.
//
static void hayesATA(char **pp, port_t *pport)
{
	if (P(call.state) == 1)	// Incoming call ringing
	{
		hayesAnswerCall(pport);
	}
	else					// No incoming call detected
	{
		// Send NO CARRIER right away...
		P(modem.responseCode) = RSP_NO_CARRIER;
		emitResponseCode(pport);
	}

	(*pp)++;
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesStore
//
// This funuction is called by some of the other AT command processing
// functions. It reads a value from the command buffer and stores it in the
// indicated address. Bounds checking is also performed.
//
static void hayesStore(char **p, port_t *pport, unsigned char *reg, char upperLimit)
{
	if (!isdigit(**p))
	{
		*reg = 0;
	}
	else
	{
		unsigned bin = atoi(*p);

		if (bin <= upperLimit)
		{
			*reg = bin;
			while (isdigit(**p)) (*p)++;
		}
		else
		{
			P(modem.responseCode) = RSP_ERROR;
			emitResponseCode(pport);
		}
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATD
//
// This function parses the parameters of the Dial AT command and subsequently
// executes it. The basic syntax of the command is
//
//     ATD99999999*999
//
// Where each 9 is a digit or a pound sign (#). The asterisk serves as a
// delimiter between called address and called subaddress. The maximum length
// of address and subaddress is determined by the relevant definitions in
// apidef.inc. The subaddress is optional.
//
// Modifiers such as 'P', 'T', ',', 'W' and '-' are accepted but ignored.
//
static void hayesATD(char **p, port_t *pport)
{
	enum {address, subaddress} state = address;
	int counter = A_ADDRESS_ARRAY_LENGTH;
	char *dest = P(call.address);
	int done = 0;

	*P(call.address) = *P(call.subaddress) = '\0';

	while ((!done) && (P(modem.responseCode) == RSP_OK))
	{
		switch(**p)
		{
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
			case '#':
				*dest++ = **p;

				if (!--counter)
				{
					P(modem.responseCode) = RSP_ERROR;
					done = 1;
				}

				break;

			case '*':
				// Switch to gathering the subaddress
				if (state == address)
				{
					state = subaddress;
					*dest = '\0';
					counter = A_SUBADDRESS_ARRAY_LENGTH;
					dest = P(call.subaddress);
				}
				else
				{
					P(modem.responseCode) = RSP_ERROR;
					done = 1;
				}
				break;

			case '-':			// Internal number separator
			case ',':			// Pause
			case 'W': case 'w':	// Wait for 2nd dialtone
			case 'T': case 't':	// DTMF
			case 'P': case 'p':	// Decadic
				break;

			default:
				*dest = '\0';
				done = 1;
		}

		(*p)++;
	}

	if (P(modem.responseCode) == RSP_OK)
	{
		setupCall(pport);
		P(modem.responseCode) = RSP_NONE;
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATH
//
// This function first reads a numeric parameter and then acts upon it:
//
//    0: Hang up. Terminate the ongoing call. Return to state where incoming
//       calls can be accepted.
//    1: Go 'off-hook': indicate to the call handling module that the
//       application is not ready to accept incoming calls. The calls can then
//       be handled by another task, or if all are busy, they're rejected.
//
static void hayesATH(char **p, port_t *pport)
{
	char hookState;

	hayesStore(p, pport, &hookState, 1);

	if (P(modem.responseCode) == RSP_OK)
	{
		hangup(pport);

		if (hookState == 0)
		{
			P(modem.responseCode) = RSP_NO_CARRIER;
			emitResponseCode(pport);
			P(call.refuseAllCalls) = 0;
		}
		else
		{
			P(call.refuseAllCalls) = 1;
		}
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATI
//
// This function displays Information about the driver on the terminal. This
// includes the current settings.
//
static void hayesATI(char **p, port_t *pport)
{
	if (P(modem.E) == 1) newlineRx(pport);
	blockIntoRxBuf(pport, (charFarPtr_t)productInfo, strlen(productInfo));

	{
		char *cmdName = "BELMQVXY";
		unsigned char *cmdValue = &(P(modem.B));
		char outBuf[9];

		for (; *cmdName; cmdName++, cmdValue++)
		{
			sprintf(outBuf, "%c%d   ", *cmdName, *cmdValue);

			if (strlen(outBuf) != blockIntoRxBuf(pport, (charFarPtr_t)outBuf, strlen(outBuf)))
				P(overrun) = 1;
		}
	}

	newlineRx(pport); newlineRx(pport);

	{
		unsigned regnum;
		char outBuf[10];

		for (regnum = 0; regnum < NUMBER_OF_REGISTERS; regnum++)
		{
			sprintf(outBuf, "S%02d=%03d  ", regnum, P(modem.S[regnum]));

			if (strlen(outBuf) != blockIntoRxBuf(pport, (charFarPtr_t)outBuf, strlen(outBuf)))
				P(overrun) = 1;

			if (7 == (regnum % 8)) newlineRx(pport);
		}
	}

	newlineRx(pport);
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATO
//
// This function returns on-line.
//
static void hayesATO(char **p, port_t *pport)
{
	// Call proceeding and command mode
	if ((P(call.state) == 2) && (P(modem.hayesState) & 0x80))
	{
		hayesCONNECT(pport);
		P(modem.hayesState) = 0;	// Switch to data mode
	}
	else
	{
		P(modem.responseCode) == RSP_ERROR;
		emitResponseCode(pport);
	}
}


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ parseInt
//
// This function parses a decimal number.
//
static int parseInt(char **p, int maxval)
{
	int retval = 0;

	while ((**p >= '0') && (**p <= '9'))
	{
		if (retval <= ((maxval + '0' - **p) / 10))
		{
			retval *= 10;
			retval += **p - '0';
		}
		else
		{
			retval = -1;
			break;
		}

		(*p)++;
	}

	return retval;
}


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננ displayRegisterRx
//
// This function sends the contents of a register to the terminal as a 3-digit
// number.
//
static void displayRegisterRx(port_t *pport, unsigned char reg)
{
	char numbuf[4];

	if (P(modem.E) == 1)
		newlineRx(pport);

	// Display the argument as a 3-digit decimal number
	numbuf[0] = numbuf[1] = numbuf[2] = '0';
	numbuf[3] = '\0';

	if (reg < 10)
	{
		itoa(reg, numbuf+2, 10);
	}
	else if (reg < 100)
	{
		itoa(reg, numbuf+1, 10);
	}
	else
	{
		itoa(reg, numbuf, 10);
	}

	blockIntoRxBuf(pport, (charFarPtr_t)numbuf, 3);
	newlineRx(pport);
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATS
//
// This function implements the register editor. Registers can be displayed
// and modified.
//
// The syntax for displaying a register is ATSr?, where r is the register
// number. The syntax for modifying a register is ATSr=v, where r is the
// register number and v is the new value. The fancier options, such as bit
// field editing, are not supported.
//
static void hayesATS(char **p, port_t *pport)
{
	int regnum;
	int value;

	// Get the register number
	if ((regnum = parseInt(p, NUMBER_OF_REGISTERS-1)) == -1)
	{
		P(modem.responseCode) = RSP_ERROR;
		emitResponseCode(pport);
	}
	else
	{
		switch (**p)
		{
			case '?':
				// Display the contents of this register
				displayRegisterRx(pport, P(modem.S[regnum]));
				(*p)++;
				break;

			case '=':
				// Change this register
				(*p)++;

				if ((value = parseInt(p, registerMaxValues[regnum])) == -1)
				{
					P(modem.responseCode) = RSP_ERROR;
					emitResponseCode(pport);
				}
				else
				{
					// The backspace character must not be printable
					if ((regnum == 5) && ((value > 32) && (value < 127)))
					{
						P(modem.responseCode) = RSP_ERROR;
						emitResponseCode(pport);
					}
					else
					{
						P(modem.S[regnum]) = value;
					}
				}

				break;

			default:
				P(modem.responseCode) = RSP_ERROR;
				emitResponseCode(pport);
		}
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ resetModemEmul

void resetModemEmul(port_t *pport)
{
	// Sorry, there's no 'Stored Configuration Profile': the default
	// configuration is hard coded.
	P(modem.B) = 0;			// This register is ignored
	P(modem.E) = 1;			// Enable command echo
	P(modem.L) = 2;			// This register is ignored
	P(modem.M) = 1;			// This register is ignored
	P(modem.Q) = 0;			// Enable result codes
	P(modem.V) = 1;			// Long form result codes
	P(modem.X) = 4;			// Detailed result codes
	P(modem.Y) = 0;			// This register is ignored
	P(modem.S[0]) = 0;		// Disable auto answer
	P(modem.S[1]) = 0;		// Ring count
	P(modem.S[2]) = '+';	// Escape character
	P(modem.S[3]) = 13;		// Carriage return character
	P(modem.S[4]) = 10;		// Line feed character
	P(modem.S[5]) = 8;		// Backspace character
	P(modem.S[6]) = 2;		// This register is ignored
	P(modem.S[7]) = 45;		// Wait time for carrier
	P(modem.S[8]) = 2;		// This register is ignored
	P(modem.S[9]) = 6;		// This register is ignored
	P(modem.S[10]) = 14;	// This register is ignored
	P(modem.S[11]) = 70;	// This register is ignored
	P(modem.S[12]) = 50;	// Escape code guard time (1s)
	P(modem.S[13]) = AM_HLC_ID_RESERVED;	// HLC
	P(modem.S[14]) = AM_HLC_CCITT_STANDARD;	// HLC coding standard
	P(modem.S[15]) = AM_B_NO_MUX;			// channel usage
	P(modem.S[16]) = AM_SPEED_64_K;			// speed
	P(modem.S[17]) = AM_8B_NP_1S;			// async parms
	P(modem.S[18]) = AM_PROT_V120_ASYNC;	// protocol
	P(modem.S[19]) = AM_PROT_V120_ASYNC;	// protocol override

	P(modem.hayesState) = 0x80;				// Command mode
	*P(modem.hayesCommandBuffer) = '\0';
	P(modem.hayesBufTail) = P(modem.hayesCommandBuffer);
	P(modem.chars) = 0;
	P(modem.responseCode) = RSP_OK;
	P(modem.extraResponseInfo) = 0;
//	emitResponseCode(pport);
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hayesATZ
//
// This function resets the modem emulator.
//
static void hayesATZ(char **p, port_t *pport)
{
	hangup(pport);
	resetModemEmul(pport);
}

static void hayesATampersand(char **p, port_t *pport)
{
//!!!
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ executeHayes
//
// This function executes and parses the contents of the command buffer.
//
void executeHayes(port_t *pport)
{
	char *p;

	P(modem.responseCode) = RSP_OK;

	for (p = P(modem.hayesCommandBuffer);
	     (*p) && (P(modem.responseCode) == RSP_OK) && (P(modem.hayesState) & 0x80); )
	{
		switch ((*p >= 'a') && (*p <= 'z') ? *p++ & ~0x20: *p++)
		{
			case 'A':	// Answer incoming call
				hayesATA(&p, pport);
				break;

			case 'B':	// Only store this-one
				hayesStore(&p, pport, &(P(modem.B)), 1);
				break;

			case 'D':	// Dial: call setup
				hayesATD(&p, pport);
				break;

			case 'E':	// Echo on/off
				hayesStore(&p, pport, &(P(modem.E)), 1);
				break;

			case 'H':	// On/off hook
				hayesATH(&p, pport);
				break;

			case 'I':	// Product code
				hayesATI(&p, pport);
				break;

			case 'L':	// Only store this-one
				hayesStore(&p, pport, &(P(modem.L)), 3);
				break;

			case 'M':	// Only store this-one
				hayesStore(&p, pport, &(P(modem.M)), 3);
				break;

			case 'O':	// Return on-line
				hayesATO(&p, pport);
				break;

			case 'Q':	// Result codes on/off
				hayesStore(&p, pport, &(P(modem.Q)), 1);
				break;

			case 'S':	// Registers
				hayesATS(&p, pport);
				break;

			case 'V':	// Long/short form result codes
				hayesStore(&p, pport, &(P(modem.V)), 1);
				break;

			case 'X':	// Result code set
				hayesStore(&p, pport, &(P(modem.X)), 4);
				break;

			case 'Y':	// Only store this-one
				hayesStore(&p, pport, &(P(modem.Y)), 1);
				break;

			case 'Z':	// Reset
				hayesATZ(&p, pport);
				break;

//			case '&':	// Alternate command set
//				hayesATampersand(&p, pport);
//				break;

			default:	// Error
				P(modem.responseCode) = RSP_ERROR;
				emitResponseCode(pport);
		}
	}

	// Return to the base state
	P(modem.hayesState) &= 0x80;
	P(modem.hayesBufTail) = P(modem.hayesCommandBuffer);

	if ((P(modem.hayesState) == 0x80) && (P(modem.responseCode) == RSP_OK))
	{
		emitResponseCode(pport);
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ processHayesChar
//
// This function is the entry-point of the AT command set interpreter.
//
void processHayesChar(port_t *pport, unsigned char txchar)
{
	// hayesState values:
	//
	//	High bit:
	//		0: data mode
	//		1: command mode
	//
	//	Other bits if in command mode:
	//		0: at beginning of line
	//		1: 'a' or 'A' received at beginning of line
	//		2: "AT" received at beginning of line in any case combination;
	//		   accumulating commands into buffer
	//		3: something other than "AT" at the beginning of the line was received;
	//		   not a valid command; ignoring rest of line

	switch (P(modem.hayesState))
	{
		// data mode
			case 0x00:	// transparent
				break;

		// command mode
			case 0x80:	// at beginning of line
				if (P(call.state) == 3)		// Outgoing call pending
				{
					// This piece of code hangs up if a key is hit after
					// setting up a call
					hangup(pport);
					P(call.state) = 0;
					P(status) &= ~0x0080; LED_OFF(LT_STAT_DCD);
					P(modem.responseCode) = RSP_NO_CARRIER;
					emitResponseCode(pport);
				}
				else
				{
					P(modem.chars) = 0;

					if (txchar > ' ')
					{
						if ((txchar == 'a') || (txchar == 'A'))
						{
							P(modem.hayesState) = 0x81;
						}
						else P(modem.hayesState) = 0x83;
					}
				}

				break;

			case 0x81:	// "A" received
				if ((txchar == 't') || (txchar == 'T'))
				{
					P(modem.hayesState) = 0x82;
					*P(modem.hayesCommandBuffer) = '\0';
				}
				else P(modem.hayesState) = 0x83;

				if (txchar == P(modem.S[5]))	// backspace
				{
					P(modem.hayesState) = 0x80;
				}

				break;

			case 0x82:	// "AT" received, accumulating
				if (txchar == P(modem.S[5]))	// backspace
				{
					if (P(modem.hayesBufTail) > P(modem.hayesCommandBuffer))
					{
						P(modem.hayesBufTail)--;
						*P(modem.hayesBufTail) = '\0';

						if (P(modem.Q) == 0)
						{
							charIntoRxBuf(pport, ' ');
							charIntoRxBuf(pport, txchar);
						}
					}
					else
					{
						if (P(modem.Q) == 0)
						{
							charIntoRxBuf(pport, 'T');
						}
					}
				}
				else if ((txchar != ' ') && (txchar != P(modem.S[3])) && (txchar != P(modem.S[4])))
				{
					if (P(modem.hayesBufTail) - P(modem.hayesCommandBuffer)
					    < sizeof(P(modem.hayesCommandBuffer)))
					{
						*(P(modem.hayesBufTail))++ = txchar;
						*(P(modem.hayesBufTail)) = '\0';
					}
				}
				break;

			case 0x83:	// Garbage received; ignore
				if (txchar == P(modem.S[5]))
				{
					if (P(modem.chars) <= 1) P(modem.hayesState) = 0x80;
					else P(modem.chars)--;
				}
				else P(modem.chars)++;
				break;
	}
}
