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 boolean connected;
60
61         public FcpConnection(String host, String clientName) throws UnknownHostException {
62                 this(host, DEFAULT_PORT, clientName);
63         }
64
65         public FcpConnection(String host, int port, String clientName) throws UnknownHostException {
66                 this(InetAddress.getByName(host), port, clientName);
67         }
68
69         public FcpConnection(InetAddress address, String clientName) {
70                 this(address, DEFAULT_PORT, clientName);
71         }
72
73         public FcpConnection(InetAddress address, int port, String clientName) {
74                 this.address = address;
75                 this.port = port;
76                 this.clientName = clientName;
77         }
78
79         //
80         // LISTENER MANAGEMENT
81         //
82
83         public void addFcpListener(FcpListener fcpListener) {
84                 fcpListeners.add(fcpListener);
85         }
86
87         public void removeFcpListener(FcpListener fcpListener) {
88                 fcpListeners.remove(fcpListener);
89         }
90
91         private void fireNodeHello(Map<String, String> nodeProperties) {
92                 for (FcpListener fcpListener: fcpListeners) {
93                         fcpListener.fcpNodeHello(this, nodeProperties);
94                 }
95         }
96
97         //
98         // ACTIONS
99         //
100
101         public synchronized void connect() throws FcpException, IOException {
102                 System.out.println("connecting...");
103                 remoteSocket = new Socket(address, port);
104                 remoteInputStream = remoteSocket.getInputStream();
105                 remoteOutputStream = remoteSocket.getOutputStream();
106                 connected = true;
107                 System.out.println("connected.");
108                 new Thread(new FcpConnectionHandler(this, remoteInputStream)).start();
109                 sendMessage(clientHelloMessage);
110         }
111
112         public synchronized void disconnect() {
113                 connected = false;
114                 Closer.close(remoteSocket);
115         }
116
117         /**
118          * Sends a “ListPeer” command to the node and returns the properties of the
119          * peer.
120          * 
121          * @param nodeIdentifier
122          *            The name (except for OpenNet nodes), the identity or the
123          *            node’s “address:port” pair
124          * @return The properties of the peer, or <code>null</code> if the peer is
125          *         unknown
126          * @throws IOException
127          * @throws FcpException 
128          */
129         public Map<String, String> sendListPeer(String nodeIdentifier) throws IOException, FcpException {
130                 FcpMessage listPeerMessage = new FcpMessage("ListPeer");
131                 listPeerMessage.setField("NodeIdentifier", nodeIdentifier);
132                 sendMessage(listPeerMessage);
133                 FcpMessage returnMessage = waitForMessage("Peer", "UnknownNodeIdentifier");
134                 if (returnMessage.getName().equals("Peer")) {
135                         return returnMessage.getFields();
136                 }
137                 return null;
138         }
139         
140         public List<Map<String, String>> sendListPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
141                 FcpMessage listPeersMessage = new FcpMessage("ListPeers");
142                 listPeersMessage.setField("WithMetadata", String.valueOf(withMetadata));
143                 listPeersMessage.setField("WithVolatile", String.valueOf(withVolatile));
144                 sendMessage(listPeersMessage);
145                 List<Map<String, String>> peers = new ArrayList<Map<String, String>>();
146                 while (true) {
147                         FcpMessage returnMessage = waitForMessage("Peer", "EndListPeers");
148                         if (returnMessage.getName().equals("EndListPeers")) {
149                                 break;
150                         }
151                         peers.add(returnMessage.getFields());
152                 }
153                 return peers;
154         }
155
156         public List<Map<String, String>> sendListPeerNotes(String nodeIdentifier) throws IOException, FcpException {
157                 FcpMessage listPeerNotesMessage = new FcpMessage("ListPeerNotes");
158                 listPeerNotesMessage.setField("NodeIdentifier", nodeIdentifier);
159                 sendMessage(listPeerNotesMessage);
160                 List<Map<String, String>> peerNotes = new ArrayList<Map<String, String>>();
161                 while (true) {
162                         FcpMessage returnMessage = waitForMessage("PeerNote", "EndListPeerNotes");
163                         if (returnMessage.getName().equals("EndListPeerNotes")) {
164                                 break;
165                         }
166                         peerNotes.add(returnMessage.getFields());
167                 }
168                 return peerNotes;
169         }
170         
171         public void sendTestDDARequest(String directory, boolean wantReadDirectory, boolean wantWriteDirectory) throws IOException, FcpException {
172                 FcpMessage testDDARequestMessage = new FcpMessage("TestDDARequest");
173                 testDDARequestMessage.setField("Directory", directory);
174                 testDDARequestMessage.setField("WantReadDirectory", String.valueOf(wantReadDirectory));
175                 testDDARequestMessage.setField("WantWriteDirectory", String.valueOf(wantWriteDirectory));
176                 sendMessage(testDDARequestMessage);
177         }
178         
179         public FcpKeyPair generateSSK() throws IOException, FcpException {
180                 FcpMessage generateSSKMessage = new FcpMessage("GenerateSSK");
181                 String identifier = hashCode() + String.valueOf(System.currentTimeMillis());
182                 generateSSKMessage.setField("Identifier", identifier);
183                 sendMessage(generateSSKMessage);
184                 FcpMessage returnMessage = waitForMessage("SSKKeypair(Identifier=" + identifier + ")");
185                 String publicKey = returnMessage.getField("RequestURI");
186                 String privateKey = returnMessage.getField("InsertURI");
187                 return new FcpKeyPair(publicKey, privateKey);
188         }
189         
190         //
191         // PACKAGE-PRIVATE METHODS
192         //
193
194         void handleMessage(FcpMessage fcpMessage) {
195                 synchronized (messageWaitSync) {
196                         while (receivedMessage != null) {
197                                 /* previous message has not yet been consumed */
198                                 System.out.println("waiting for message to be consumed...");
199                                 try {
200                                         messageWaitSync.wait();
201                                 } catch (InterruptedException ie1) {
202                                 }
203                         }
204                         /* TODO - check whether to send events here or later. */
205                         if ("NodeHello".equals(fcpMessage.getName())) {
206                                 fireNodeHello(fcpMessage.getFields());
207                         }
208                         System.out.println("setting receivedMessage");
209                         receivedMessage = fcpMessage;
210                         messageWaitSync.notifyAll();
211                 }
212         }
213
214         //
215         // PRIVATE METHODS
216         //
217
218         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
219                 System.out.println("sending message: " + fcpMessage.getName());
220                 fcpMessage.write(remoteOutputStream);
221         }
222
223         public FcpMessage waitForMessage(String... messageNames) throws FcpException {
224                 FcpMessage oldMessage = null;
225                 synchronized (messageWaitSync) {
226                         while (true) {
227                                 while (receivedMessage == oldMessage) {
228                                         System.out.println("waiting for receivedMessage");
229                                         try {
230                                                 messageWaitSync.wait();
231                                         } catch (InterruptedException ie1) {
232                                         }
233                                 }
234                                 System.out.println("got message: " + receivedMessage.getName());
235                                 String receivedMessageName = receivedMessage.getName();
236                                 if ("ProtocolError".equals(receivedMessageName)) {
237                                         int code = Integer.valueOf(receivedMessage.getField("Code"));
238                                         boolean fatal = Boolean.valueOf(receivedMessage.getField("Fatal"));
239                                         boolean global = Boolean.valueOf(receivedMessage.getField("Global"));
240                                         String codeDescription = receivedMessage.getField("CodeDescription");
241                                         String extraDescription = receivedMessage.getField("ExtraDescription");
242                                         String identifier = receivedMessage.getField("Identifier");
243                                         FcpProtocolException fcpProtocolException = new FcpProtocolException(code, fatal, global);
244                                         fcpProtocolException.setCodeDescription(codeDescription);
245                                         fcpProtocolException.setExtraDescription(extraDescription);
246                                         fcpProtocolException.setIdentifier(identifier);
247                                         throw fcpProtocolException;
248                                 }
249                                 for (String messageName: messageNames) {
250                                         int firstBracket = messageName.indexOf('(');
251                                         Map<String, String> wantedIdentifiers = new HashMap<String, String>();
252                                         if (firstBracket > -1) {
253                                                 StringTokenizer identifierTokens = new StringTokenizer(messageName.substring(firstBracket), "()");
254                                                 while (identifierTokens.hasMoreTokens()) {
255                                                         String identifierToken = identifierTokens.nextToken();
256                                                         int equalSign = identifierToken.indexOf('=');
257                                                         if (equalSign > -1) {
258                                                                 wantedIdentifiers.put(identifierToken.substring(0, equalSign), identifierToken.substring(equalSign + 1));
259                                                         }
260                                                 }
261                                                 messageName = messageName.substring(0, firstBracket);
262                                         }
263                                         if (receivedMessageName.equals(messageName)) {
264                                                 boolean found = true;
265                                                 for (Entry<String, String> wantedIdentifier: wantedIdentifiers.entrySet()) {
266                                                         System.out.println("key: " + wantedIdentifier.getKey() + ", value: " + wantedIdentifier.getValue() + ", msg: " + receivedMessage.getField(wantedIdentifier.getKey()));
267                                                         if (!wantedIdentifier.getValue().equals(receivedMessage.getField(wantedIdentifier.getKey()))) {
268                                                                 found = false;
269                                                                 break;
270                                                         }
271                                                 }
272                                                 if (found) {
273                                                         System.out.println("message found");
274                                                         FcpMessage foundMessage = receivedMessage;
275                                                         receivedMessage = null;
276                                                         messageWaitSync.notifyAll();
277                                                         return foundMessage;
278                                                 }
279                                         }
280                                 }
281                                 oldMessage = receivedMessage;
282                         }
283                 }
284         }
285
286 }