+/*
+ * jSite2 - FpcConnection.java -
+ * Copyright © 2008 David Roden
+ *
+ * This program 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package net.pterodactylus.util.fcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+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
+ *
+ * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
+ * @version $Id$
+ */
+public class FcpConnection {
+
+ public static final int DEFAULT_PORT = 9481;
+
+ private final Object messageWaitSync = new Object();
+ private FcpMessage receivedMessage = null;
+
+ private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
+
+ private final InetAddress address;
+ private final int port;
+ private final String clientName;
+
+ private Socket remoteSocket;
+ private InputStream remoteInputStream;
+ private OutputStream remoteOutputStream;
+ private boolean connected;
+
+ public FcpConnection(String host, String clientName) throws UnknownHostException {
+ this(host, DEFAULT_PORT, clientName);
+ }
+
+ public FcpConnection(String host, int port, String clientName) throws UnknownHostException {
+ this(InetAddress.getByName(host), port, clientName);
+ }
+
+ public FcpConnection(InetAddress address, String clientName) {
+ this(address, DEFAULT_PORT, clientName);
+ }
+
+ public FcpConnection(InetAddress address, int port, String clientName) {
+ this.address = address;
+ this.port = port;
+ this.clientName = clientName;
+ }
+
+ //
+ // LISTENER MANAGEMENT
+ //
+
+ public void addFcpListener(FcpListener fcpListener) {
+ fcpListeners.add(fcpListener);
+ }
+
+ public void removeFcpListener(FcpListener fcpListener) {
+ fcpListeners.remove(fcpListener);
+ }
+
+ private void fireNodeHello(Map<String, String> nodeProperties) {
+ for (FcpListener fcpListener: fcpListeners) {
+ fcpListener.fcpNodeHello(this, nodeProperties);
+ }
+ }
+
+ //
+ // ACTIONS
+ //
+
+ public synchronized void connect() throws FcpException, IOException {
+ System.out.println("connecting...");
+ remoteSocket = new Socket(address, port);
+ remoteInputStream = remoteSocket.getInputStream();
+ remoteOutputStream = remoteSocket.getOutputStream();
+ connected = true;
+ System.out.println("connected.");
+ new Thread(new FcpConnectionHandler(this, remoteInputStream)).start();
+ sendMessage(clientHelloMessage);
+ }
+
+ public synchronized void disconnect() {
+ connected = false;
+ Closer.close(remoteSocket);
+ }
+
+ /**
+ * Sends a “ListPeer” command to the node and returns the properties of the
+ * peer.
+ *
+ * @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
+ * @throws IOException
+ * @throws FcpException
+ */
+ 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;
+ }
+
+ 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);
+ }
+
+ //
+ // PACKAGE-PRIVATE METHODS
+ //
+
+ void handleMessage(FcpMessage fcpMessage) {
+ synchronized (messageWaitSync) {
+ while (receivedMessage != null) {
+ /* previous message has not yet been consumed */
+ System.out.println("waiting for message to be consumed...");
+ try {
+ messageWaitSync.wait();
+ } catch (InterruptedException ie1) {
+ }
+ }
+ /* TODO - check whether to send events here or later. */
+ if ("NodeHello".equals(fcpMessage.getName())) {
+ fireNodeHello(fcpMessage.getFields());
+ }
+ System.out.println("setting receivedMessage");
+ receivedMessage = fcpMessage;
+ messageWaitSync.notifyAll();
+ }
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
+ System.out.println("sending message: " + fcpMessage.getName());
+ fcpMessage.write(remoteOutputStream);
+ }
+
+ 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;
+ }
+ }
+ }
+
+}