/*
 * GNU.FREE 2002
 *
 * Copyright (c) 1999, 2000, 2001, 2002
 * The Free Software Foundation (www.fsf.org)
 *
 * GNU.FREE Co-ordinator: Jason Kitcat <jeep@free-project.org>
 *
 * GNU site: http://www.gnu.org/software/free/
 * 
 * FREE e-democracy site: http://www.free-project.org
 *
 * 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 (COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
  
package Free;
 
import java.util.*;
import Free.util.*;

/**
 * Processes data from <code>TCPClient</code> to formulate the correct resonses.
 *
 * The source code is extremely readable and provides the best explanation of its function.
 *
 * @version 1.1 8 September 2001
 * @author Jason Kitcat
 */
public class ClientProtocol {

	/**
	 * process analyses packets and responds appropriately
	 *
	 * @param inputData   Contains the contents of a received packet
	 * @returns   String with a packet to return to client
	 * @throws		<code>Exception</code>, all errors are handled by <code>TCPClient</code>
	 *				which has the ability to gracefully close the connection.  
	 */
	protected static String[] process(String inputData, int vectIndex) throws Exception {

		String[] outputData = new String[] {" "," "}; // field one for control data, two for rest
		
		if (inputData.charAt(0) == 'C') {
			/* confirm */
			Packet p = new Packet(inputData);

			// get session data
			String[] sess_data = RTServer.comms.getSession(vectIndex);
			
			if (AuthSys.checkHMAC(p,sess_data[1],new Integer(sess_data[4]).intValue()+1)) {  // tamper check
			
				if (p.getMessage().equals("STAMP")) {
					if (RTServer.comms.getCheck() != null) {
						outputData[1] = new Packet('C',RTServer.comms.getCheckKey()+"-"+RTServer.comms.getCheck().toString(),sess_data[5],RTServer.rt_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
						RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
						RTServer.comms.noCheck(); //clear info
						RTServer.comms.setCheckKey("");
					} else {
						RTServer.NORM.error("Total security error.");
						outputData[1] = "ERROR";
					}
				} else if (p.getMessage().equals("OK")) {  // everything went fine
					RTServer.NORM.info("Total confirmed.");
					/* are there any more totals left? */
					if (RTServer.comms.tdCount < (RTServer.comms.totalData.size()-1)) {
						RTServer.comms.tdCount++;  // if so do the next one
						RTServer.comms.newCheck();
						RTServer.comms.setCheckKey(AuthSys.makeDigest(RTServer.comms.getCheck().toString()+RTServer.comms.totalData.elementAt(RTServer.comms.tdCount)));
						outputData[1] = new Packet('T',RTServer.comms.getCheckKey() + "-" + RTServer.comms.getCheck().toString() + "-" + RTServer.comms.totalData.elementAt(RTServer.comms.tdCount),sess_data[5],RTServer.rt_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
						RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
					} else {  // otherwise tell them we're all done
						outputData[1] = new Packet('T',"DONE",sess_data[5],RTServer.rt_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
						RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
					}					
				} else if (p.getMessage().equals("DONE")) {  // all totals sent successfully
					outputData[0] = "X"; // ending connection
					outputData[1] = new Packet('X',"OK",sess_data[5],RTServer.rt_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
					RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
					RTServer.comms.noCheck(); // just in case, we clear the check date
					RTServer.NORM.info("All totals sent successfully.");
				} else {
					RTServer.NORM.warn("Unrecognized confirm packet received.");
					outputData[1] = "ERROR";
				}
			} else {
				RTServer.NORM.warn("Packet corrupted or altered!");
				outputData[1] = "ERROR";
			}			

		} else if (inputData.charAt(0) == 'D') {
			/* diagnostic */
			Packet p = new Packet(inputData);

			// get session data
			String[] sess_data = RTServer.comms.getSession(vectIndex);
			
			if (AuthSys.checkHMAC(p,sess_data[1],new Integer(sess_data[4]).intValue()+1)) {  // tamper check

				RTServer.NORM.info("Diagnostic packet received.");
				outputData[0] = "X"; // ending connection
				outputData[1] = new Packet('X',"OK",sess_data[5],RTServer.er_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
				RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
			
			} else {
				outputData[1] = "ERROR";
				throw new Exception("Packet corrupted or altered!");
			}
							
		} else if (inputData.charAt(0) == 'I') {
			/* initialisation */
			Packet p = new Packet(inputData);
			
			// parse to get seperate data fields
			String[] pp = p.splitMessage();
			String seed2 = pp[0];
			String clientmac = pp[1];

			// get session data
			String[] sess_data = RTServer.comms.getSession(vectIndex);
			String server_hmac_key = AuthSys.seedToKey(seed2);
			
			// set session data
			//RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],server_hmac_key,sess_data[2],seed2,new Integer(sess_data[4]).intValue(),sess_data[6]);
			
			if (AuthSys.checkHMAC(p,server_hmac_key,new Integer(sess_data[4]).intValue()+1)) {  // tamper check
			
				if (AuthSys.checkHMAC(new Packet("I|"+sess_data[2]+"|"+clientmac),sess_data[0],0)) {  // authentication check of server's key

					outputData[1] = sess_data[6];  // now send real data
					RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],server_hmac_key,sess_data[2],seed2,new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
				
				} else {
					outputData[1] = "ERROR";
					throw new Exception("Packet corrupted or altered!");
				}
			} else {
				outputData[1] = "ERROR";
				throw new Exception("Packet corrupted or altered!");
			}


		} else if (inputData.charAt(0) == 'K') {
			/* AuthKey packet */
			RTServer.NORM.info("AuthKey packet received.");
			Packet p = new Packet(inputData);

			// get session data
			String[] sess_data = RTServer.comms.getSession(vectIndex);
			
			if (AuthSys.checkHMAC(p,sess_data[1],new Integer(sess_data[4]).intValue()+1)) {  // tamper check
				// parse message
				String[] pp = p.splitMessage();
				String ERAuthKey = pp[0];
				String miniMess = pp[1];
				
				if (miniMess.equals("OK")) {
					// remove key + close connection
					if (DBase.removeKey(ERAuthKey)) {
						outputData[0] = "X"; // ending connection
						outputData[1] = new Packet('X',"OK",sess_data[5],RTServer.er_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
						RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
					} else {
						// otherwise it's an error
						outputData[1] = "ERROR";
						throw new Exception("Packet corrupted or altered!");
					}
				} else {
					// ERServer didn't accept key
					RTServer.NORM.warn("AuthKey was rejected by ERServer.");
					// we currently fail so that voters aren't locked out even if they
					// have voted. I'm leaning towards changing this the other way so that
					// they it fails so that voters are locked out even if their vote
					// wasn't registered.
					// It's about balancing voter rights against system security.
					outputData[0] = "X"; // ending connection
					outputData[1] = new Packet('X',"OK",sess_data[5],RTServer.er_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
					RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
				}
			} else {
				outputData[1] = "ERROR";
				throw new Exception("Packet corrupted or altered!");
			}

		} else if (inputData.charAt(0) == 'Q') {
			/* verification query */
			Packet p = new Packet(inputData);

			// get session data
			String[] sess_data = RTServer.comms.getSession(vectIndex);
			
			if (AuthSys.checkHMAC(p,sess_data[1],new Integer(sess_data[4]).intValue()+1)) {  // tamper check

				DBase.verCheck(new Integer(p.getMessage()).intValue());
				outputData[0] = "X"; // ending connection
				outputData[1] = new Packet('X',"OK",sess_data[5],RTServer.er_public_key,sess_data[0],(new Integer(sess_data[4]).intValue()+2)).toCryptString(true);
				RTServer.comms.setSession(vectIndex,sess_data[5],sess_data[0],sess_data[1],sess_data[2],sess_data[3],new Integer(sess_data[4]).intValue()+2,sess_data[6]); // increment
				RTServer.comms.sendTotals();
			
			} else {
				outputData[1] = "ERROR";
				throw new Exception("Packet corrupted or altered!");
			}

		} else {
			outputData[1] = "ERROR";
			RTServer.NORM.warn("Unrecognized packet received.");
		}
		
		return outputData;

	} //EOF process()

} //EOF Class