package eu.hgross.blaubot.core; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.UUID; import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionListener; import eu.hgross.blaubot.messaging.BlaubotMessage; import eu.hgross.blaubot.mock.BlaubotConnectionQueueMock; import eu.hgross.blaubot.util.Log; /** * This is a wrapper for connections to the server. * It extends a standard connection by exchanging the kingdom's king's uniqueDeviceId at construction * time. * It can only be constructed using createFromOutboundConnect (peasants, kings, princes) and * createFromInboundConnection (for the server side). * The Handshake will be at construction time on inbound connections. * On outbound connections the handshake is done automatically before the first byte is send. */ public class BlaubotKingdomConnection extends AbstractBlaubotConnection implements IBlaubotConnection { private static final String LOG_TAG = "BlaubotKingdomConnection"; private final IBlaubotConnection connection; private volatile boolean handshakeDone = false; private volatile boolean isOutboundConnection = false; private UUID uuid = UUID.randomUUID(); /** * The unique device id of the king */ private String kingUniqueDeviceId; private BlaubotKingdomConnection(IBlaubotConnection connection, String kingUniqueDeviceId) { this.connection = connection; this.connection.addConnectionListener(new IBlaubotConnectionListener() { @Override public void onConnectionClosed(IBlaubotConnection connection) { disconnect(); notifyDisconnected(); } }); this.kingUniqueDeviceId = kingUniqueDeviceId; } /** * Creates this connection upon another connection and ensures to send the handshake before first write. * @param connection the outbound connection to be wrapped * @param kingUniqueDeviceId the current kingdom's king unique device id * @return the kingdom connection created from the outbound connection */ public static BlaubotKingdomConnection createFromOutboundConnection(IBlaubotConnection connection, String kingUniqueDeviceId) { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Creating KingdomConnection from outbound connection" + connection + " for kingdom of " + kingUniqueDeviceId); } BlaubotKingdomConnection blaubotKingdomConnection = new BlaubotKingdomConnection(connection, kingUniqueDeviceId); blaubotKingdomConnection.kingUniqueDeviceId = kingUniqueDeviceId; blaubotKingdomConnection.isOutboundConnection = true; return blaubotKingdomConnection; } /** * Creates this conneciton upon another connection and awaits data to be sent instantly. * Should be used in a separate thread. * * @param connection the inbound connection * @return the kingdom connection * @throws IOException if the initial handshake or something connection related failed */ public static BlaubotKingdomConnection createFromInboundConnection(IBlaubotConnection connection) throws IOException { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Creating KingdomConnection from inbound connection" + connection); } BlaubotKingdomConnection blaubotKingdomConnection = new BlaubotKingdomConnection(connection, null); blaubotKingdomConnection.isOutboundConnection = false; BlaubotMessage message = BlaubotMessage.readFromBlaubotConnection(connection); String kingUniqueDeviceId = new String(message.getPayload(), BlaubotConstants.STRING_CHARSET); blaubotKingdomConnection.kingUniqueDeviceId = kingUniqueDeviceId; if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Sucessfully created KingdomConnection from inbound connection" + connection + " for kingdom " + kingUniqueDeviceId); } return blaubotKingdomConnection; } /** * ensures that the handshake was done before the first byte is sent */ private synchronized void ensureHandshake() throws IOException { if(!isOutboundConnection) { return; } if(!handshakeDone) { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "A write was requested on the kingdom connection but a handshake never took place. Handshaking now ..."); } // send uniqueDeviceId of the king byte[] deviceIdBytes = kingUniqueDeviceId.getBytes(BlaubotConstants.STRING_CHARSET); BlaubotMessage kingdomIdMessage = new BlaubotMessage(); kingdomIdMessage.setPriority(BlaubotMessage.Priority.ADMIN); kingdomIdMessage.setPayload(deviceIdBytes); kingdomIdMessage.getMessageType().setIsAdminMessage(false).setContainsPayload(true).setIsKeepAliveMessage(false).setIsFirstHop(false); byte[] toSend = kingdomIdMessage.toBytes(); connection.write(toSend); handshakeDone = true; if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Handshake completed."); } } } @Override public void disconnect() { connection.disconnect(); } @Override public boolean isConnected() { return connection.isConnected(); } /** * used for the inbound connection (the server side) to fake the remote device as it is * relayed from this connection */ private IBlaubotDevice fakedKingDevice; @Override public IBlaubotDevice getRemoteDevice() { if(!isOutboundConnection) { // inbound connection if (fakedKingDevice == null) { fakedKingDevice = new BlaubotDevice(kingUniqueDeviceId); } return fakedKingDevice; } return connection.getRemoteDevice(); } @Override public void write(int b) throws SocketTimeoutException, IOException { ensureHandshake(); connection.write(b); } @Override public void write(byte[] bytes) throws SocketTimeoutException, IOException { ensureHandshake(); connection.write(bytes); } @Override public void write(byte[] bytes, int byteOffset, int byteCount) throws SocketTimeoutException, IOException { ensureHandshake(); connection.write(bytes, byteOffset, byteCount); } @Override public int read() throws SocketTimeoutException, IOException { return connection.read(); } @Override public int read(byte[] buffer) throws SocketTimeoutException, IOException { return connection.read(buffer); } @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws SocketTimeoutException, IOException { return connection.read(buffer, byteOffset, byteCount); } @Override public void readFully(byte[] buffer) throws SocketTimeoutException, IOException { connection.readFully(buffer); } @Override public void readFully(byte[] buffer, int offset, int byteCount) throws SocketTimeoutException, IOException { connection.readFully(buffer, offset, byteCount); } @Override public String toString() { final StringBuffer sb = new StringBuffer("BlaubotKingdomConnection{"); sb.append("connection=").append(connection); sb.append(", handshakeDone=").append(handshakeDone); sb.append(", isOutboundConnection=").append(isOutboundConnection); sb.append(", kingUniqueDeviceId='").append(kingUniqueDeviceId).append('\''); sb.append('}'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; BlaubotKingdomConnection that = (BlaubotKingdomConnection) o; if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (uuid != null ? uuid.hashCode() : 0); return result; } public static void main(String[] args) throws InterruptedException { final BlaubotConnectionQueueMock mockCon1 = new BlaubotConnectionQueueMock(new BlaubotDevice("server")); final BlaubotConnectionQueueMock mockCon2 = mockCon1.getOtherEndpointConnection(new BlaubotDevice("client")); Runnable outboundConnectionTask = new Runnable() { @Override public void run() { BlaubotKingdomConnection fromOutboundConnection = BlaubotKingdomConnection.createFromOutboundConnection(mockCon1, "daKing"); try { // to trigger the handshake fromOutboundConnection.write((byte)5); } catch (IOException e) { e.printStackTrace(); } } }; Runnable inboundConnectionTask = new Runnable() { @Override public void run() { try { BlaubotKingdomConnection fromInboundConnection = BlaubotKingdomConnection.createFromInboundConnection((mockCon2)); System.out.println("king: " + fromInboundConnection.kingUniqueDeviceId); } catch (IOException e) { e.printStackTrace(); } } }; Thread t1 = new Thread(outboundConnectionTask); Thread t2 = new Thread(inboundConnectionTask); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("done"); } /** * Get the uniqueDeviceId of the king * * @return the king's unique device id */ public String getKingUniqueDeviceId() { return kingUniqueDeviceId; } }