/*
   David Tate

	java BeaconKeyServer port interval_seconds

	BeaconKeyServer, provides centralized
		authorization.  A beacon finds it
		location from the central server, and
		it responds to authenticate, validate, and
		revoke calls.

	In the real world the validateUser method would 
		call a database, but here we assume all names,
		keys are valid.
*/

import java.net.*;
import java.io.*;
import java.util.*;

public class BeaconKeyServer {
	private final int INIT_NUM_TOKENS = 25;
	private Vector tokenList;
	private int port;
	private int interval;	//time (sec) tokens are valid

	public BeaconKeyServer (int p, int i) {
		this.port = p;
		this.interval = i;

		tokenList = new Vector (INIT_NUM_TOKENS);

		System.out.println("BeaconKeyServer listening from port: " + this.port);
		ServerSocket server = null;
		try {
			server = new ServerSocket (this.port);
			Thread t;
			while (true) {
				Socket s = server.accept();
				ObjectOutputStream out = new ObjectOutputStream ( s.getOutputStream() );
				out.flush();
				ObjectInputStream in = new ObjectInputStream ( s.getInputStream() );

				t = new MessageHandler(s, in, out);
				t.start();
			}
		}			
		catch (BindException e) {
			System.out.println("Could not start server because the port " + this.port +" is occupied");
			System.out.println(e);
			e.printStackTrace();
			System.exit(1);
		}
		catch (IOException e) {
			System.out.println(e);
			e.printStackTrace();
			System.exit(1);
		}
		finally {
			try {
				if (server != null) server.close();
			}
			catch (IOException ie) { /* already closed, so ignore */ }
		}		
	}

	class MessageHandler extends Thread {
		private ObjectOutputStream out;
      private ObjectInputStream in;
      private Socket conn;

		public MessageHandler (Socket s, ObjectInputStream i, ObjectOutputStream o) {
			this.conn = s;
			this.out = o;
			this.in = i;
		}
	
		public void run() {
			 try {

            //retrieve message
            Object oMsg = in.readObject();
            BeaconMessage msg = (BeaconMessage) oMsg;
            int type = msg.getType();

            switch (type) {
					case BeaconMessage.AUTHENTICATE:
						handleAuthenticate (msg, out, in);
						break;
					case BeaconMessage.VALIDATE:
						handleValidate (msg, out, in);
						break;
					case BeaconMessage.REVOKE:
						handleRevoke (msg, out, in);
						break;
					default:
						System.out.println("Invalid message recieved, ignoring...");
				}
			}
			 catch (OptionalDataException ode) {
            System.out.println(ode);
            ode.printStackTrace();
         }
         catch (ClassNotFoundException cnfe) {
            System.out.println(cnfe);
            cnfe.printStackTrace();
         }
         catch (SocketException se) {
            System.out.println(se);
            se.printStackTrace();
         }
         catch (IOException ioe) {
            System.out.println(ioe);
            ioe.printStackTrace();
         }
         finally {
            try {
               if (conn != null)  conn.close();
            }
            catch (IOException ie) {/* already closed */ }
         }
		} //end run

		private boolean validateUser (String name, String key) {
			/* Check if user/pass is valid; we assume all are valid for this proj */
			return true;
		}

		private void handleAuthenticate (BeaconMessage msg, ObjectOutputStream out, ObjectInputStream in) {
			String name = (String) msg.getArg(0);
			String key = (String) msg.getArg(1);
	
			try {
				if (validateUser(name,key)) {
					//create new time-stamped token
					Token token = new Token (msg.getAddress(), port, System.currentTimeMillis(), name);
					tokenList.addElement(token);	
				
					//send back to user
				 	Object[] args = new Object[1];
             	args[0] = token;
             	out.writeObject(new BeaconMessage(
						BeaconMessage.AUTHENTICATE_RESPONSE, 
						args, 
						msg.getName(), 
						msg.getAddress(), 
						msg.getPort()));
             	out.flush();
				}
				else {
					out.writeObject(new BeaconMessage(
						BeaconMessage.INVALID_USER, 
						msg.getName(), 
						msg.getAddress(), 
						msg.getPort()));
				}
			}
			catch (IOException ie) {
         	System.out.println(ie);
         	ie.printStackTrace();
         }
		}

		private void handleValidate (BeaconMessage msg, ObjectOutputStream out, ObjectInputStream in) {
			Token token = (Token) msg.getArg(0);
	
			try {
				String name = validateToken(token);
				if (name == null) {
					out.writeObject(new BeaconMessage(
						BeaconMessage.INVALID_TOKEN, 
						msg.getName(), 
						msg.getAddress(), 
						msg.getPort()));
				}
				else if (expired(token)) {
					out.writeObject (new BeaconMessage(
						BeaconMessage.TOKEN_EXPIRED, 
						msg.getName(), 
						msg.getAddress(), 
						msg.getPort()));

					revoke(token);
				}
				else { 	//valid token
					Object[] args = new Object[1];
					args[0] = name;
					out.writeObject(new BeaconMessage(
						BeaconMessage.VALIDATE_RESPONSE, 
						args, 
						msg.getName(), 
						msg.getAddress(), 
						msg.getPort()));
				}
			}
			catch (IOException ie) {
         	System.out.println(ie);
         	ie.printStackTrace();
			}
		}

		private void handleRevoke (BeaconMessage msg, ObjectOutputStream out, ObjectInputStream in) {
			Token tok = (Token) msg.getArg(0);	
			revoke(tok);
		}

		private void revoke (Token tok) {
			tokenList.removeElement(tok);
		}

		private String validateToken (Token tok) {
			synchronized (tokenList) {
				Iterator iter = tokenList.iterator();
				while (iter.hasNext()) {
					Token token = (Token) iter.next();
					if (token.equals(tok))
						return token.getName();
				}
				return null;
			}	
		}
		
		private boolean expired (Token tok) {
			long timeIssued = tok.getTime();
			if ( (timeIssued + (interval * 1000)) < System.currentTimeMillis())
				return true;
			else
				return false;
		}
	} //end MessageHandler class

	public static void main (String[] args) {
		try {
			int port = Integer.parseInt(args[0]);
			int interval = Integer.parseInt(args[1]);

			BeaconKeyServer bks = new BeaconKeyServer (port, interval);
		}
		catch (Exception e) {
			System.out.println ("Usage: java BeaconKeyServer port interval_seconds");
			System.exit(10);
		}
	}
}

