package madynes.flowoid.exporter;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import madynes.flowoid.exporter.Exportable.TemplateMismatchException;
import madynes.flowoid.exporter.TemplateField.InadequateLengthException;
import madynes.flowoid.exporter.TemplateFlowSet.TemplateNotFoundException;
import madynes.flowoid.exporter.TemplateRecord.AlreadyUsedIDException;
import madynes.flowoid.exporter.TemplateRecord.InadequateIDException;
import android.app.Activity;
import android.app.IntentService;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.StrictMode;
import android.util.Log;
import android.widget.Toast;

public abstract class ExporterService extends IntentService {
	
	// Preferences
	private static int prefDriversPort;
	private static int prefCollectorPort;
	private static String prefCollectorAddr;
	private static int prefNetflowTimeout;
	private static int prefMaxHeadersSize;
	private static int prefMaxExportPacketSize;
	private static int prefMinRecordsByFlowset;
	private static int prefSendTemplatesEveryNPackets;
	private static int prefCheckToExportInterval;
	private static int prefCheckDriversInterval;
	private static int prefMaxNetflowsToTrack;
	private static int prefPacketTimeout;
	private static int prefTimeBeforeNetFlowEnd;
	
	// Statistics
	private static Integer statSniffedPackets = 0;
	private static Integer statRecognizedSniffedPackets = 0;
	private static Integer statFailedSniffedPackets = 0;
	private static Integer statExportedNetFlows = 0;
	private static Integer statRejectedNetFlows = 0;
	private static Integer statConnectedDrivers = 0;
	private static Integer statActiveNetflows = 0;
	private static Integer statNetflowsWaitingToBeExported = 0;
	
	// Miscellaneous
	private static boolean isRunning, isForcedExport;
	private static Intent intent;
	private HashMap<Object, DataRecord> records;
	private ArrayList<DataRecord> exportQueue;
	private TemplateFlowSet templateFlowSet;
	private Date exportPacketExpirationTime;
	private ServerSocket exporterSocket;
	private static HandlerThread exportThread;
	private static Handler exportHandler;
	private static Runnable exportRun;
	
	public static class HeaderTooBigException extends Exception {
		private static final long serialVersionUID = 1L;
		private final String message = "Packet header too big.";

		public String getMessage() {
			return message;
		}
	}
	
	/**
	 * Creates an Android Service.
	 * 
	 * @param activity an Android activity
	 */
	public static void start(Activity activity, Class<?> exporterImpl) {
		intent = new Intent(activity, exporterImpl);
		activity.startService(intent);

		Log.d("EXPORTER", "Service started");
	}
	
	/**
	 * Return the intent corresponding to the service.
	 * 
	 * @return current intent
	 */
	public static Intent getIntent() {
		return intent;
	}
	
	/**
	 * Defines the main class of the exporter in charge of sending export
	 * packets to the remote collector.
	 */
	public ExporterService() {
		super("ExporterService");

		records = new HashMap<Object, DataRecord>();
		templateFlowSet = new TemplateFlowSet();
		exportQueue = new ArrayList<DataRecord>();
	}
	
	@Override
	protected void onHandleIntent(Intent intent) {
		try {
			isRunning = true;
			isForcedExport = false;
			onStart();
			createExportTimer();
			registerTemplates();
			listenDrivers();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		
		Log.d("EXPORTER", "Stop service");
		
		try {
			isRunning = false;
			exporterSocket.close();
			exportHandler.removeCallbacks(exportRun);
			exportThread.quit();
			exportHandler = null;
			Driver.flushUsedDrivers();
			purgeStatsTrackedNetflows();
			onStop();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Accepts drivers connections and create each time a new thread in
	 * charge of transmitting sniffed packets headers to filtering function.
	 */
	private void listenDrivers() {
		try {
			exporterSocket = new ServerSocket(prefDriversPort);

			while(true) {
				final Socket driverSocket = exporterSocket.accept();
				
				synchronized (statConnectedDrivers) {
					statConnectedDrivers++;	
				}
				
				Log.d("EXPORTER", "Accept new driver");
				Toast.makeText(getApplicationContext(), "New Driver connected", Toast.LENGTH_SHORT).show();

				new Thread() {
					@Override
					public void run() {
						Looper.prepare();
						
						try {
							byte[] headers = new byte[prefMaxHeadersSize];
							ByteBuffer buffer;
							
							DataInputStream driverStream = new DataInputStream(driverSocket.getInputStream());

							while(true) {
								if(driverStream.read(headers, 0, 2) != 2) {
									break;
								}

								buffer = ByteBuffer.allocate(2);
								buffer.order(ByteOrder.LITTLE_ENDIAN);
								buffer.put(headers, 0, 2);

								short headersSize = buffer.getShort(0);

								if(headersSize > prefMaxHeadersSize) {
									throw new HeaderTooBigException();
								}

								driverStream.read(headers, 0, headersSize);
								
								synchronized (statSniffedPackets) {
									statSniffedPackets++;
								}

								if(!ExporterService.this.processPacketFiltering(headers)) {
									synchronized (statFailedSniffedPackets) {
										statFailedSniffedPackets++;
									}
								}
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
						
						synchronized (statConnectedDrivers) {
							statConnectedDrivers--;
						}
						
						Log.d("EXPORTER", "Driver left");
						Toast.makeText(getApplicationContext(), "Driver left", Toast.LENGTH_SHORT).show();
					}
				}.start();
			}
		} catch(SocketException e) {
			if(isRunning) {
				e.printStackTrace();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Registers a template record in order to have access to it when the
	 * data records will be created.
	 * 
	 * @param id arbitrary key associated to this template to retrieve it later
	 * @param templateRecord template record to register
	 */
	public void registerTemplateRecord(TemplateRecord templateRecord) {
		templateFlowSet.registerTemplateRecord(templateRecord.getTemplateID(), templateRecord);
	}
	
	/**
	 * Returns a template record previously registered.
	 * 
	 * @param id arbitrary key
	 * @return corresponding template
	 * @throws TemplateNotFoundException launched if this template never was registered
	 */
	public TemplateRecord getRegistredTemplateRecord(int id) throws TemplateNotFoundException {
		return templateFlowSet.getRegistredTemplateRecord(id);
	}
	
	/**
	 * Adds a new sniffed packet to an existent NetFlow/record or creates a new one. 
	 * 
	 * @param record record corresponding to this sniffed packet, partially initialized.
	 * See processPacketFiltering(byte[])
	 * @param headers raw packet headers sniffed
	 */
	public void processPacket(DataRecord record, byte[] headers) {
		record.initNetFlowKeyPartFields(headers);
		
		Object recordID = record.getRecordID(record.getTemplate().getTemplateID());

		synchronized (records) {
			if(records.containsKey(recordID)) {
				record = records.get(recordID);
				record.updateFields(headers);
				
			} else if((records.size() + exportQueue.size()) <= prefMaxNetflowsToTrack) {
				record.initNoNetFlowKeyPartFields(headers);
				records.put(recordID, record);
				
				synchronized (statActiveNetflows) {
					statActiveNetflows++;	
				}

				Log.d("DEBUG", "New record");
				
			} else {
				synchronized (statRejectedNetFlows) {
					statRejectedNetFlows++;
				}
				
				Log.d("DEBUG", "New record rejected");
			}
			
			if(!record.isFlaggedEnded()) {
				record.setLastHeaders(headers);
			
				if(record.isEnded(headers)) {
					record.flagAsEnded();
					record.forceExpiration(prefTimeBeforeNetFlowEnd);
					
					Log.d("DEBUG", "Record ready to be exported");
				}
			}
		}
		
		synchronized (statRecognizedSniffedPackets) {
			statRecognizedSniffedPackets++;
		}
	}
	
	/**
	 * Forces the export of all current records.
	 */
	public static void forceExport() {
		if(exportRun != null) {
			isForcedExport = true;
			exportRun.run();
			isForcedExport = false;
		}
	}
	
	/**
	 * Creates the thread in charge of checking records at regular
	 * times, asking the sending of the export queue and sometimes
	 * adding some expired records into it.
	 */
	private void createExportTimer() {
		if(exportHandler != null) {
			exportHandler.removeCallbacks(exportRun);
			exportThread.quit();
			exportHandler = null;
		}
		
		exportThread = new HandlerThread("ExportThread");
		exportThread.start();
		exportHandler = new Handler(exportThread.getLooper());
		
		exportRun = new Runnable() {
			@Override
			public void run() {
				Date date = new Date();
				
				StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
				StrictMode.setThreadPolicy(policy);
				
				Log.d("THREAD", "Checking for export");

				synchronized (exportQueue) {
					if(!records.isEmpty()) {
						ArrayList<Object> toExportQueue = new ArrayList<Object>();

						synchronized (records) {
							for(Object recordID : records.keySet()) {
								DataRecord record = records.get(recordID);

								if(date.after(record.getExpirationTime()) || isForcedExport) {
									toExportQueue.add(recordID);
								}
							}
						}

						for(Object recordID : toExportQueue) {
							addToExportQueue(recordID);
						}
					}

					if(!exportQueue.isEmpty()) {
						if(exportQueue.size() >= prefMinRecordsByFlowset || date.after(exportPacketExpirationTime) || isForcedExport) {
							try {
								export();
								
							} catch (TemplateMismatchException e) {
								e.printStackTrace();
							}
						}
					}
				}
				
				if(exportHandler != null) {
					exportHandler.postDelayed(this, prefCheckToExportInterval * 1000);
				}
			}
		};
		
		exportHandler.post(exportRun);
	}
	
	/**
	 * Adds an ended NetFlow/record to the export queue. When the minimum number
	 * of records for an export is met, the queue is emptied and records are transmitted
	 *  to the remote collector inside an export packet.
	 *  
	 * @param recordID record ID corresponding to the data record to add, from the records
	 * hash table to the export queue
	 */
	public void addToExportQueue(Object recordID) {
		synchronized (exportQueue) {
			if(exportQueue.isEmpty())
				exportPacketExpirationTime = new Date(new Date().getTime() + getPrefPacketTimeout() * 1000);
	
			synchronized (records) {
				exportQueue.add(records.get(recordID));
				records.remove(recordID);				
			}
			
			synchronized (statActiveNetflows) {
				statActiveNetflows--;
			}
			
			synchronized (statNetflowsWaitingToBeExported) {
				statNetflowsWaitingToBeExported++;
			}
			
			Log.d("DEBUG", "Record added to export queue");
		}
	}
	
	/**
	 * Creates the needed data FlowSet and sometimes the template FlowSet.
	 * Creates an export packet with all of the records of the export queue.
	 * Sends it to the remote collector.
	 * 
	 * @throws TemplateMismatchException launched if a field returns a value's size
	 * that not corresponding to its associated template definition
	 */
	public void export() throws TemplateMismatchException {
		try {
			DatagramSocket collector = new DatagramSocket();
			InetAddress address = InetAddress.getByName(prefCollectorAddr);
			ExportPacket exportPacket;
			int lastPacketExportedRecords;
			int totalExportedRecords = 0;
			boolean isTemplateNetFlowSent = false;
			
			synchronized (exportQueue) {

				while(exportQueue.size() > 0) {
					byte[] export = null;
					exportPacket = new ExportPacket(getContentResolver());
					lastPacketExportedRecords = 0;
					
					if(((prefSendTemplatesEveryNPackets != 0 && getStatExportedPackets() % prefSendTemplatesEveryNPackets == 0) || isForcedExport) && !isTemplateNetFlowSent) {
						exportPacket.setTemplateFlowSet(templateFlowSet);
						isTemplateNetFlowSent = true;
					}

					for(DataRecord record : exportQueue) {
						short templateID = record.getTemplate().getTemplateID();

						if(exportPacket.containsDataFlowSet(templateID)) {
							exportPacket.getDataFlowSet(templateID).addDataRecord(record);
						} else {
							DataFlowSet flowSet = new DataFlowSet(record.getTemplate());
							flowSet.addDataRecord(record);
							exportPacket.addDataFlowSet(flowSet);
						}

						record.exportFields();
						lastPacketExportedRecords++;

						export = exportPacket.toPrimitiveBytes();
						
						if(export.length >= prefMaxExportPacketSize) {
							break;
						}
					}

					DatagramPacket packet = new DatagramPacket(export, export.length, address, prefCollectorPort);
					collector.send(packet);

					ExportPacket.incSequenceNumber();
					totalExportedRecords += lastPacketExportedRecords;
					
					Log.d("DEBUG", "Export Packet Size: " + export.length);
					
					for(int i = 0; i < lastPacketExportedRecords; i++) {
						exportQueue.remove(0);
					}
				}

				synchronized (statExportedNetFlows) {
					statExportedNetFlows += totalExportedRecords;
				}

				synchronized (statNetflowsWaitingToBeExported) {
					statNetflowsWaitingToBeExported = 0;
				}

				exportQueue.clear();
			}

		} catch(IOException e) {
			Log.i("NETWORK", "Temporary unreachable. Cannot send Export Packets.");
		}
	}
	
	/**
	 * Purges some stats. Useful when service is disabled.
	 */
	public static void purgeStatsTrackedNetflows() {
		synchronized (statActiveNetflows) {
			statActiveNetflows = 0;
		}
		
		synchronized (statNetflowsWaitingToBeExported) {
			statNetflowsWaitingToBeExported = 0;
		}
	}
	
	/**
	 * Get the current Exporter Service state.
	 * Used by threads to detect deprecated instances after a stopService.
	 * 
	 * @return current Exporter Service state
	 */
	public static boolean isRunning() {
		return isRunning;
	}
	
	/**
	 * Returns TCP port to listen on for receiving packets headers from drivers.
	 * 
	 * @return TCP port
	 */
	public static int getPrefDriversPort() {
		return prefDriversPort;
	}

	/**
	 * Sets TCP port to listen on for receiving packets headers from drivers.
	 * 
	 * @param driversPort TCP port to listen on
	 */
	public static void setPrefDriversPort(int driversPort) {
		ExporterService.prefDriversPort = driversPort;
	}

	/**
	 * Returns UDP port to connect to for sending NetFlow to the remote Collector.
	 * 
	 * @return UDP port
	 */
	public static int getPrefCollectorPort() {
		return prefCollectorPort;
	}

	/**
	 * Sets UDP port to connect to for sending NetFlow to the remote Collector.
	 * 
	 * @param collectorPort UDP port to connect to
	 */
	public static void setPrefCollectorPort(int collectorPort) {
		ExporterService.prefCollectorPort = collectorPort;
	}

	/**
	 * Returns Collector address (IP address or hostname).
	 * 
	 * @return Collector address
	 */
	public static String getPrefCollectorAddr() {
		return prefCollectorAddr;
	}

	/**
	 * Sets Collector address (IP address or hostname).
	 * 
	 * @param collectorAddr Collector address
	 */
	public static void setPrefCollectorAddr(String collectorAddr) {
		ExporterService.prefCollectorAddr = collectorAddr;
	}

	/**
	 * Returns maximum headers size in bytes authorized for a sniffed packet transmitted by a Driver.
	 * 
	 * @return maximum headers size authorized for a sniffed packet
	 */
	public static int getPrefMaxHeadersSize() {
		return prefMaxHeadersSize;
	}

	/**
	 * Sets maximum headers size in bytes authorized for a sniffed packet transmitted by a Driver.
	 * 
	 * @param maxHeadersSize maximum headers size authorized for a sniffed packet
	 */
	public static void setPrefMaxHeadersSize(int maxHeadersSize) {
		ExporterService.prefMaxHeadersSize = maxHeadersSize;
	}

	/**
	 * Returns minimum records by FlowSet.
	 * 
	 * @return minimum records by FlowSet
	 */
	public static int getPrefMinRecordsByFlowset() {
		return prefMinRecordsByFlowset;
	}

	/**
	 * Sets minimum records by FlowSet.
	 * 
	 * @param minRecordsByFlowset minimum records by FlowSet
	 */
	public static void setPrefMinRecordsByFlowset(int minRecordsByFlowset) {
		ExporterService.prefMinRecordsByFlowset = minRecordsByFlowset;
	}

	/**
	 * Returns the minimum number of Export Packet to send before send
	 * the Template FlowSet. 
	 * 
	 * @return minimum number of Export Packet to send before send templates
	 */
	public static int getPrefSendTemplatesEveryNPackets() {
		return prefSendTemplatesEveryNPackets;
	}

	/**
	 * Sets the minimum number of Export Packet to send before send
	 * the Template FlowSet. 
	 * 
	 * @param sendTemplatesEveryNPackets minimum number of Export Packet to
	 * send before send templates
	 */
	public static void setPrefSendTemplatesEveryNPackets(int sendTemplatesEveryNPackets) {
		ExporterService.prefSendTemplatesEveryNPackets = sendTemplatesEveryNPackets;
	}

	/**
	 * Returns Export Thread check interval in seconds.
	 * 
	 * @return Export Thread check interval 
	 */
	public static int getPrefCheckToExportInterval() {
		return prefCheckToExportInterval;
	}

	/**
	 * Sets Export Thread check interval.
	 * 
	 * @param checkToExportInterval Thread check interval in seconds
	 */
	public static void setPrefCheckToExportInterval(int checkToExportInterval) {
		ExporterService.prefCheckToExportInterval = checkToExportInterval;
	}
	
	/**
	 * Returns the drivers working check interval in seconds.
	 * 
	 * @return drivers working check interval in seconds
	 */
	public static int getPrefCheckDriversInterval() {
		return prefCheckDriversInterval;
	}

	/**
	 * Sets the drivers working check interval.
	 * 
	 * @param checkToExportInterval working check interval in seconds
	 */
	public static void setPrefCheckDriversInterval(int checkDriversInterval) {
		ExporterService.prefCheckDriversInterval = checkDriversInterval;
	}

	/**
	 * Returns the number in seconds after which a record is considered ended
	 * and is added to the Export Queue. 
	 * 
	 * @return NetFlow timeout in seconds
	 */
	public static int getPrefNetflowTimeout() {
		return prefNetflowTimeout;
	}

	/**
	 * Sets the number in seconds after which a record is considered ended
	 * and is added to the Export Queue. 
	 * 
	 * @param netflowTimeout timeout in seconds
	 */
	public static void setPrefNetflowTimeout(int netflowTimeout) {
		ExporterService.prefNetflowTimeout = netflowTimeout;
	}

	/**
	 * Returns the number in seconds after which the Export Queue must be emptied.
	 * 
	 * @return timeout in seconds
	 */
	public static int getPrefPacketTimeout() {
		return prefPacketTimeout;
	}

	/**
	 * Sets the number in seconds after which the Export Queue must be emptied.
	 * 
	 * @return timeout in seconds
	 */
	public static void setPrefPacketTimeout(int packetTimeout) {
		ExporterService.prefPacketTimeout = packetTimeout;
	}
	
	/**
	 * Returns the maximum number of NetFlows to track simultaneous.
	 * 
	 * @return maximum number of NetFlows to track
	 */
	public static int getPrefMaxNetflowsToTrack() {
		return prefMaxNetflowsToTrack;
	}
	
	/**
	 * Sets the maximum number of NetFlows to track simultaneous.
	 * 
	 * @return packetTimeout maximum number of NetFlows to track
	 */
	public static void setPrefMaxNetflowsToTrack(int maxNetflowsToTrack) {
		ExporterService.prefMaxNetflowsToTrack = maxNetflowsToTrack;
	}
	
	/**
	 * Returns the life time remaining when a NetFlow is detected as ended.
	 * This is useful for the last ACK with TCP three-way handshake.
	 * 
	 * @return time remaining to life when tagged as ended
	 */
	public static int getPrefTimeBeforeNetFlowEnd() {
		return prefTimeBeforeNetFlowEnd;
	}
	
	/**
	 * Sets the life time remaining when a NetFlow is detected as ended.
	 * 
	 * @return prefTimeBeforeNetFlowEnd time remaining to life when tagged as ended
	 */
	public static void setPrefTimeBeforeNetFlowEnd(int prefTimeBeforeNetFlowEnd) {
		ExporterService.prefTimeBeforeNetFlowEnd = prefTimeBeforeNetFlowEnd;
	}
	
	/**
	 * Returns the export packet max size.
	 * 
	 * @return export packet max size in bytes
	 */
	public static int getPrefMaxExportPacketSize() {
		return prefMaxExportPacketSize;
	}
	
	/**
	 * Sets the export packet max size.
	 * 
	 * @return prefMaxExportPacketSize export packet max size in bytes
	 */
	public static void setPrefMaxExportPacketSize(int prefMaxExportPacketSize) {
		ExporterService.prefMaxExportPacketSize = prefMaxExportPacketSize;
	}
	
	/**
	 * Returns the number of packets sent by all drivers.
	 * 
	 * @return number of sniffed packets
	 */
	public static int getStatSniffedPackets() {
		return statSniffedPackets;
	}

	/**
	 * Returns the number of packets sent by all drivers
	 * recognized by at least one parser at the filtering step.
	 * 
	 * @return number of recognized sniffed packets
	 */
	public static int getStatRecognizedSniffedPackets() {
		return statRecognizedSniffedPackets;
	}
	
	/**
	 * Returns the number of packets sent by all drivers
	 * unrecognized by any parser at the filtering step.
	 * 
	 * @return number of unrecognized sniffed packets
	 */
	public static int getStatUnrecognizedSniffedPackets() {
		return statSniffedPackets - statFailedSniffedPackets - statRecognizedSniffedPackets;
	}

	/**
	 * Returns the number of packets sent by all drivers with which
	 * a parser error has occurred at the filtering step.
	 * 
	 * @return number of failed sniffed packets
	 */
	public static int getStatFailedSniffedPackets() {
		return statFailedSniffedPackets;
	}

	/**
	 * Returns the number of Export Packets sent by the Exporter to the
	 * Collector.
	 * 
	 * @return number of Export Packets sent
	 */
	public static int getStatExportedPackets() {
		return ExportPacket.getSequenceNumber();
	}

	/**
	 * Returns the number of NetFlow/records sent by the Exporter to the
	 * Collector.
	 * 
	 * @return number of NetFlows sent
	 */
	public static int getStatExportedNetFlows() {
		return statExportedNetFlows;
	}
	
	/**
	 * Returns the number of NetFlow/records rejected due to the prefMaxNetflowsToTrack
	 * preference. 
	 * 
	 * @return number of NetFlows sent
	 */
	public static int getStatRejectedNetFlows() {
		return statRejectedNetFlows;
	}
		
	/**
	 * Returns the number of currently connected Drivers.
	 * 
	 * @return number of connected drivers
	 */
	public static int getStatConnectedDrivers() {
		return statConnectedDrivers;
	}
	
	/**
	 * Returns the number of currently active NetFlows/records.
	 * 
	 * @return number of active NetFlows
	 */
	public static int getStatActiveNetflows() {
		return statActiveNetflows;
	}
	
	/**
	 * Returns the number of NetFlows currently waiting to be exported.
	 * 
	 * @return export queue size
	 */
	public static int getStatNetflowsWaitingToBeExported() {
		return statNetflowsWaitingToBeExported;
	}
	
	/**
	 * Allows the implementation class to call code on startup.
	 */
	abstract public void onStart();
	
	/**
	 * Allows the implementation class to call code on stop.
	 */
	abstract public void onStop();
	
	/**
	 * Defines NetFlow v9 (record) templates.
	 * 
	 * @throws AlreadyUsedIDException a record template with this ID was already defined
	 * @throws InadequateIDException the proposed ID doesn't respect the RFC constraints
	 * @throws InadequateLengthException the length of a template field doesn't respect the RFC constraints  
	 */
	abstract public void registerTemplates() throws AlreadyUsedIDException, InadequateIDException, InadequateLengthException;
	
	/**
	 * Process every sniffed packet headers sent by any driver and associates to a
	 * NetFlow/record template.
	 * 
	 * @param headers raw packet headers sniffed
	 * @return True if there was a filtering error
	 * @throws TemplateNotFoundException the record templates registry of the exporter was asked
	 * with an unknown key
	 */
	abstract public boolean processPacketFiltering(byte[] headers) throws TemplateNotFoundException;
}