///////////////////////////////////////////////////////////////////////////////////////
//
//    PING.C    version 1.10    July 29/99     (C)opyright by Microchip Technology Inc
//
///////////////////////////////////////////////////////////////////////////////////////
//
// For more documentation read the Microchip Application Note 724
// This code is ready to compile with the HiTech C compiler demo for the PIC16C63A.
//
// You will need these additional things to make this code work:
//
//    - the simple hardware described in application note
//
//    - an Internet account with PPP dialup access (not compatible with all ISPs)
//
//    - replace 5551234 with your ISP's phone number in the line like this
//         if (sendwait("5551234\r","NNECT",3000)) {
//
//    - replace userid with your account userid in the line like this:
//         if (sendwait("userid\r","word:",200))
//
//    - replace password with your account password in the line like this:
//         if (sendwait("password\r","tion:",1000))
//
//    - replace the entire string in the line like this:
//         MakePacket(PAP,REQ,number,"\x14\x06userid\x08password");
//
//         C converts the \x## in the string to a character with that ASCII value
//         ## is a hexadecimal value, so the following character cannot be
//         if the next character is 0-9 or A-F or a-f then it will confuse the compiler
//         the solution is to convert the next characters to \x## until a non hex char
//         if in doubt look at the assembly output from the compiler
//         or try MakePacket(PAP,REQ,number,"\x14\x06" "userid\x08" "password");
//         replace the userid with yours and the \x06 with your userid length
//         replace the password with yours and the \x08 with your password length
//         replace the first value in the string, it must be the string length plus 4
//
// Once login is working you should also change the IP address of the Internet host to ping
// if you can not ping 207.161.117.67 with your PC this code will not work either
//    It is CF.A1.75.43, the characters 2 to 5, in the string in the line like this:
//       MakePacket(IP,0,1,"\x10\xCF\xA1\x75\x43\x8\x0\xF7\xFE\x0\x1\x0\x0");
//    Convert the address you want to hexadecimal and replace the four values.
//
// Make sure the power-on reset and brownout detect config bits are enabled
//
///////////////////////////////////////////////////////////////////////////////////////

// Defines for Internet constants
#define	REQ	1 	// Request options list for PPP negotiations
#define	ACK	2	// Acknowledge options list for PPP negotiations
#define	NAK	3	// Not acknowledged options list for PPP negotiations
#define	REJ	4	// Reject options list for PPP negotiations
#define	TERM	5	// Termination packet for LCP to close connection
#define	IP	0x0021	// Internet Protocol packet
#define	IPCP	0x8021	// Internet Protocol Configure Protocol packet
#define	CCP	0x80FD	// Compression Configure Protocol packet
#define	LCP	0xC021	// Link Configure Protocol packet
#define	PAP	0xC023	// Password Authenication Protocol packet

#define	MaxRx	46	// Maximum size of receive buffer
#define	MaxTx	46	// Maximum size of transmit buffer

unsigned char addr1, addr2, addr3, addr4;	// Assigned IP address
unsigned int rx_ptr, tx_ptr, tx_end;		// pointers into buffers
unsigned int checksum1, checksum2;		// Rx and Tx checksums
unsigned char number;				// Unique packet id

#include <pic1663.h>	// Defines specific to this processor

#define serial_init()	RCSTA=0x90;TXSTA=0x24;SPBRG=103	// Set up serial port
#define serial_tx_ready()	TXIF				// Transmitter empty
#define serial_send(a)	TXREG=a			// Transmit char a
#define serial_rx_ready()	RCIF				// Receiver full
#define serial_get()	RCREG				// Receive char
#define serial_error()	OERR				// USART error
#define serial_fix()	{CREN=0;CREN=1;}		// Clear error

unsigned int TIME;				// 10 millseconds counter
#define TIME_SET(a) TIME=a			// Set 10 millisecond counter to value 'a'
bank1 unsigned char tx_str[MaxRx+1];	// Transmitter buffer
bank1 unsigned char rx_str[MaxTx+1];	// Receiver buffer

// Process all the interupts in the PIC here
static void interrupt isr(void) {
   if (T0IF) {			// Timer overflow interrupt?
      TMR0 = 100;			// Set to overflow again in 10ms @ 4MHz
      T0IF = 0;			// Clear overflow interrupt flag
      TIME++;				// Increment 10 ms counter
   }
}

// Add next character to the CRC checksum for PPP packets
unsigned int calc(unsigned int c) {
   char i;				// Just a loop index
   c &= 0xFF;				// Only calculate CRC on low byte
   for (i=0;i<8;i++) {		// Loop eight times, once for each bit
      if (c&1) {			// Is bit high?
         c /= 2;			// Position for next bit      
         c ^= 0x8408;		// Toggle the feedback bits
      } else c /= 2;		// Just position for next bit
   }					// This routine would be best optimized in assembly
   return c;				// Return the 16 bit checksum
}

// Add character to the new packet
void add(unsigned char c) {
   checksum2 = calc(c^checksum2) ^ (checksum2/256); // Add CRC from this char to running total
   tx_str[tx_ptr] = c;		// Store character in the transmit buffer
   tx_ptr++;				// Point to next empty spot in buffer
}

// Create packet of type, code, length, and data string specified
//   packet is the type, like LCP or IP
//   code is the LCP type of packet like REQ, not used for IP packets
//   num is the packet ID for LCP, or the IP data type for IP packets
//   *str is the packet data to be added after the header
//   returns the packet as a string in tx_str
void MakePacket(unsigned int packet, unsigned char code, unsigned char num, const unsigned char *str) {
   unsigned int length;		// Just a dual use temp variable
   tx_ptr = 1;			// Point to second character in transmit buffer
   tx_str[0] = ' ';			// Set first character to a space for now
   checksum2 = 0xFFFF;		// Initialize checksum
   add(0xFF);				// Insert PPP header OxFF
   add(3);				// Insert PPP header 0x03
   add(packet/256);			// Insert high byte of protocol field
   add(packet&255);			// Insert low byte of protocol field
   if (packet==IP) {		// If Internet Protocol
      add(0x45);			// Insert header version and length
      add(0);				// Insert type of service
      add(0);				// Insert total packet length high byte
      add((*str)+12);		// Insert total packet length low byte
      add(0x88);			// Insert identification high byte
      add(0x10);			// Insert identification low byte
      add(0x40);			// Insert flags and fragement offset
      add(0);				// Insert rest of fragment offset
      add(127);			// Insert time to live countdown
      add(num);			// insert the protocol field
      length = 0x45+0x88+0x40+127+addr1+addr3+str[1]+str[3];  // high byte checksum
      packet = *str + 12 + 0x10 + num + addr2 + addr4 + str[2] + str[4];  
							// low byte checksum
      packet += length/256;			// make 1's complement
      length = (length&255) + packet/256;	// by adding low carry to high byte
      packet = (packet&255) + length/256;	// and adding high carry to low byte
      length += packet/256;			// fix new adding carries
      add(~length);			// Insert 1's complement checksum high byte
      add(~packet);			// Insert 1's complement checksum low byte
      add(addr1);			// Insert the 4 bytes of this login's IP address
      add(addr2);
      add(addr3);
      add(addr4);
      length = *str - 4;		// save the number of following data bytes
      str++;				// point to the first data byte
   } else {
      add(code);			// Insert packet type, like REQ or NAK
      add(num);			// Insert packet ID number
      add(0);				// Insert most significant byte of length
      length = *str - 3;		// point to the first data byte
   }
   while (length) {			// copy the whole string into packet
      length--;			// decrement packet length
      add(*str);			// add current character to packet
      str++;				// point to next character
   }
   length = ~checksum2;		// invert the checksum
   add(length&255);			// Insert checksum msb
   add(length/256);			// Insert checksum lsb
   tx_end=tx_ptr;			// Set end of buffer marker to end of packet
   tx_ptr = 0;			// Point to the beginning of the packet
}

// Test the option list in packet for valid passwords
//   option is the 16 bit field, where a high accepts the option one greater than the
//   bit #
//   returns 2 for LCP NAK, 1 is only correct fields found, and zero means bad options
//   return also modifies RX_STR to list unacceptable options if NAK or REJ required
unsigned char TestOptions(unsigned int option){
   unsigned int size;		// size is length of option string
   unsigned ptr1 = 8,		// ptr1 points data insert location
            ptr2 = 8;		// ptr2 points to data origin
   char pass = 3;			// pass is the return value
   size = rx_str[7]+4;		// size if length of packet
   if (size>MaxRx) size=MaxRx;	// truncate packet if larger than buffer
   while (ptr1<size) {		// scan options in receiver buffer
      if (rx_str[ptr1]==3 && rx_str[ptr1+2]!=0x80  && rx_str[2]==0xc2)
         pass&=0xfd;		// found a CHAP request, mark for NAK
      if (!((1<<(rx_str[ptr1]-1))&option))
         pass=0;			// found illegal options, mark for REJ
      ptr1 += rx_str[ptr1+1];	// point to start of next option
   }
   if (!(pass&2)) {			// If marked for NAK or REJ
      if (pass&1) {			// save state for NAK
         option=0xfffb;
      }
      for (ptr1=8; ptr1<size;) {
         if (!((1<<(rx_str[ptr1]-1))&option)) {	// if illegal option
            for (pass=rx_str[ptr1+1]; ptr1<size && pass; ptr1++) {  // move option
               rx_str[ptr2]=rx_str[ptr1];		// move current byte to new storage
               ptr2++;					// increment storage pointer
               pass--;					// decrement number of characters
            }
         } else {
            ptr1+=rx_str[ptr1+1];				// point to next option
         }
      }
      rx_str[7] = ptr2-4;			// save new option string length
      pass=0;					// restore state for REJ
      if (option==0xfffb) pass=1;		// restore state for NAK
   }
   return pass;
}

// Send a string and loop until wait string arrives or it times out
//  send is the string to transmit
//  wait is the string to wait for
//  timeout is in multiples of 10 milliseconds
//  addr1 is used to control the status LED, 0=off, 1=flash, 2=on
//  returns 0 if timeout, returns 1 if wait string is matched
char sendwait(const char *send, const char *wait, unsigned int timeout) {
   addr2=addr3=0;
   for (TIME_SET(0); TIME<timeout; ) {		// loop until time runs out
      if (!addr1) PORTB&=0xFB;			// if addr1=0 turn off status LED
      else if (addr1==1) {				// if addr1=1 flash status LED
         if (TIME&4) PORTB&=0xFB;			// flash period is 8 x 10ms 
         else PORTB|=4;
      } else PORTB|=4;				// if addr1>1 turn on status LED
      if (serial_rx_ready()) {			// is there an incoming character
         PORTB|=1;					// turn on the Rx LED
         addr4 = serial_get();			// get character
         if (serial_error()) serial_fix();	// clear serial errors
         if (wait[addr2]==addr4) addr2++;	// does char match wait string
         else addr2=0;				// otherwise reset match pointer
         PORTB&=0xFE;				// turn off the Rx LED
         if (!wait[addr2]) return 1;		// finished if string matches
      } else if (send[addr3] && (serial_tx_ready())) {  // if char to send and Tx ready
         if (send[addr3]=='|') {	// if pause character
            if (TIME>100) {			// has 1 second expired yet?
               TIME_SET(0);			// if yes clear timer
               addr3++;				// and point to next character
            }
         } else {
            PORTB|=2;			// turn on Tx LED
            TIME_SET(0);			// clear timer, timeout starts after last char
            serial_send(send[addr3]);	// send the character
            addr3++;			// point to next char in tx string
         }
         PORTB&=0xFD;			// turn off Tx LED
         if (!send[addr3] && !(*wait))
            return 1; 			// done if end of string and no wait string
      }
   }
   return 0;					// return with 0 to indicate timeout
}

void flash(void) {				// flash all LEDs if catastrophic failure
   for (TIME_SET(0);;) {
      if (TIME&8) PORTB|=0x07;		// flash period is 16 x 10ms
      else PORTB&=0xF8;
      if (TIME>3000) PORTB&=0xF7;	// after 30 seconds turn off the power
   }
}

void pulse(unsigned char data) {		// pulse Status LED with IP address
   TIME_SET(0);
   for(number=0;number<9;) {		// pulse out 8 address bits and a blank
      if (TIME<100) PORTB&=0xFB;	// turn off Status LED between bits
      else if (number<8) PORTB|=4;	// start each address bit here
      if (TIME>200 || (!(data&0x80) && TIME>120)) {	// end of bit?
         TIME_SET(0);			// yes, then restart timer for next bit
         number++;				// increment bit counter
         data<<=1;				// position address to send next bit
      }
   }
}

// The main loop, login script, PPP state machine, and ping transponder
void main(void) {
   signed int c;			// serial character received
   unsigned int packet = 0;	// Type of the last received packet, reused as temp
   unsigned char state = 0;	// PPP negotiation state, from dialing=0 to done=6
   unsigned char extended = 0;	// flag if last character was an escape sequence

   PORTA=0;
   PORTB=0;				// Turn off power supply, turn off LEDs
   PORTC=0;
   TRISA=0;				// Turn all I/O into outputs
   TRISB=0x00;
   TRISC=0xC0;
   OPTION=0x85;			// Set up TIMER 0 for millisecond counting
   INTCON=0xA0; 

   serial_init();		// Initalize serial port to 2400 baud format N81
   TIME_SET(0);
   while (TIME<25);		// 250 millisecond delay to prevent false power up
   PORTB=8;			// Turn on the power so user can release power button

   for(number=1;;number++) {		// Redial indefinately every 30 seconds
      if (number==10) PORTB&=0xF7;	// Turn off power if dialing fails
      addr1=0;				// Set flag to keep the Status LED off
      if(!sendwait("|+++|\rath\r|atz\r|at&fs11=55\r|atdt","atdt",3000))	// Init modem
         flash();
      addr1=1;				// Set flag to flash Status LED
      // Modify this line with your ISP phone number 
      if (sendwait("5551234\r","NNECT",3000)) {
         addr1=2;				// Set flag to keep the Status LED on
         if (sendwait("",": ",1000))	// Wait for user id prompt
            if (sendwait("\x7e\xff\x7d\x23\x08\x08\x08\x08","~~",1000)) 
               break; 			// Start PPP
            else {						// Fallback to script if required
               if (sendwait("userid\r","word:",200))	// Modify these lines as described
                  if (sendwait("password\r","tion:",1000))
                     if (!sendwait("ppp\r","IP address",200))	
                       // Modify is start PPP command is not ppp or 2
                       sendwait("2\r","IP address",200);
            }
         break;
      }
   }

   // State machine loop until successful ping or PPP negotiation timeout
   for (TIME_SET(0);;) {
      if (TIME>7000 || number>20) PORTB&=0xF7;
      if (serial_rx_ready()) {			// Incoming character?
         PORTB ^=1;					// Turn on Rx LED
         c = serial_get();				// get the character
         if (serial_error()) serial_fix();	// clear Rx errors
         if (c == 0x7E) {				// start or end of a packet 
            if (rx_ptr && (checksum1==0xF0B8))
               packet = rx_str[2]*256 + rx_str[3]; // if CRC passes accept packet
            extended &= 0x7E;			// clear escape character flag
            rx_ptr = 0;				// get ready for next packet
            checksum1 = 0xFFFF;			// start new checksum
         } else if (c == 0x7D) {			// if tilde character set escape flag
            extended |= 1;
         } else {
            if (extended&1) {			// if escape flag
               c ^= 0x20;				// recover next character
               extended &= 0xFE;			// clear Rx escape flag
            }
            if (rx_ptr==0 && c!=0xff) rx_str[rx_ptr++] = 0xff; // uncompress PPP header
            if (rx_ptr==1 && c!=3) rx_str[rx_ptr++] = 3;
            if (rx_ptr==2 && (c&1)) rx_str[rx_ptr++] = 0;
            rx_str[rx_ptr++] = c;			// insert character in buffer
            if (rx_ptr>MaxRx) rx_ptr = MaxRx;	// Inc pointer up to end of buffer
            checksum1 = calc(c^checksum1) ^ (checksum1/256); // calculate CRC checksum
         }
         PORTB&=0xFE;				// turn off Status LED
      } else if (tx_end && (serial_tx_ready())) { // Data to send and Tx empty?
         PORTB|=2;					// turn on Tx LED
         c = tx_str[tx_ptr];			// get character from buffer
         if (tx_ptr==tx_end) {			// was it the last character
            tx_end=0;				// mark buffer empty
            c='~';					// send tilde character last
            PORTB&=0xFD;				// turn off Tx LED
         } else if (extended&2) {			// sending escape sequence?
            c^=0x20;				// yes then convert character
            extended &= 0xFD;			// clear Tx escape flag
            tx_ptr++;				// point to next char
         } else if (c<0x20 || c==0x7D || c==0x7E) { // if escape sequence required?
            extended |= 2;				// set Tx escape flag
            c = 0x7D;					// send escape character
         } else {
           if (!tx_ptr) c='~';			// send ~ if first character of packet
           tx_ptr++;
         }
         serial_send(c);				// Put character in transmitter
      }

      if (packet == LCP) {
         switch (rx_str[4]) {			// Switch on packet type
            case REQ:
               state &= 0xfd;			// clear remote ready state bit
               if (c=TestOptions(0x00c6)) {	// is option request list OK?
                  if (c>1) {
                     c = ACK;			// ACK packet
                     if (state<3) state |= 2;	// set remote ready state bit
                  } else {
                     rx_str[10]=0xc0;		// else NAK password authentication
                     c = NAK;
                  }
               } else {				// else REJ bad options
                  c = REJ;
               }
               TIME_SET(0);
               MakePacket(LCP,c,rx_str[5],rx_str+7); // create LCP packet from Rx buffer
               break;
            case ACK:
               if (rx_str[5]!=number) break;	// does reply id match the rrquest
               if (state<3) state |= 1;		// Set the local ready flag
               break;
            case NAK:
               state &= 0xfe;			// Clear the local ready flag
               break;
            case REJ:
               state &= 0xfe;			// Clear the local ready flag
               break;
            case TERM:
               break;
         }
         if (state==3) state = 4;		// When both ends ready, go to state 4
      } else if (packet == PAP) {
         switch (rx_str[4]) {			// Switch on packet type
            case REQ:				
               break;				// Ignore incoming PAP REQ
            case ACK:
               state = 5;			// PAP ack means this state is done
               break;
            case NAK:
               break;				// Ignore incoming PAP NAK
         }
      } else if (packet == IPCP) {
         switch (rx_str[4]) {			// Switch on packet type
            case REQ:
               if (TestOptions(0x0004)) {	// move to next state on ACK
                  c = ACK;
                  state = 6;
               } else {				// otherwise reject bad options
                  c = REJ;
               }
               MakePacket(IPCP,c,rx_str[5],rx_str+7);
               break; 				// Create IPCP packet from Rx buffer
            case ACK:
               if (rx_str[5]==number) {		// If IPCP response id matches request id
                  state = 7;			// Move into final state
                  pulse(addr1);			// Pulse Status LED to show the 
                  pulse(addr2);			// IP address
                  pulse(addr3);
                  pulse(addr4);
                  PORTB|=4;			// Turn on Status LED after pulsing
                  TIME_SET(5800);			// Move timer ahead for quicker PING
               }
               break;
            case NAK:   				// This is where we get our address
               addr1 = rx_str[10];
               addr2 = rx_str[11];   		// Store address for use in IP packets 
               addr3 = rx_str[12];
               addr4 = rx_str[13];
               MakePacket(IPCP,REQ,rx_str[5],rx_str+7);
               break; 				// Make IPCP packet from Rx buffer
            case REJ:
               break;				// Ignore incoming IPCP REJ
            case TERM:
               break;				// Ignore incoming IPCP TERM
         }
      } else if (packet == IP) {
         if (state<7 || (rx_str[19]==addr4 && rx_str[18]==addr3 && 
                         rx_str[17]==addr2 && rx_str[16]==addr1)) {
                    // ignore echoed packets from our address or before we reach state 7
                    // may power down here because echos are good indications that modem
                    // connection hungup
                    // This would be a good place to insert a traceroute test and 
                    // response
         } else if (rx_str[13]==1) {   		// IP packet with ICMP payload
            if (rx_str[24]==8) {			// Received PING request
               rx_str[20]=rx_str[16];		// Copy 4 origin address bytes to
 							// destination address
               rx_str[21]=rx_str[17];
               rx_str[22]=rx_str[18];
               rx_str[23]=rx_str[19];
               rx_str[19]=16;	// Length of IP address(4) + ping protocol(8) + 4
               rx_str[24]=0;	// Change received ping request(8) to ping reply(0)
               packet = rx_str[28]+rx_str[30];	// Calculate 1's comp checksum
               rx_str[26] = packet&255;
               rx_str[27] = packet/256;
               packet = rx_str[27]+rx_str[29]+rx_str[31];
               rx_str[27] = packet&255;
               packet = packet/256 + rx_str[26];
               rx_str[26] = packet&255;
               rx_str[27] += packet/256;
               rx_str[26] = ~rx_str[26];	// Invert the checksum bits
               rx_str[27] = ~rx_str[27];
               MakePacket(IP,0,1,rx_str+19);	// Make IP packet from modified Rx buffer
            } else if (rx_str[24]==0) { 	// Received PING reply
               if ((rx_str[28]|rx_str[30]|rx_str[31])+rx_str[29]==1)
                  PORTB&=0xF7; 		// Turn off the power after successful ping
            }
         }
      } else if (packet == CCP) {
         switch (rx_str[4]) {		// If CCP response id matches request id
            case REQ:
               c = REJ;
               if (TestOptions(0x0004)) c = ACK; // ACK option 3 only, REJ anything else
               MakePacket(CCP,c,rx_str[5],rx_str+7);	// Create CCP ACK or REJ packet
 								// from Rx buffer
         }
      } else if (packet) {				// Ignore any other received packet types
      } else if (!tx_end && (state==0 || state==2) && TIME>100) { 
 							// Once a second try negotiating LCP
         number++;					// Increment Id to make packets unique
         TIME_SET(0);				// Reset timer
         MakePacket(LCP,REQ,number,"\x0E\x02\x06\x00\x0A\x00\x00\x07\x02\x08\x02"); 
 							// Reqest LCP options 2,7,8
      } else if (!tx_end && state == 4 && TIME>100) {	
 							// Once a second try negotiating password
         TIME_SET(0);					// Reset timer
         number++;
         // format like printf("%c%c%s%c%s",strlen(name)+strlen(password)+6,
         //strlen(name),name,strlen(password),password);
         // Modify this line as described above
         MakePacket(PAP,REQ,number,"\x14\x06userid\x08password"); 
      } else if (!tx_end && state == 6 && TIME>100) {
 							// Once a second try negotiating IPCP
         number++;					// Increment Id to make packets unique
         TIME_SET(0);				// Reset timer
         MakePacket(IPCP,REQ,number,"\xA\x3\x6\x0\x0\x0\x0");
 							// Request IPCP option 3 with addr 0.0.0.0
      } else if (!tx_end && state == 7 && TIME>3000) { // Every 30 seconds do a ping
         TIME_SET(0);					// Reset timer
         number++;					// Increment ping count
         MakePacket(IP,0,1,"\x10\xCF\xA1\x75\x43\x8\x0\xF7\xFE\x0\x1\x0\x0"); 
 							// Ping 207.161.117.67
      }
      packet = 0;					// Indicate that packet is processed
   }
}


