package eu.hgross.blaubot.android.wifip2p;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import eu.hgross.blaubot.core.AbstractBlaubotConnection;
import eu.hgross.blaubot.core.BlaubotConstants;
import eu.hgross.blaubot.core.BlaubotUUIDSet;
import eu.hgross.blaubot.core.IBlaubotAdapter;
import eu.hgross.blaubot.core.IBlaubotConnection;
import eu.hgross.blaubot.core.IBlaubotDevice;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor;
import eu.hgross.blaubot.util.Log;
/**
* WIFI P2P connection implementation
*
* @author Henning Gross {@literal (mail.to@henning-gross.de)}
*
*/
public class BlaubotWifiP2PConnection extends AbstractBlaubotConnection {
protected static int UUID_BYTE_LENGTH = UUID.randomUUID().toString().getBytes(BlaubotConstants.STRING_CHARSET).length;
private static final String LOG_TAG = "BlaubotWifiP2PConnection";
private final BlaubotWifiP2PDevice device;
private final Socket socket;
private final DataInputStream dataInputStream;
private volatile boolean notifiedDisconnect = false;
private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
/**
* Creates the blaubot connection from a connected clientSocket.
* This methods connects to the socket an checks wether the remote endpoint
* is a valid blaubot endpoint (wifip2p {@link IBlaubotConnectionAcceptor}) by
* directly reading a {@link UUID} from the connection.
*
* If this {@link UUID} matches the app OR beacon UUID, then this connection
* will return a valid {@link IBlaubotConnection}.
* Otherwise, if the UUID does not match or nothing is received for too long,
* null will be the result.
*
* @param device the remote device
* @param clientSocket the client socket
* @param uuidSet the {@link BlaubotUUIDSet} retrievable from a {@link IBlaubotAdapter}
* @return the connection object iff the remote endpoint is a blaubot instance which responded fast enougth with a valid beacon or app UUID.
*/
public static BlaubotWifiP2PConnection fromSocket(BlaubotWifiP2PDevice device, Socket clientSocket, final BlaubotUUIDSet uuidSet) {
final BlaubotWifiP2PConnection conn = new BlaubotWifiP2PConnection(device, clientSocket);
// using a future to be able to time out
Future<Boolean> future = EXECUTOR_SERVICE.submit(new Callable<Boolean>() {
@Override
public Boolean call() {
// read uuid and validate
byte[] buffer = new byte[UUID_BYTE_LENGTH];
byte[] buffer_ordered = new byte[UUID_BYTE_LENGTH];
try {
// read bytes to buffer
conn.readFully(buffer);
// read in order
ByteBuffer.wrap(buffer).order(BlaubotConstants.BYTE_ORDER).get(buffer_ordered);
String uuidString = new String(buffer_ordered, BlaubotConstants.STRING_CHARSET);
// validate uuid
if(uuidString.equals(uuidSet.getAppUUID().toString()) || uuidString.equals(uuidSet.getBeaconUUID().toString())) {
return true;
}
} catch (IOException e) {
}
return false;
}
});
try {
boolean isValid = future.get(4000, TimeUnit.MILLISECONDS);
if(isValid) {
return conn;
}
} catch (InterruptedException e) {
} catch (ExecutionException e) {
} catch (TimeoutException e) {
try {
clientSocket.close();
} catch (IOException e1) {}
}
return null;
// now read
}
private BlaubotWifiP2PConnection(BlaubotWifiP2PDevice device, Socket clientSocket) {
this.socket = clientSocket;
this.device = device;
try {
this.dataInputStream = new DataInputStream(clientSocket.getInputStream());
} catch (IOException e) {
throw new RuntimeException("Could not get InputStream from clientSocket. A socket handed to the constructor has to be connected!");
}
}
@Override
protected synchronized void notifyDisconnected() {
if (notifiedDisconnect)
return;
super.notifyDisconnected();
notifiedDisconnect = true;
}
@Override
public void disconnect() {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Disconnecting BlaubotWifiP2PConnection " + this + " ...");
}
try {
socket.close();
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to close socket", e);
}
this.notifyDisconnected();
}
@Override
public boolean isConnected() {
return socket.isConnected() && !socket.isClosed();
}
@Override
public IBlaubotDevice getRemoteDevice() {
return device;
}
private void handleSocketException(IOException e) throws SocketTimeoutException, IOException {
if(Log.logWarningMessages()) {
Log.w(LOG_TAG, "Got socket exception", e);
}
if(!(e instanceof SocketTimeoutException)) {
this.disconnect();
}
throw e;
}
@Override
public int read() throws SocketTimeoutException, IOException {
try {
return this.socket.getInputStream().read();
} catch (IOException e) {
this.handleSocketException(e);
return -1; // will never get here
}
}
@Override
public int read(byte[] b) throws SocketTimeoutException, IOException {
try {
return this.socket.getInputStream().read(b);
} catch (IOException e) {
this.handleSocketException(e);
return -1; // will never get here
}
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws SocketTimeoutException, IOException {
try {
return this.socket.getInputStream().read(buffer, byteOffset, byteCount);
} catch (IOException e) {
this.handleSocketException(e);
return -1; // will never get here
}
}
@Override
public void write(int b) throws SocketTimeoutException, IOException {
try {
this.socket.getOutputStream().write(b);
} catch (IOException e) {
this.handleSocketException(e);
}
}
@Override
public void write(byte[] bytes) throws SocketTimeoutException, IOException {
try {
this.socket.getOutputStream().write(bytes);
} catch (IOException e) {
this.handleSocketException(e);
}
}
@Override
public void write(byte[] b, int off, int len) throws SocketTimeoutException, IOException {
try {
this.socket.getOutputStream().write(b,off,len);
} catch (IOException e) {
this.handleSocketException(e);
}
}
@Override
public void readFully(byte[] buffer) throws SocketTimeoutException, IOException {
try {
dataInputStream.readFully(buffer);
} catch (IOException e) {
handleSocketException(e);
}
}
@Override
public void readFully(byte[] buffer, int offset, int byteCount) throws SocketTimeoutException, IOException {
try {
dataInputStream.readFully(buffer, offset, byteCount);
} catch (IOException e) {
handleSocketException(e);
}
}
}