current state
[jSite2.git] / src / net / pterodactylus / util / fcp / FcpConnection.java
1 /*
2  * jSite2 - FpcConnection.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.util.fcp;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.InetAddress;
26 import java.net.Socket;
27 import java.net.UnknownHostException;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.StringTokenizer;
33 import java.util.Map.Entry;
34
35 import net.pterodactylus.util.io.Closer;
36
37 /**
38  * TODO
39  * 
40  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
41  * @version $Id$
42  */
43 public class FcpConnection {
44
45         public static final int DEFAULT_PORT = 9481;
46
47         private final Object messageWaitSync = new Object();
48         private FcpMessage receivedMessage = null;
49
50         private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
51
52         private final InetAddress address;
53         private final int port;
54         private final String clientName;
55
56         private Socket remoteSocket;
57         private InputStream remoteInputStream;
58         private OutputStream remoteOutputStream;
59         private FcpConnectionHandler connectionHandler;
60         private boolean connected;
61
62         public FcpConnection(String host, String clientName) throws UnknownHostException {
63                 this(host, DEFAULT_PORT, clientName);
64         }
65
66         public FcpConnection(String host, int port, String clientName) throws UnknownHostException {
67                 this(InetAddress.getByName(host), port, clientName);
68         }
69
70         public FcpConnection(InetAddress address, String clientName) {
71                 this(address, DEFAULT_PORT, clientName);
72         }
73
74         public FcpConnection(InetAddress address, int port, String clientName) {
75                 this.address = address;
76                 this.port = port;
77                 this.clientName = clientName;
78         }
79
80         //
81         // LISTENER MANAGEMENT
82         //
83
84         /**
85          * Adds the given listener to the list of listeners.
86          * 
87          * @param fcpListener
88          *            The listener to add
89          */
90         public void addFcpListener(FcpListener fcpListener) {
91                 fcpListeners.add(fcpListener);
92         }
93
94         /**
95          * Removes the given listener from the list of listeners.
96          * 
97          * @param fcpListener
98          *            The listener to remove
99          */
100         public void removeFcpListener(FcpListener fcpListener) {
101                 fcpListeners.remove(fcpListener);
102         }
103
104         /**
105          * Notifies all registered listeners that a message has been received.
106          * 
107          * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
108          * @param fcpMessage
109          *            The message that was received
110          */
111         private void fireMessageReceived(FcpMessage fcpMessage) {
112                 for (FcpListener fcpListener: fcpListeners) {
113                         fcpListener.receivedMessage(this, fcpMessage);
114                 }
115         }
116
117         //
118         // ACTIONS
119         //
120
121         /**
122          * Connects to the node.
123          * 
124          * @throws IOException
125          *             if an I/O error occurs
126          * @throws IllegalStateException
127          *             if there is already a connection to the node
128          */
129         public synchronized void connect() throws IOException, IllegalStateException {
130                 if (connectionHandler != null) {
131                         throw new IllegalStateException("already connected, disconnect first");
132                 }
133                 remoteSocket = new Socket(address, port);
134                 remoteInputStream = remoteSocket.getInputStream();
135                 remoteOutputStream = remoteSocket.getOutputStream();
136                 connected = true;
137                 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
138         }
139
140         /**
141          * Disconnects from the node. If there is no connection to the node, this
142          * method does nothing.
143          */
144         public synchronized void disconnect() {
145                 if (connectionHandler == null) {
146                         return;
147                 }
148                 connected = false;
149                 Closer.close(remoteSocket);
150                 connectionHandler.stop();
151                 connectionHandler = null;
152         }
153
154         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
155                 System.out.println("sending message: " + fcpMessage.getName());
156                 fcpMessage.write(remoteOutputStream);
157         }
158
159         /**
160          * Sends a “ListPeer” command to the node and returns the properties of the
161          * peer.
162          * 
163          * @param nodeIdentifier
164          *            The name (except for OpenNet nodes), the identity or the
165          *            node’s “address:port” pair
166          * @return The properties of the peer, or <code>null</code> if the peer is
167          *         unknown
168          * @throws IOException
169          * @throws FcpException
170          */
171         public Map<String, String> sendListPeer(String nodeIdentifier) throws IOException, FcpException {
172                 FcpMessage listPeerMessage = new FcpMessage("ListPeer");
173                 listPeerMessage.setField("NodeIdentifier", nodeIdentifier);
174                 sendMessage(listPeerMessage);
175                 FcpMessage returnMessage = waitForMessage("Peer", "UnknownNodeIdentifier");
176                 if (returnMessage.getName().equals("Peer")) {
177                         return returnMessage.getFields();
178                 }
179                 return null;
180         }
181         
182         void handleMessage(FcpMessage fcpMessage) {
183                 fireMessageReceived(fcpMessage);
184         }
185
186         public List<Map<String, String>> sendListPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
187                 FcpMessage listPeersMessage = new FcpMessage("ListPeers");
188                 listPeersMessage.setField("WithMetadata", String.valueOf(withMetadata));
189                 listPeersMessage.setField("WithVolatile", String.valueOf(withVolatile));
190                 sendMessage(listPeersMessage);
191                 List<Map<String, String>> peers = new ArrayList<Map<String, String>>();
192                 while (true) {
193                         FcpMessage returnMessage = waitForMessage("Peer", "EndListPeers");
194                         if (returnMessage.getName().equals("EndListPeers")) {
195                                 break;
196                         }
197                         peers.add(returnMessage.getFields());
198                 }
199                 return peers;
200         }
201
202         public List<Map<String, String>> sendListPeerNotes(String nodeIdentifier) throws IOException, FcpException {
203                 FcpMessage listPeerNotesMessage = new FcpMessage("ListPeerNotes");
204                 listPeerNotesMessage.setField("NodeIdentifier", nodeIdentifier);
205                 sendMessage(listPeerNotesMessage);
206                 List<Map<String, String>> peerNotes = new ArrayList<Map<String, String>>();
207                 while (true) {
208                         FcpMessage returnMessage = waitForMessage("PeerNote", "EndListPeerNotes");
209                         if (returnMessage.getName().equals("EndListPeerNotes")) {
210                                 break;
211                         }
212                         peerNotes.add(returnMessage.getFields());
213                 }
214                 return peerNotes;
215         }
216
217         public void sendTestDDARequest(String directory, boolean wantReadDirectory, boolean wantWriteDirectory) throws IOException, FcpException {
218                 FcpMessage testDDARequestMessage = new FcpMessage("TestDDARequest");
219                 testDDARequestMessage.setField("Directory", directory);
220                 testDDARequestMessage.setField("WantReadDirectory", String.valueOf(wantReadDirectory));
221                 testDDARequestMessage.setField("WantWriteDirectory", String.valueOf(wantWriteDirectory));
222                 sendMessage(testDDARequestMessage);
223         }
224
225         public FcpKeyPair generateSSK() throws IOException, FcpException {
226                 FcpMessage generateSSKMessage = new FcpMessage("GenerateSSK");
227                 String identifier = hashCode() + String.valueOf(System.currentTimeMillis());
228                 generateSSKMessage.setField("Identifier", identifier);
229                 sendMessage(generateSSKMessage);
230                 FcpMessage returnMessage = waitForMessage("SSKKeypair(Identifier=" + identifier + ")");
231                 String publicKey = returnMessage.getField("RequestURI");
232                 String privateKey = returnMessage.getField("InsertURI");
233                 return new FcpKeyPair(publicKey, privateKey);
234         }
235
236         //
237         // PRIVATE METHODS
238         //
239
240         public FcpMessage waitForMessage(String... messageNames) throws FcpException {
241                 FcpMessage oldMessage = null;
242                 synchronized (messageWaitSync) {
243                         while (true) {
244                                 while (receivedMessage == oldMessage) {
245                                         System.out.println("waiting for receivedMessage");
246                                         try {
247                                                 messageWaitSync.wait();
248                                         } catch (InterruptedException ie1) {
249                                         }
250                                 }
251                                 System.out.println("got message: " + receivedMessage.getName());
252                                 String receivedMessageName = receivedMessage.getName();
253                                 if ("ProtocolError".equals(receivedMessageName)) {
254                                         int code = Integer.valueOf(receivedMessage.getField("Code"));
255                                         boolean fatal = Boolean.valueOf(receivedMessage.getField("Fatal"));
256                                         boolean global = Boolean.valueOf(receivedMessage.getField("Global"));
257                                         String codeDescription = receivedMessage.getField("CodeDescription");
258                                         String extraDescription = receivedMessage.getField("ExtraDescription");
259                                         String identifier = receivedMessage.getField("Identifier");
260                                         FcpProtocolException fcpProtocolException = new FcpProtocolException(code, fatal, global);
261                                         fcpProtocolException.setCodeDescription(codeDescription);
262                                         fcpProtocolException.setExtraDescription(extraDescription);
263                                         fcpProtocolException.setIdentifier(identifier);
264                                         throw fcpProtocolException;
265                                 }
266                                 for (String messageName: messageNames) {
267                                         int firstBracket = messageName.indexOf('(');
268                                         Map<String, String> wantedIdentifiers = new HashMap<String, String>();
269                                         if (firstBracket > -1) {
270                                                 StringTokenizer identifierTokens = new StringTokenizer(messageName.substring(firstBracket), "()");
271                                                 while (identifierTokens.hasMoreTokens()) {
272                                                         String identifierToken = identifierTokens.nextToken();
273                                                         int equalSign = identifierToken.indexOf('=');
274                                                         if (equalSign > -1) {
275                                                                 wantedIdentifiers.put(identifierToken.substring(0, equalSign), identifierToken.substring(equalSign + 1));
276                                                         }
277                                                 }
278                                                 messageName = messageName.substring(0, firstBracket);
279                                         }
280                                         if (receivedMessageName.equals(messageName)) {
281                                                 boolean found = true;
282                                                 for (Entry<String, String> wantedIdentifier: wantedIdentifiers.entrySet()) {
283                                                         System.out.println("key: " + wantedIdentifier.getKey() + ", value: " + wantedIdentifier.getValue() + ", msg: " + receivedMessage.getField(wantedIdentifier.getKey()));
284                                                         if (!wantedIdentifier.getValue().equals(receivedMessage.getField(wantedIdentifier.getKey()))) {
285                                                                 found = false;
286                                                                 break;
287                                                         }
288                                                 }
289                                                 if (found) {
290                                                         System.out.println("message found");
291                                                         FcpMessage foundMessage = receivedMessage;
292                                                         receivedMessage = null;
293                                                         messageWaitSync.notifyAll();
294                                                         return foundMessage;
295                                                 }
296                                         }
297                                 }
298                                 oldMessage = receivedMessage;
299                         }
300                 }
301         }
302
303 }