import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.StringTokenizer;
-import java.util.Map.Entry;
import net.pterodactylus.util.io.Closer;
/**
- * TODO
+ * An FCP connection to a Freenet node.
*
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
*/
public class FcpConnection {
+ /** The default port for FCP v2. */
public static final int DEFAULT_PORT = 9481;
- private final Object messageWaitSync = new Object();
- private FcpMessage receivedMessage = null;
-
+ /** The list of FCP listeners. */
private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
+ /** The address of the node. */
private final InetAddress address;
+
+ /** The port number of the node’s FCP port. */
private final int port;
- private final String clientName;
+ /** The remote socket. */
private Socket remoteSocket;
+
+ /** The input stream from the node. */
private InputStream remoteInputStream;
+
+ /** The output stream to the node. */
private OutputStream remoteOutputStream;
+
+ /** The connection handler. */
private FcpConnectionHandler connectionHandler;
- private boolean connected;
- public FcpConnection(String host, String clientName) throws UnknownHostException {
- this(host, DEFAULT_PORT, clientName);
+ /**
+ * Creates a new FCP connection to the Freenet node running on the given
+ * host, listening on the default port.
+ *
+ * @param host
+ * The hostname of the Freenet node
+ * @throws UnknownHostException
+ * if <code>host</code> can not be resolved
+ */
+ public FcpConnection(String host) throws UnknownHostException {
+ this(host, DEFAULT_PORT);
}
- public FcpConnection(String host, int port, String clientName) throws UnknownHostException {
- this(InetAddress.getByName(host), port, clientName);
+ /**
+ * Creates a new FCP connection to the Freenet node running on the given
+ * host, listening on the given port.
+ *
+ * @param host
+ * The hostname of the Freenet node
+ * @param port
+ * The port number of the node’s FCP port
+ * @throws UnknownHostException
+ * if <code>host</code> can not be resolved
+ */
+ public FcpConnection(String host, int port) throws UnknownHostException {
+ this(InetAddress.getByName(host), port);
}
- public FcpConnection(InetAddress address, String clientName) {
- this(address, DEFAULT_PORT, clientName);
+ /**
+ * Creates a new FCP connection to the Freenet node running at the given
+ * address, listening on the default port.
+ *
+ * @param address
+ * The address of the Freenet node
+ */
+ public FcpConnection(InetAddress address) {
+ this(address, DEFAULT_PORT);
}
- public FcpConnection(InetAddress address, int port, String clientName) {
+ /**
+ * Creates a new FCP connection to the Freenet node running at the given
+ * address, listening on the given port.
+ *
+ * @param address
+ * The address of the Freenet node
+ * @param port
+ * The port number of the node’s FCP port
+ */
+ public FcpConnection(InetAddress address, int port) {
this.address = address;
this.port = port;
- this.clientName = clientName;
}
//
}
/**
+ * Notifies listeners that a “NodeHello” message was received.
+ *
+ * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
+ * @param nodeHello
+ * The “NodeHello” message
+ */
+ private void fireReceivedNodeHello(NodeHello nodeHello) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedNodeHello(this, nodeHello);
+ }
+ }
+
+ /**
+ * Notifies listeners that a “CloseConnectionDuplicateClientName” message
+ * was received.
+ *
+ * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
+ * CloseConnectionDuplicateClientName)
+ * @param closeConnectionDuplicateClientName
+ * The “CloseConnectionDuplicateClientName” message
+ */
+ private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
+ }
+ }
+
+ /**
+ * Notifies listeners that a “SSKKeypair” message was received.
+ *
+ * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
+ * @param sskKeypair
+ * The “SSKKeypair” message
+ */
+ private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedSSKKeypair(this, sskKeypair);
+ }
+ }
+
+ /**
+ * Notifies listeners that a “Peer” message was received.
+ *
+ * @see FcpListener#receivedPeer(FcpConnection, Peer)
+ * @param peer
+ * The “Peer” message
+ */
+ private void fireReceivedPeer(Peer peer) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedPeer(this, peer);
+ }
+ }
+
+ /**
+ * Notifies all listeners that an “EndListPeers” message was received.
+ *
+ * @see FcpListener#receivedEndListPeers(FcpConnection, EndListPeers)
+ * @param endListPeers
+ * The “EndListPeers” message
+ */
+ private void fireReceivedEndListPeers(EndListPeers endListPeers) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedEndListPeers(this, endListPeers);
+ }
+ }
+
+ /**
+ * Notifies all listeners that a “PeerNote” message was received.
+ *
+ * @see FcpListener#receivedPeerNote(FcpConnection, PeerNote)
+ * @param peerNote
+ */
+ private void fireReceivedPeerNote(PeerNote peerNote) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedPeerNote(this, peerNote);
+ }
+ }
+
+ /**
+ * Notifies all listeners that an “EndListPeerNotes” message was received.
+ *
+ * @see FcpListener#receivedEndListPeerNotes(FcpConnection,
+ * EndListPeerNotes)
+ * @param endListPeerNotes
+ * The “EndListPeerNotes” message
+ */
+ private void fireReceivedEndListPeerNotes(EndListPeerNotes endListPeerNotes) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedEndListPeerNotes(this, endListPeerNotes);
+ }
+ }
+
+ /**
+ * Notifies all listeners that a “PeerRemoved” message was received.
+ *
+ * @see FcpListener#receivedPeerRemoved(FcpConnection, PeerRemoved)
+ * @param peerRemoved
+ * The “PeerRemoved” message
+ */
+ private void fireReceivedPeerRemoved(PeerRemoved peerRemoved) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedPeerRemoved(this, peerRemoved);
+ }
+ }
+
+ /**
+ * Notifies all listeners that a “NodeData” message was received.
+ *
+ * @see FcpListener#receivedNodeData(FcpConnection, NodeData)
+ * @param nodeData
+ * The “NodeData” message
+ */
+ private void fireReceivedNodeData(NodeData nodeData) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedNodeData(this, nodeData);
+ }
+ }
+
+ /**
+ * Notifies all listeners that a “TestDDAReply” message was received.
+ *
+ * @param testDDAReply
+ * The “TestDDAReply” message
+ */
+ private void fireReceivedTestDDAReply(TestDDAReply testDDAReply) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.receivedTestDDAReply(this, testDDAReply);
+ }
+ }
+
+ /**
* Notifies all registered listeners that a message has been received.
*
* @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
remoteSocket = new Socket(address, port);
remoteInputStream = remoteSocket.getInputStream();
remoteOutputStream = remoteSocket.getOutputStream();
- connected = true;
new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
}
if (connectionHandler == null) {
return;
}
- connected = false;
Closer.close(remoteSocket);
connectionHandler.stop();
connectionHandler = null;
}
- public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
- System.out.println("sending message: " + fcpMessage.getName());
- fcpMessage.write(remoteOutputStream);
- }
-
/**
- * Sends a “ListPeer” command to the node and returns the properties of the
- * peer.
+ * Sends the given FCP message.
*
- * @param nodeIdentifier
- * The name (except for OpenNet nodes), the identity or the
- * node’s “address:port” pair
- * @return The properties of the peer, or <code>null</code> if the peer is
- * unknown
+ * @param fcpMessage
+ * The FCP message to send
* @throws IOException
- * @throws FcpException
+ * if an I/O error occurs
*/
- public Map<String, String> sendListPeer(String nodeIdentifier) throws IOException, FcpException {
- FcpMessage listPeerMessage = new FcpMessage("ListPeer");
- listPeerMessage.setField("NodeIdentifier", nodeIdentifier);
- sendMessage(listPeerMessage);
- FcpMessage returnMessage = waitForMessage("Peer", "UnknownNodeIdentifier");
- if (returnMessage.getName().equals("Peer")) {
- return returnMessage.getFields();
- }
- return null;
- }
-
- void handleMessage(FcpMessage fcpMessage) {
- fireMessageReceived(fcpMessage);
- }
-
- public List<Map<String, String>> sendListPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
- FcpMessage listPeersMessage = new FcpMessage("ListPeers");
- listPeersMessage.setField("WithMetadata", String.valueOf(withMetadata));
- listPeersMessage.setField("WithVolatile", String.valueOf(withVolatile));
- sendMessage(listPeersMessage);
- List<Map<String, String>> peers = new ArrayList<Map<String, String>>();
- while (true) {
- FcpMessage returnMessage = waitForMessage("Peer", "EndListPeers");
- if (returnMessage.getName().equals("EndListPeers")) {
- break;
- }
- peers.add(returnMessage.getFields());
- }
- return peers;
- }
-
- public List<Map<String, String>> sendListPeerNotes(String nodeIdentifier) throws IOException, FcpException {
- FcpMessage listPeerNotesMessage = new FcpMessage("ListPeerNotes");
- listPeerNotesMessage.setField("NodeIdentifier", nodeIdentifier);
- sendMessage(listPeerNotesMessage);
- List<Map<String, String>> peerNotes = new ArrayList<Map<String, String>>();
- while (true) {
- FcpMessage returnMessage = waitForMessage("PeerNote", "EndListPeerNotes");
- if (returnMessage.getName().equals("EndListPeerNotes")) {
- break;
- }
- peerNotes.add(returnMessage.getFields());
- }
- return peerNotes;
- }
-
- public void sendTestDDARequest(String directory, boolean wantReadDirectory, boolean wantWriteDirectory) throws IOException, FcpException {
- FcpMessage testDDARequestMessage = new FcpMessage("TestDDARequest");
- testDDARequestMessage.setField("Directory", directory);
- testDDARequestMessage.setField("WantReadDirectory", String.valueOf(wantReadDirectory));
- testDDARequestMessage.setField("WantWriteDirectory", String.valueOf(wantWriteDirectory));
- sendMessage(testDDARequestMessage);
- }
-
- public FcpKeyPair generateSSK() throws IOException, FcpException {
- FcpMessage generateSSKMessage = new FcpMessage("GenerateSSK");
- String identifier = hashCode() + String.valueOf(System.currentTimeMillis());
- generateSSKMessage.setField("Identifier", identifier);
- sendMessage(generateSSKMessage);
- FcpMessage returnMessage = waitForMessage("SSKKeypair(Identifier=" + identifier + ")");
- String publicKey = returnMessage.getField("RequestURI");
- String privateKey = returnMessage.getField("InsertURI");
- return new FcpKeyPair(publicKey, privateKey);
+ public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
+ System.out.println("sending message: " + fcpMessage.getName());
+ fcpMessage.write(remoteOutputStream);
}
//
- // PRIVATE METHODS
+ // PACKAGE-PRIVATE METHODS
//
- public FcpMessage waitForMessage(String... messageNames) throws FcpException {
- FcpMessage oldMessage = null;
- synchronized (messageWaitSync) {
- while (true) {
- while (receivedMessage == oldMessage) {
- System.out.println("waiting for receivedMessage");
- try {
- messageWaitSync.wait();
- } catch (InterruptedException ie1) {
- }
- }
- System.out.println("got message: " + receivedMessage.getName());
- String receivedMessageName = receivedMessage.getName();
- if ("ProtocolError".equals(receivedMessageName)) {
- int code = Integer.valueOf(receivedMessage.getField("Code"));
- boolean fatal = Boolean.valueOf(receivedMessage.getField("Fatal"));
- boolean global = Boolean.valueOf(receivedMessage.getField("Global"));
- String codeDescription = receivedMessage.getField("CodeDescription");
- String extraDescription = receivedMessage.getField("ExtraDescription");
- String identifier = receivedMessage.getField("Identifier");
- FcpProtocolException fcpProtocolException = new FcpProtocolException(code, fatal, global);
- fcpProtocolException.setCodeDescription(codeDescription);
- fcpProtocolException.setExtraDescription(extraDescription);
- fcpProtocolException.setIdentifier(identifier);
- throw fcpProtocolException;
- }
- for (String messageName: messageNames) {
- int firstBracket = messageName.indexOf('(');
- Map<String, String> wantedIdentifiers = new HashMap<String, String>();
- if (firstBracket > -1) {
- StringTokenizer identifierTokens = new StringTokenizer(messageName.substring(firstBracket), "()");
- while (identifierTokens.hasMoreTokens()) {
- String identifierToken = identifierTokens.nextToken();
- int equalSign = identifierToken.indexOf('=');
- if (equalSign > -1) {
- wantedIdentifiers.put(identifierToken.substring(0, equalSign), identifierToken.substring(equalSign + 1));
- }
- }
- messageName = messageName.substring(0, firstBracket);
- }
- if (receivedMessageName.equals(messageName)) {
- boolean found = true;
- for (Entry<String, String> wantedIdentifier: wantedIdentifiers.entrySet()) {
- System.out.println("key: " + wantedIdentifier.getKey() + ", value: " + wantedIdentifier.getValue() + ", msg: " + receivedMessage.getField(wantedIdentifier.getKey()));
- if (!wantedIdentifier.getValue().equals(receivedMessage.getField(wantedIdentifier.getKey()))) {
- found = false;
- break;
- }
- }
- if (found) {
- System.out.println("message found");
- FcpMessage foundMessage = receivedMessage;
- receivedMessage = null;
- messageWaitSync.notifyAll();
- return foundMessage;
- }
- }
- }
- oldMessage = receivedMessage;
- }
+ /**
+ * Handles the given message, notifying listeners. This message should only
+ * be called by {@link FcpConnectionHandler}.
+ *
+ * @param fcpMessage
+ * The received message
+ */
+ void handleMessage(FcpMessage fcpMessage) {
+ String messageName = fcpMessage.getName();
+ if ("Peer".equals(messageName)) {
+ fireReceivedPeer(new Peer(fcpMessage));
+ } else if ("PeerNote".equals(messageName)) {
+ fireReceivedPeerNote(new PeerNote(fcpMessage));
+ } else if ("EndListPeerNotes".equals(messageName)) {
+ fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
+ } else if ("EndListPeers".equals(messageName)) {
+ fireReceivedEndListPeers(new EndListPeers(fcpMessage));
+ } else if ("SSKKeypair".equals(messageName)) {
+ fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
+ } else if ("PeerRemoved".equals(messageName)) {
+ fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
+ } else if ("NodeData".equals(messageName)) {
+ fireReceivedNodeData(new NodeData(fcpMessage));
+ } else if ("TestDDAReply".equals(messageName)) {
+ fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
+ } else if ("NodeHello".equals(messageName)) {
+ fireReceivedNodeHello(new NodeHello(fcpMessage));
+ } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
+ fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
+ } else {
+ fireMessageReceived(fcpMessage);
}
}