/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.net; import java.net.InetSocketAddress; import java.util.Date; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.Member; import de.dal33t.powerfolder.PFComponent; import de.dal33t.powerfolder.light.MemberInfo; import de.dal33t.powerfolder.message.Identity; import de.dal33t.powerfolder.message.IdentityReply; import de.dal33t.powerfolder.message.Message; import de.dal33t.powerfolder.message.Pong; import de.dal33t.powerfolder.message.Problem; import de.dal33t.powerfolder.message.RelayedMessage; import de.dal33t.powerfolder.message.RelayedMessageExt; import de.dal33t.powerfolder.message.RelayedMessage.Type; import de.dal33t.powerfolder.util.ByteSerializer; import de.dal33t.powerfolder.util.Format; import de.dal33t.powerfolder.util.IdGenerator; import de.dal33t.powerfolder.util.Reject; /** * The base super class for connection which get relayed through a third node. * <p> * TRAC #597 * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc</a> * @version $Revision: 1.5 $ */ public abstract class AbstractRelayedConnectionHandler extends PFComponent implements ConnectionHandler { /** The relay to use */ private Member relay; /** * The connection id */ private long connectionId; /** * The aimed remote for this connection. */ private MemberInfo remote; /** The assigned member */ private Member member; // Our identity private Identity myIdentity; // Identity of remote peer private Identity identity; private IdentityReply identityReply; // The magic id, which has been send to the remote peer private String myMagicId; private ByteSerializer serializer; // The send buffer private Queue<Message> messagesToSendQueue; private boolean started; // Locks private final Object identityWaiter = new Object(); private final Object identityAcceptWaiter = new Object(); // Lock for sending message private final Object sendLock = new Object(); /** * The current active sender. */ private Runnable sender; /** * Lock to ensure that modifications to senders are performed by one thread * only. */ private Lock senderSpawnLock; // Keepalive stuff private Date lastKeepaliveMessage; /** * Flag that indicates a received ACK. relyed connection is ready for * traffic. */ private boolean ackReceived; /** * Flag that indicates a received NACK. relyed connection is cannot be * established */ private boolean nackReceived; /** * Builds a new anonymous connection manager using the given relay. * <p> * Should be called from <code>ConnectionHandlerFactory</code> only. * * @see ConnectionHandlerFactory * @param controller * the controller. * @param remote * the aimed remote side to connect. * @param relay * the relay to use. * @throws ConnectionException */ protected AbstractRelayedConnectionHandler(Controller controller, MemberInfo remote, long connectionId, Member relay) { super(controller); Reject.ifNull(remote, "Remote is null"); Reject.ifNull(relay, "Relay is null"); this.remote = remote; this.relay = relay; this.serializer = new ByteSerializer(); this.connectionId = connectionId; } // Abstract behaviour ***************************************************** /** * Called before the message gets actally written into the * <code>RelayedMessage</code> * * @param message * the message to serialize * @return the serialized message */ protected abstract byte[] serialize(Message message) throws ConnectionException; /** * Called when the data got read from the <code>RelayedMessage</code>. * Should re-construct the serialized object from the data. * * @param data * the serialized data * @param len * the actual size of the data in data buffer * @return the deserialized object */ protected abstract Object deserialize(byte[] data, int len) throws ConnectionException, ClassNotFoundException; /** * (Optional) Handles the received object. * * @param obj * the obj that was received * @return true if this object/message was handled. * @throws ConnectionException * if something is broken. */ protected boolean receivedObject(Object obj) throws ConnectionException { return false; } /** * @return an identity that gets send to the remote side. */ protected abstract Identity createOwnIdentity(); /** * @return the internal used serializer */ protected ByteSerializer getSerializer() { return serializer; } /** * @return the relay */ protected Member getRelay() { return relay; } /** * Initializes the connection handler. * * @throws ConnectionException */ public void init() throws ConnectionException { if (!relay.isCompletelyConnected()) { throw new ConnectionException("Connection to peer is closed") .with(this); } this.started = true; // Don't clear, might have been already received! // this.identity = null; // this.identityReply = null; this.messagesToSendQueue = new ConcurrentLinkedQueue<Message>(); this.senderSpawnLock = new ReentrantLock(); long startTime = System.currentTimeMillis(); // Generate magic id, 16 byte * 8 * 8 bit = 1024 bit key myMagicId = IdGenerator.makeId() + IdGenerator.makeId() + IdGenerator.makeId() + IdGenerator.makeId() + IdGenerator.makeId() + IdGenerator.makeId() + IdGenerator.makeId() + IdGenerator.makeId(); // Create identity myIdentity = createOwnIdentity(); if (isFiner()) { logFiner("Sending my identity, nick: '" + myIdentity.getMemberInfo().nick + "', ID: " + myIdentity.getMemberInfo().id); } // Send identity sendMessagesAsynchron(myIdentity); waitForRemoteIdentity(); if (!isConnected()) { shutdown(); throw new ConnectionException( "Remote peer disconnected while waiting for his identity") .with(this); } if (identity == null || identity.getMemberInfo() == null) { throw new ConnectionException( "Did not receive a valid identity from peer after 60s: " + getRemote()).with(this); } // Check if IP is on LAN // onLAN = getController().getBroadcastManager().receivedBroadcastFrom( // socket.getInetAddress()); // logWarning("Received broadcast from ? " + onLAN); long took = System.currentTimeMillis() - startTime; if (isFiner()) { logFiner("Connect took " + took + "ms, time differ: " + ((getTimeDeltaMS() / 1000) / 60) + " min, remote ident: " + getIdentity()); } // Check this connection for keep-alive getController().getIOProvider().startKeepAliveCheck(this); } /** * Shuts down this connection handler by calling shutdown of member. If no * associated member is found, the con handler gets directly shut down. * <p> */ public void shutdownWithMember() { if (getMember() != null) { // Shutdown member. This means this connection handler gets shut // down by member getMember().shutdown(); } if (started) { // Not shutdown yet, just shut down shutdown(); } } /** * Shuts down the connection handler. The member is shut down optionally */ public void shutdown() { if (!started) { return; } if (isFiner()) { logFiner("Shutting down"); } // if (isConnected() && started) { // // Send "EOF" if possible, the last thing you see // sendMessagesAsynchron(new Problem("Closing connection, EOF", true, // Problem.DISCONNECTED)); // // Give him some time to receive the message // waitForEmptySendQueue(1000); // } started = false; // Clear magic ids // myMagicId = null; // identity = null; // Remove link to member setMember(null); // Clear send queue messagesToSendQueue.clear(); getController().getIOProvider().getRelayedConnectionManager() .removePedingRelayedConnectionHandler(this); getController().getIOProvider().removeKeepAliveCheck(this); // Trigger all waiting treads synchronized (identityWaiter) { identityWaiter.notifyAll(); } synchronized (identityAcceptWaiter) { identityAcceptWaiter.notifyAll(); } synchronized (messagesToSendQueue) { messagesToSendQueue.notifyAll(); } // make sure the garbage collector gets this serializer = null; } /** * @return true if the connection is active */ public boolean isConnected() { return started && relay.isConnected(); } public boolean isEncrypted() { return false; } public boolean isOnLAN() { return false; } public void setOnLAN(boolean onlan) { } public void setMember(Member member) { this.member = member; // Logic moved into central place <code>Member.isOnLAN()</code> // if (!isOnLAN() // && member != null // && getController().getNodeManager().isNodeOnConfiguredLan( // member.getInfo())) // { // setOnLAN(true); // } } public Member getMember() { return member; } public Date getLastKeepaliveMessageTime() { return lastKeepaliveMessage; } /** * @return the aimed remote destination for this connection. */ public MemberInfo getRemote() { return remote; } /** * @return the unique connection id. */ public long getConnectionId() { return connectionId; } public boolean isAckReceived() { return ackReceived; } public void setAckReceived(boolean ackReceived) { this.ackReceived = ackReceived; } public boolean isNackReceived() { return nackReceived; } public void setNackReceived(boolean nackReceived) { this.nackReceived = nackReceived; } public void sendMessage(Message message) throws ConnectionException { if (message == null) { throw new NullPointerException("Message is null"); } if (!isConnected()) { throw new ConnectionException("Connection to remote peer closed") .with(this); } // break if remote peer did no identitfy if (identity == null && (!(message instanceof Identity))) { throw new ConnectionException( "Unable to send message, peer did not identify yet").with(this); } try { synchronized (sendLock) { if (isFiner()) { logFiner("-- (sending) -> " + message); } if (!isConnected() || !started) { throw new ConnectionException( "Connection to remote peer closed").with(this); } byte[] data = serialize(message); RelayedMessage dataMsg = identity != null && identity.getProtocolVersion() >= 108 && relay.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.DATA_ZIPPED, getController() .getMySelf().getInfo(), remote, connectionId, data) : new RelayedMessage(Type.DATA_ZIPPED, getController() .getMySelf().getInfo(), remote, connectionId, data); relay.sendMessage(dataMsg); getController().getTransferManager() .getTotalUploadTrafficCounter() .bytesTransferred(data.length + 4); } } catch (RuntimeException e) { logSevere("Runtime exception while serializing: " + message, e); // Ensure shutdown shutdownWithMember(); throw e; } catch (ConnectionException e) { // Ensure shutdown shutdownWithMember(); throw e; } } public void sendMessagesAsynchron(Message... messages) { for (Message message : messages) { sendMessageAsynchron(message, null); } } /** * A message to be send later. code execution does not wait util message was * sent successfully * * @param message * the message to be sent * @param errorMessage * the error message to be logged on connection problem */ private void sendMessageAsynchron(Message message, String errorMessage) { Reject.ifNull(message, "Message is null"); senderSpawnLock.lock(); messagesToSendQueue.offer(message); if (messagesToSendQueue.size() > 50 && isWarning()) { String msg = "Many messages in send queue: " + messagesToSendQueue.size() + ": " + messagesToSendQueue; if (msg.length() > 300) { msg = msg.substring(0, 300); msg += "..."; } logWarning(msg); } if (sender == null) { sender = new Sender(); getController().getIOProvider().startIO(sender); } senderSpawnLock.unlock(); } public long getTimeDeltaMS() { if (identity.getTimeGMT() == null) return 0; return myIdentity.getTimeGMT().getTimeInMillis() - identity.getTimeGMT().getTimeInMillis(); } public boolean canMeasureTimeDifference() { return identity.getTimeGMT() != null; } public Identity getIdentity() { return identity; } public Identity getMyIdentity() { return myIdentity; } public String getMyMagicId() { return myMagicId; } public String getRemoteMagicId() { return identity != null ? identity.getMagicId() : null; } public ConnectionQuality getConnectionQuality() { return ConnectionQuality.POOR; } /** * Waits until we received the remote identity */ private void waitForRemoteIdentity() { synchronized (identityWaiter) { if (identity == null) { // wait for remote identity try { identityWaiter.wait(60000); } catch (InterruptedException e) { // Ignore logFiner("InterruptedException", e); } } } } public boolean acceptIdentity(Member node) { Reject.ifNull(node, "node is null"); // Connect member with this node member = node; // now handshake if (isFiner()) { logFiner("Sending accept of identity to " + this); } sendMessagesAsynchron(IdentityReply.accept()); // wait for accept of our identity long start = System.currentTimeMillis(); synchronized (identityAcceptWaiter) { if (identityReply == null) { try { identityAcceptWaiter.wait(20000); } catch (InterruptedException e) { logFiner("InterruptedException", e); } } } long took = (System.currentTimeMillis() - start) / 1000; if (identityReply != null && !identityReply.accepted) { logWarning("Remote peer rejected our connection: " + identityReply.message); member = null; return false; } if (!isConnected()) { logFine("Remote member disconnected while waiting for identity reply. " + identity); member = null; return false; } if (identityReply == null) { logFine("Did not receive a identity reply after " + took + "s. Connected? " + isConnected() + ". remote id: " + identity); member = null; return false; } if (identityReply.accepted) { if (isFiner()) { logFiner("Identity accepted by remote peer. " + this); } } else { member = null; logWarning("Identity rejected by remote peer. " + this); } return identityReply.accepted; } public boolean waitForEmptySendQueue(long ms) { long waited = 0; while (!messagesToSendQueue.isEmpty() && isConnected()) { try { // logWarning("Waiting for empty send buffer to " + // getMember()); waited += 50; // Wait a bit the let the send queue get empty Thread.sleep(50); if (ms >= 0 && waited >= ms) { // Stop waiting break; } } catch (InterruptedException e) { logFiner("InterruptedException", e); break; } } if (waited > 0) { if (isFiner()) { logFiner("Waited " + waited + "ms for empty sendbuffer, clear now, proceeding to " + getMember()); } } return messagesToSendQueue.isEmpty(); } public boolean acceptHandshake() { // IS not longer pending. Now connected to Member. getController().getIOProvider().getRelayedConnectionManager() .removePedingRelayedConnectionHandler(this); return true; } public InetSocketAddress getRemoteAddress() { return getMember() != null ? getMember().getReconnectAddress() : null; } public int getRemoteListenerPort() { if (identity == null || identity.getMemberInfo() == null || identity.getMemberInfo().getConnectAddress() == null) { return -1; } if (identity.isTunneled()) { // No reconnection available to a tunneled connection. return -1; } return identity.getMemberInfo().getConnectAddress().getPort(); } // Receiving ************************************************************** private ConcurrentLinkedQueue<RelayedMessage> receiveQueue = new ConcurrentLinkedQueue<RelayedMessage>(); private AtomicBoolean receiving = new AtomicBoolean(false); /** * Receives and processes the relayed message. * * @param message * the message received from a relay. */ public void receiveRelayedMessage(RelayedMessage message) { // in queue for later processing in own thread boolean startReceiver = false; synchronized (receiveQueue) { receiveQueue.offer(message); startReceiver = receiving.compareAndSet(false, true); } if (startReceiver) { getController().getIOProvider().startIO(new Runnable() { public void run() { while (true) { RelayedMessage rm; synchronized (receiveQueue) { rm = receiveQueue.poll(); if (rm == null) { receiving.set(false); break; } } receiveRelayedMessage0(rm); } } }); } } /** * Receives and processes the relayed message. * * @param message * the message received from a relay. */ private void receiveRelayedMessage0(RelayedMessage message) { try { // if (!started) { // // Do not process this message // return; // } byte[] data = message.getPayload(); Object obj = deserialize(data, data.length); lastKeepaliveMessage = new Date(); getController().getTransferManager() .getTotalDownloadTrafficCounter().bytesTransferred(data.length); // Consistency check: // if (getMember() != null // && getMember().isCompletelyConnected() // && getMember().getPeer() != // AbstractSocketConnectionHandler.this) // { // logSevere( // "DEAD connection handler found for member: " // + getMember()); // shutdown(); // return; // } if (isFiner()) { logFiner("<- (received, " + Format.formatBytes(data.length) + ") - " + obj); } if (!getController().isStarted()) { logFiner("Peer still active, shutting down " + getMember()); shutdownWithMember(); return; } if (obj instanceof Identity) { if (isFiner()) { logFiner("Received remote identity: " + obj); } // Trigger identitywaiter synchronized (identityWaiter) { // the remote identity identity = (Identity) obj; identityWaiter.notifyAll(); } // Get magic id if (isFiner()) { logFiner("Received magicId: " + identity.getMagicId()); } } else if (obj instanceof IdentityReply) { if (isFiner()) { logFiner("Received identity reply: " + obj); } // Trigger identity accept waiter synchronized (identityAcceptWaiter) { // remote side accpeted our identity identityReply = (IdentityReply) obj; identityAcceptWaiter.notifyAll(); } } else if (obj instanceof Pong) { // Do nothing. // TRAC #812: Ping is answered on Member, not here! } else if (obj instanceof Problem) { Problem problem = (Problem) obj; if (member != null) { member.handleMessage(problem, this); } else { logWarning("(" + (identity != null ? identity.getMemberInfo().nick : "-") + ") Problem received: " + problem.message); if (problem.fatal) { // Fatal problem, disconnecting shutdown(); } } } else if (receivedObject(obj)) { // The object was handled by the subclass. // OK pass through } else if (obj instanceof Message) { if (member != null) { member.handleMessage((Message) obj, this); } else if (!isConnected()) { // Simply break. Already disconnected shutdownWithMember(); } else { logSevere("Connection closed, message received, before peer identified itself: " + obj); // connection closed shutdownWithMember(); } } else { logSevere("Received unknown message from peer: " + obj); } } catch (ConnectionException e) { logFiner("ConnectionException", e); logConnectionClose(e); StringBuffer hexString = new StringBuffer(); for (int i = 0; i < message.getPayload().length; i++) { hexString.append(Integer.toHexString(0xFF & message .getPayload()[i])); } logWarning("On message: " + message + ": " + hexString); } catch (ClassNotFoundException e) { logFiner("ClassNotFoundException", e); logWarning("Received unknown packet/class: " + e.getMessage() + " from " + AbstractRelayedConnectionHandler.this); StringBuffer hexString = new StringBuffer(); for (int i = 0; i < message.getPayload().length; i++) { hexString.append(Integer.toHexString(0xFF & message .getPayload()[i])); } logWarning("On message: " + message + ": " + hexString); // do not break connection } catch (RuntimeException e) { logSevere("RuntimeException", e); shutdownWithMember(); StringBuffer hexString = new StringBuffer(); for (int i = 0; i < message.getPayload().length; i++) { hexString.append(Integer.toHexString(0xFF & message .getPayload()[i])); } logWarning("On message: " + message + ": " + hexString); throw e; } } /** * Logs a connection closed event * * @param e */ private void logConnectionClose(Exception e) { String msg = "Connection closed to " + ((member == null) ? this.toString() : member.toString()); if (e != null) { msg += ". Cause: " + e.toString(); } logFine(msg); logFiner("Exception", e); } // General **************************************************************** public String toString() { return "RelayedConHan '" + remote.nick + "-" + connectionId + "'"; } // Inner classes ********************************************************** /** * The sender class, handles all asynchron messages * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a> * @version $Revision: 1.72 $ */ class Sender implements Runnable { public void run() { if (isFiner()) { logFiner("Asynchron message send triggered, sending " + messagesToSendQueue.size() + " message(s)"); } if (!isConnected()) { // Client disconnected, stop logFine("Peer disconnected while sender got active. Msgs in queue: " + messagesToSendQueue.size() + ": " + messagesToSendQueue); return; } // logWarning( // "Sender started with " + messagesToSendQueue.size() // + " messages in queue"); int i = 0; Message msg; // long start = System.currentTimeMillis(); while (true) { senderSpawnLock.lock(); msg = messagesToSendQueue.poll(); if (msg == null) { sender = null; senderSpawnLock.unlock(); break; } senderSpawnLock.unlock(); i++; if (!started) { logFine("Peer shutdown while sending: " + msg); senderSpawnLock.lock(); sender = null; senderSpawnLock.unlock(); shutdownWithMember(); break; } try { // logWarning( // "Sending async (" + messagesToSendQueue.size() // + "): " + asyncMsg.getMessage()); sendMessage(msg); // logWarning("Send complete: " + // asyncMsg.getMessage()); } catch (ConnectionException e) { logFine("Unable to send message asynchronly. " + e); logFiner("ConnectionException", e); senderSpawnLock.lock(); sender = null; senderSpawnLock.unlock(); shutdownWithMember(); // Stop thread execution break; } catch (Throwable t) { logSevere("Unable to send message asynchronly. " + t, t); senderSpawnLock.lock(); sender = null; senderSpawnLock.unlock(); shutdownWithMember(); // Stop thread execution break; } } // logWarning("Sender finished after sending " + i + " messages"); } } }