/*
 * Wireless Host Monitor
 *
 * Copyright (C) 2003, Jonathan Sevy <jsevy@mcs.drexel.edu>
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


package airporthostmon;

import java.math.*;
import java.util.*;
import java.io.*;




/**
* 	This class defines a structure to hold information about the location and format (type)
* 	of a piece of information in the Airport memory block. Location is specified by a triple:
*	the object identifier number (1 to 68), row in the block (0 to 15), and column (0 to 15), 
*	where the ByteBlockWindow begins; size of the window is specified by giving the number of
*	rows and columns in the window; and the datatype is specified as one of the class's constants.
*	
*	The class also implements a toString() method which provides a pretty-printed representation
*	of the value in the window based on its datatype, and a setBytesFromString() method which
*	writes a value to the window given an appropriately formatted String representation.
*/

public class AirportInfoRecord
{
	public static final int CHAR_STRING = 0;
	public static final int IP_ADDRESS = 1;
	public static final int BYTE_STRING = 2;
	public static final int PHONE_NUMBER = 3;
	public static final int UNSIGNED_INTEGER = 4;
	public static final int BYTE = 5;
	public static final int LITTLE_ENDIAN_UNSIGNED_INTEGER = 6;
	
	public static final int UNENCRYPTED = 0;
	public static final int ENCRYPTED = 2;
	
	
	
	public String tag;
	
	public String description;
	
	public int dataType;
	
	public int encryption;
	
	public int maxLength;
	
	public byte[] value;
	
	
	
	
	public static byte[] cipherBytes = 	
	{
		(byte)0x0e, (byte)0x39, (byte)0xf8, (byte)0x05, (byte)0xc4, (byte)0x01, (byte)0x55, (byte)0x4f, (byte)0x0c, (byte)0xac, (byte)0x85, (byte)0x7d, (byte)0x86, (byte)0x8a, (byte)0xb5, (byte)0x17, 
		(byte)0x3e, (byte)0x09, (byte)0xc8, (byte)0x35, (byte)0xf4, (byte)0x31, (byte)0x65, (byte)0x7f, (byte)0x3c, (byte)0x9c, (byte)0xb5, (byte)0x6d, (byte)0x96, (byte)0x9a, (byte)0xa5, (byte)0x07, 
		(byte)0x2e, (byte)0x19, (byte)0xd8, (byte)0x25, (byte)0xe4, (byte)0x21, (byte)0x75, (byte)0x6f, (byte)0x2c, (byte)0x8c, (byte)0xa5, (byte)0x9d, (byte)0x66, (byte)0x6a, (byte)0x55, (byte)0xf7, 
		(byte)0xde, (byte)0xe9, (byte)0x28, (byte)0xd5, (byte)0x14, (byte)0xd1, (byte)0x85, (byte)0x9f, (byte)0xdc, (byte)0x7c, (byte)0x55, (byte)0x8d, (byte)0x76, (byte)0x7a, (byte)0x45, (byte)0xe7, 
		(byte)0xce, (byte)0xf9, (byte)0x38, (byte)0xc5, (byte)0x04, (byte)0xc1, (byte)0x95, (byte)0x8f, (byte)0xcc, (byte)0x6c, (byte)0x45, (byte)0xbd, (byte)0x46, (byte)0x4a, (byte)0x75, (byte)0xd7, 
		(byte)0xfe, (byte)0xc9, (byte)0x08, (byte)0xf5, (byte)0x34, (byte)0xf1, (byte)0xa5, (byte)0xbf, (byte)0xfc, (byte)0x5c, (byte)0x75, (byte)0xad, (byte)0x56, (byte)0x5a, (byte)0x65, (byte)0xc7, 
		(byte)0xee, (byte)0xd9, (byte)0x18, (byte)0xe5, (byte)0x24, (byte)0xe1, (byte)0xb5, (byte)0xaf, (byte)0xec, (byte)0x4c, (byte)0x65, (byte)0xdd, (byte)0x26, (byte)0x2a, (byte)0x15, (byte)0xb7, 
		(byte)0x9e, (byte)0xa9, (byte)0x68, (byte)0x95, (byte)0x54, (byte)0x91, (byte)0xc5, (byte)0xdf, (byte)0x9c, (byte)0x3c, (byte)0x15, (byte)0xcd, (byte)0x36, (byte)0x3a, (byte)0x05, (byte)0xa7, 
		(byte)0x8e, (byte)0xb9, (byte)0x78, (byte)0x85, (byte)0x44, (byte)0x81, (byte)0xd5, (byte)0xcf, (byte)0x8c, (byte)0x2c, (byte)0x05, (byte)0xfd, (byte)0x06, (byte)0x0a, (byte)0x35, (byte)0x97, 
		(byte)0xbe, (byte)0x89, (byte)0x48, (byte)0xb5, (byte)0x74, (byte)0xb1, (byte)0xe5, (byte)0xff, (byte)0xbc, (byte)0x1c, (byte)0x35, (byte)0xed, (byte)0x16, (byte)0x1a, (byte)0x25, (byte)0x87, 
		(byte)0xae, (byte)0x99, (byte)0x58, (byte)0xa5, (byte)0x64, (byte)0xa1, (byte)0xf5, (byte)0xef, (byte)0xac, (byte)0x0c, (byte)0x25, (byte)0x1d, (byte)0xe6, (byte)0xea, (byte)0xd5, (byte)0x77, 
		(byte)0x5e, (byte)0x69, (byte)0xa8, (byte)0x55, (byte)0x94, (byte)0x51, (byte)0x05, (byte)0x1f, (byte)0x5c, (byte)0xfc, (byte)0xd5, (byte)0x0d, (byte)0xf6, (byte)0xfa, (byte)0xc5, (byte)0x67, 
		(byte)0x4e, (byte)0x79, (byte)0xb8, (byte)0x45, (byte)0x84, (byte)0x41, (byte)0x15, (byte)0x0f, (byte)0x4c, (byte)0xec, (byte)0xc5, (byte)0x3d, (byte)0xc6, (byte)0xca, (byte)0xf5, (byte)0x57, 
		(byte)0x7e, (byte)0x49, (byte)0x88, (byte)0x75, (byte)0xb4, (byte)0x71, (byte)0x25, (byte)0x3f, (byte)0x7c, (byte)0xdc, (byte)0xf5, (byte)0x2d, (byte)0xd6, (byte)0xda, (byte)0xe5, (byte)0x47, 
		(byte)0x6e, (byte)0x59, (byte)0x98, (byte)0x65, (byte)0xa4, (byte)0x61, (byte)0x35, (byte)0x2f, (byte)0x6c, (byte)0xcc, (byte)0xe5, (byte)0x5d, (byte)0xa6, (byte)0xaa, (byte)0x95, (byte)0x37, 
		(byte)0x1e, (byte)0x29, (byte)0xe8, (byte)0x15, (byte)0xd4, (byte)0x11, (byte)0x45, (byte)0x5f, (byte)0x1c, (byte)0xbc, (byte)0x95, (byte)0x4d, (byte)0xb6, (byte)0xba, (byte)0x85, (byte)0x27
	};
	
	
	/**
	*	Create a new record with the specified parameters
	*/
	
	public AirportInfoRecord(String tag, String description, int dataType, int encryption, int maxLength, byte[] value)
	{
		this.tag = tag;
		this.description = description;
		this.dataType = dataType;
		this.encryption = encryption;
		this.maxLength = maxLength;
		this.value = value;
	}
	
	
	
	public AirportInfoRecord(String tag, String description, int dataType, int encryption, int maxLength)
	{
		this.tag = tag;
		this.description = description;
		this.maxLength = maxLength;
		this.dataType = dataType;
		this.encryption = encryption;
		this.value = new byte[maxLength];
	}
	
	
	public AirportInfoRecord()
	{
		this.tag = "";
		this.description = "";
		this.dataType = CHAR_STRING;
		this.encryption = UNENCRYPTED;
		this.maxLength = 0;
		this.value = new byte[0];
	}
	
	
	
	
	
	
	/**
	*	Clear all bytes in the underlying ByteBlockWindow.
	*/
	
	public void clearWindow()
	{
		this.value = new byte[0];
	}
	
	
	
	
	/**
	*	Method which provides a pretty-printed representation of the value bytes 
	*	based on its datatype.
	*/
	
	public String toString()
	{
		
		String returnString = new String();
		byte[] bytes = value;
		
		
		switch (dataType)
		{
			case UNSIGNED_INTEGER:
			{
				try
				{
					returnString = convertToUnsignedInteger(bytes);
				}
				catch (NumberFormatException e)
				{
					returnString = hexBytes(bytes);
				}
				
				break;
			}
			
			
			case LITTLE_ENDIAN_UNSIGNED_INTEGER:
			{
				try
				{
					bytes = reverseBytes(bytes);
					
					returnString = convertToUnsignedInteger(bytes);
					 
				}
				catch (NumberFormatException e)
				{
					returnString = hexBytes(bytes);
				}
				
				break;
			}
			
			
			case CHAR_STRING:
			case PHONE_NUMBER:
			{
				
				returnString = convertToCharString(bytes);
				break;
			}
			
			case IP_ADDRESS:
			{
				returnString = convertToIPAddress(bytes);
				break;
			}
	
			
			case BYTE:
			case BYTE_STRING:
			default:
			{
				returnString = hexBytes(bytes);
				break;
			}
		}
		
		return returnString;
	}
	
	
	
	
	
	private String convertToCharString(byte[] bytes)
	{
		String charString;
		
		if (bytes.length == 0)
		{
			charString = "";
		}
		else
		{
			charString = new String(bytes);
			
			// trim 0's
			int endIndex = charString.indexOf('\0');
			
			if (endIndex >= 0)
				charString = charString.substring(0, endIndex);
		}
		
		return charString;
	}
	
	
	
	private String convertToUnsignedInteger(byte[] bytes)
	{
		BigInteger bigInt = new BigInteger(1, bytes);
		return bigInt.toString();
	}
	
	
	
	private String convertToIPAddress(byte[] bytes)
	{
		String returnString = new String();
		int value;
		
		for (int i = 0; i < bytes.length - 1; i++)
		{
			value = bytes[i];
			if (value < 0)
				value += 256;
			returnString += value + ".";
			
		}
		
		value = bytes[bytes.length - 1];
		if (value < 0)
			value += 256;
		returnString += value;
		
		return returnString;
	}
	
	
	
	
	/**
	*	Writes value bytes given an appropriately formatted String representation for
	*	the value.
	*/
	
	public void setBytesFromString(String valueString)
		throws ValueFormatException
	{
		
		byte[] bytes;
		
			
		switch (dataType)
		{
			
			case UNSIGNED_INTEGER:
			{
				bytes = convertFromUnsignedInteger(valueString);
				break;
			}
			
			case LITTLE_ENDIAN_UNSIGNED_INTEGER:
			{
				bytes = convertFromUnsignedInteger(valueString);
				bytes = reverseBytes(bytes);
				
				break;
			}
			
			case CHAR_STRING:
			case PHONE_NUMBER:
			{
				
				if (valueString.length() > maxLength - 1)
				{
					// System.out.println("Value format exception at " + OIDNum + " " + OIDRow + " " + OIDCol);
					throw new ValueFormatException("Maximum " + (maxLength - 1) + " characters.");
				}
				else
					bytes = valueString.getBytes();
					
				break;
			}
			
			case IP_ADDRESS:
			{
				bytes = convertFromIPv4Address(valueString);
				break;
			}
	
			
			case BYTE:
			case BYTE_STRING:
			default:
			{
				bytes = convertFromHexString(valueString);	
				break;
			}
		}
		
		
		this.value = bytes;
		
		
	}
	
	
	
	
	private byte[] convertFromIPv4Address(String addressString)
		throws ValueFormatException
	{
		// might be partial address
		byte[] bytes = new byte[maxLength];
		
		int i = 0;
		int value;
		StringTokenizer st = new StringTokenizer(addressString, ".");
		if (st.countTokens() != maxLength)
		{
			if (st.countTokens() == 4)
				throw new ValueFormatException("Bad IP address: must be of form a.b.c.d, with a,b,c and d between 0 and 255.");
			else
				throw new ValueFormatException("Bad dotted address supplied: should have " + maxLength + " components.");
		}
		
		while (st.hasMoreTokens())
		{
			try
			{
				String component = st.nextToken();
				value = Integer.parseInt(component);
				if ((value < 0) || (value > 255))
					throw new ValueFormatException("Bad IP address: must be of form a.b.c.d, with a,b,c and d between 0 and 255.");
				else
				{
					bytes[i] = (byte)value;
					i++;
				}
			}
			catch (NumberFormatException e)
			{
				throw new ValueFormatException("Bad IP address: must be of form a.b.c.d, with a,b,c and d between 0 and 255.");
			}
			
		}
		
		return bytes;
	}
	
	
	
	
	
		
	private byte[] convertFromUnsignedInteger(String valueString)
		throws ValueFormatException
	{
		int length = maxLength;
		byte[] bytes = new byte[length];
		
		try
		{
			int minValue = 0;
			long maxValue = 1;
			for (int i = 0; i < length; i++)
			{
				maxValue *= 256;
			}
			maxValue -= 1;
			
			long value = Long.parseLong(valueString);
			
			if ((value < minValue) || (value > maxValue))
				 throw new ValueFormatException("Value must be between " + minValue + " and " + maxValue + ".");
			
			for (int i = 0; i < length; i++)
			{
				bytes[length - i - 1] = (byte)(value%256);
				value = value/256;
			}
		}
		catch (NumberFormatException e)
		{
			throw new ValueFormatException("Bad number format.");
		}

		return bytes;
	}
	
	
	
	
	
	private byte[] convertFromHexString(String hexString)
		throws ValueFormatException
	{
		byte[] bytes = new byte[this.maxLength];
		
		// eliminate spaces in string
		hexString.trim();
		int index;
		while((index = hexString.indexOf(' ')) != -1)
		{
			hexString = hexString.substring(0,index) + hexString.substring(index+1);
		}
		
		// make sure have even number of hex digits
		if (2 * (hexString.length()/2) != hexString.length())
			throw new ValueFormatException("Must have an even number of hexadecimal digits.");
		
		// make sure don't have wrong number of bytes
		if ((this.maxLength > 0) && (hexString.length() / 2 != maxLength))
			throw new ValueFormatException("Too many hexadecimal digits (must have " + 2*maxLength + " hex digits).");
		
		for (int i = 0; i < (hexString.length()/2); i++)
		{
			// get next pair of digits
			String digitString = hexString.substring(2*i,2*i+2);
			
			try
			{
				int value = Integer.parseInt(digitString, 16);	
				bytes[i] = (byte)value;
			}
			catch (NumberFormatException e)
			{
				throw new ValueFormatException("Entries must be hexadecimal digits (0 through 9 and a through f or A through F) or spaces.");
			}
			
		}
		
		return bytes;
	}
	
	
	
	
	private byte[] reverseBytes(byte[] inBytes)
	{
		int length = inBytes.length;
		byte[] outBytes = new byte[length];
		
		for (int i = 0; i < length; i++)
		{
			outBytes[i] = inBytes[length - i - 1];
		}
		
		return outBytes;
	}
	
	
	
	public byte[] getValue()
	{	
		return value;
	}
	
	
	
	public void setValue(byte[] bytes)
	{
		// just set value array
		
		value = bytes;
		
	}
	
	
	
	
	public byte[] getUpdateBytes()
	{
		// serialize all the fields
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		DataOutputStream outStream = new DataOutputStream(byteStream);
		
		try
		{
			outStream.writeBytes(tag);
			outStream.writeInt(encryption);
			outStream.writeInt(value.length);
			
			if (value.length > 0)
			{
				// encrypt bytes if needed
				if (this.encryption == ENCRYPTED)
				{
					outStream.write(encryptBytes(cipherBytes, value));
				}
				else
				{
					outStream.write(value);
				}
			}
				
		}
		catch(IOException e)
		{
			// IOException should never occur...
		}
		
		return byteStream.toByteArray();
	}
	
	
	
	public byte[] getRequestBytes()
	{
		// serialize all the fields
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
		DataOutputStream outStream = new DataOutputStream(byteStream);
		
		try
		{
			outStream.writeBytes(tag);
			outStream.writeInt(encryption);
			outStream.writeInt(0);
		}
		catch(IOException e)
		{
			// IOException should never occur...
		}
		
		return byteStream.toByteArray();
	}
	
	
	
	public static byte[] decryptBytes(byte[] cipherString, byte[] encryptedString)
	{
		byte[] returnBytes = new byte[encryptedString.length];
		
		// just xor each byte in encryptedString with cipherString
		int length = encryptedString.length;
		
		for (int i = 0; i < length; i++)
		{
			returnBytes[i] = (byte)(encryptedString[i] ^ cipherString[i%256]);
		}
		
		return returnBytes;
	}
	
	
	
	public static byte[] encryptBytes(byte[] cipherString, byte[] encryptedString)
	{
		return decryptBytes(cipherString, encryptedString);
	}
	
	
	
	
	
	
	private int getIntegerValue(byte[] valueBytes)
	{
		int value = 0;
		
		for (int i = 0; i < valueBytes.length; i++)
		{
			int absValue = valueBytes[i];
			
			if (absValue < 0)
				absValue += 256;
			
			value = value*256 + absValue;
		}
		
		return value;
	}
	
	
	
	private String hexByte(byte b)
	{
		int pos = b;
		if (pos < 0)
			pos += 256;
		String returnString = new String();
		returnString += Integer.toHexString(pos/16);
		returnString += Integer.toHexString(pos%16);
		return returnString;
	}
	
	
	
	private String hexBytes(byte[] bytes)
	{
		String returnString = new String();
		
		for(int i = 0; i < bytes.length; i++)
		{
			returnString += hexByte(bytes[i]);
		}
		
		return returnString;
		
	}

	
	

}