/**
* Copyright (C) 2007 Sly Technologies, Inc. This library is free software; you
* can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version. This
* library 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 Lesser General Public License for more
* details. You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.slytechs.jnetstream.livecapture;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jnetstream.capture.CaptureType;
import org.jnetstream.capture.LiveCapture;
import org.jnetstream.capture.LiveCaptureDevice;
import org.jnetstream.capture.LiveIterator;
import org.jnetstream.capture.LivePacket;
import org.jnetstream.filter.Filter;
import org.jnetstream.packet.ProtocolFilterTarget;
import com.slytechs.utils.collection.IOIterator.IteratorAdapter;
import com.slytechs.utils.io.IORuntimeException;
/**
* @author Mark Bednarczyk
* @author Sly Technologies, Inc.
*/
public abstract class AbstractLiveCapture implements LiveSource,
LiveDispatchable, LiveCapture {
protected static final Log logger = LogFactory.getLog(LiveCapture.class);
private static final AtomicReference<IOException> ioError =
new AtomicReference<IOException>();
private static final String DISPATCHED = "DISPATCH";
protected int count;
protected int dispatched = 0;
protected final LiveCaptureDevice[] devices;
protected LiveClient client;
protected Filter<ProtocolFilterTarget> filter;
private boolean openState = true;
private LiveIterator packetIterator;
protected boolean promiscuous;
protected int snaplen;
protected int timeout;
/**
* Currently active worker thread. If null, no thread is running
*/
@SuppressWarnings("unused")
private volatile Thread[] workers;
/**
* @param captureDevice
* @param captureCount
* @param snaplen
* @param promiscuous
* @param timeout
* @param filter
*/
public AbstractLiveCapture(final LiveCaptureDevice[] captureDevice,
int captureCount, int snaplen, boolean promiscuous, int timeout,
Filter<ProtocolFilterTarget> filter) {
this.devices = captureDevice;
this.count = captureCount;
this.snaplen = snaplen;
this.promiscuous = promiscuous;
this.timeout = timeout;
this.filter = filter;
}
/**
* Tells the open implementation dependent capture object to capture count
* number of packets and then return.
*
* @param count
* number of packets to capture and return or 0 for infinate
* @param index
* TODO
* @throws IOException
* any IO errors during the capture
*/
protected abstract void capture(int count, int index) throws IOException;
/**
* Flags the capture closed and waits for all the workers to exit
*/
public void close() throws IOException {
this.openState = false;
for (Thread worker : workers) {
if (worker == null) {
continue;
}
try {
worker.join();
} catch (InterruptedException e) {
break;
}
}
client.getPacketQueue().clear();
}
/**
* Dispatches a packet that is ready to clients. A concurrent blocking queue
* is used to pass packets between this server thread and clients. Each client
* maintains its own queue through which the packet is exchanged. If
* particular clients queue is full, the packet is dropped and the appropriate
* counter incremented to indicate a drop, on this particular client's queue.
*/
public void dispatch(final LivePacket packet) {
if (client == null) {
return; // Nothing to do, ignore any packets received
}
if (logger.isTraceEnabled()) {
logger.trace("Received packet");
}
final BlockingQueue<LivePacket> queue = client.getPacketQueue();
if (queue.remainingCapacity() == 0) {
client.incrementDropCounter(1);
} else {
queue.offer(packet);
}
synchronized (DISPATCHED) {
this.dispatched++;
}
}
public Filter<ProtocolFilterTarget> getFilter() {
return filter;
}
/**
* Gets the current pending IO exception which needs to be reported
*
* @return IO exception which needs to be reported.
*/
public IOException getIOException() {
return ioError.get();
}
public LiveCaptureDevice[] getOpenCaptureDevices() {
return devices;
}
public LiveIterator getPacketIterator() throws IOException {
return getPacketIterator(count);
}
public LiveIterator getPacketIterator(int count) throws IOException {
if (packetIterator == null) {
this.packetIterator = new LiveIteratorImpl(this);
this.client = (LiveClient) packetIterator;
/*
* Start the capture in the background
*/
start(count);
}
return packetIterator;
}
public int getSnaplen() {
return snaplen;
}
public int getTimeout() {
return timeout;
}
public CaptureType getType() {
return CaptureType.LiveCapture;
}
/**
* Checks if there is a pending IO exception to be reported
*
* @return true means that there is a pending exception, otherwise false
*/
public boolean hasIOException() {
return ioError.get() != null;
}
public boolean isMutable() {
return false;
}
public boolean isOpen() throws IOException {
return openState;
}
public boolean isPromisuous() {
return promiscuous;
}
public boolean isRunning() {
synchronized (DISPATCHED) {
return workers != null && (count == 0 || dispatched < count);
}
}
public Iterator<LivePacket> iterator() {
try {
return new IteratorAdapter<LivePacket>(getPacketIterator());
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
public void setFilter(Filter<ProtocolFilterTarget> filter) throws IOException {
this.filter = filter;
}
/**
* Captures count packets using an open capture session. Once the capture
* session has been closed it can not be opened again.
*
* @param count
* @throws IOException
*/
private void start(final int count) throws IOException {
if (isOpen() == false) {
throw new IOException("Capture session is closed");
}
/**
* Check if we still have running workers
*/
if (workers != null) {
return;
}
this.dispatched = 0;
if (workers == null) {
workers = new Thread[devices.length];
/*
* Initialize with dummy data, so that the array is not full of nulls,
* which would break the below loop if early workers start exitting
* quickly and reseting workers back to null as well.
*/
Arrays.fill(workers, Thread.currentThread());
}
for (int i = 0; i < devices.length; i++) {
final int index = i;
final String name = "Live-" + devices[i].getDisplayName();
/*
* Our capture session worker thread. Closes the LiveCapture when its done
*/
workers[i] = new Thread(new Runnable() {
public void run() {
try {
capture(count, index);
} catch (IOException e) {
/*
* The IO exception will be reported at next available opportunity
* such as in LiveIterator's next or hasNext method calls.
*/
ioError.set(e);
} finally {
workers[index] = null;
/*
* Now check if there are any workers left, if not then reset the
* workers variable, as a flag
*/
int j = 0;
for (; j < workers.length; j++) {
if (workers[j] != null) {
break;
}
}
if (j == workers.length) {
workers = null;
}
Thread.yield();
}
}
}, name);
workers[i].start();
}
}
}