96f2ea0e903208212335e9a53c1cbe5b2dbde98d
[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.List;
30
31 import net.pterodactylus.util.io.Closer;
32 import net.pterodactylus.util.io.LimitedInputStream;
33
34 /**
35  * An FCP connection to a Freenet node.
36  * 
37  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
38  * @version $Id$
39  */
40 public class FcpConnection {
41
42         /** The default port for FCP v2. */
43         public static final int DEFAULT_PORT = 9481;
44
45         /** The list of FCP listeners. */
46         private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
47
48         /** The address of the node. */
49         private final InetAddress address;
50
51         /** The port number of the node’s FCP port. */
52         private final int port;
53
54         /** The remote socket. */
55         private Socket remoteSocket;
56
57         /** The input stream from the node. */
58         private InputStream remoteInputStream;
59
60         /** The output stream to the node. */
61         private OutputStream remoteOutputStream;
62
63         /** The connection handler. */
64         private FcpConnectionHandler connectionHandler;
65
66         /**
67          * Creates a new FCP connection to the Freenet node running on the given
68          * host, listening on the default port.
69          * 
70          * @param host
71          *            The hostname of the Freenet node
72          * @throws UnknownHostException
73          *             if <code>host</code> can not be resolved
74          */
75         public FcpConnection(String host) throws UnknownHostException {
76                 this(host, DEFAULT_PORT);
77         }
78
79         /**
80          * Creates a new FCP connection to the Freenet node running on the given
81          * host, listening on the given port.
82          * 
83          * @param host
84          *            The hostname of the Freenet node
85          * @param port
86          *            The port number of the node’s FCP port
87          * @throws UnknownHostException
88          *             if <code>host</code> can not be resolved
89          */
90         public FcpConnection(String host, int port) throws UnknownHostException {
91                 this(InetAddress.getByName(host), port);
92         }
93
94         /**
95          * Creates a new FCP connection to the Freenet node running at the given
96          * address, listening on the default port.
97          * 
98          * @param address
99          *            The address of the Freenet node
100          */
101         public FcpConnection(InetAddress address) {
102                 this(address, DEFAULT_PORT);
103         }
104
105         /**
106          * Creates a new FCP connection to the Freenet node running at the given
107          * address, listening on the given port.
108          * 
109          * @param address
110          *            The address of the Freenet node
111          * @param port
112          *            The port number of the node’s FCP port
113          */
114         public FcpConnection(InetAddress address, int port) {
115                 this.address = address;
116                 this.port = port;
117         }
118
119         //
120         // LISTENER MANAGEMENT
121         //
122
123         /**
124          * Adds the given listener to the list of listeners.
125          * 
126          * @param fcpListener
127          *            The listener to add
128          */
129         public void addFcpListener(FcpListener fcpListener) {
130                 fcpListeners.add(fcpListener);
131         }
132
133         /**
134          * Removes the given listener from the list of listeners.
135          * 
136          * @param fcpListener
137          *            The listener to remove
138          */
139         public void removeFcpListener(FcpListener fcpListener) {
140                 fcpListeners.remove(fcpListener);
141         }
142
143         /**
144          * Notifies listeners that a “NodeHello” message was received.
145          * 
146          * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
147          * @param nodeHello
148          *            The “NodeHello” message
149          */
150         private void fireReceivedNodeHello(NodeHello nodeHello) {
151                 for (FcpListener fcpListener: fcpListeners) {
152                         fcpListener.receivedNodeHello(this, nodeHello);
153                 }
154         }
155
156         /**
157          * Notifies listeners that a “CloseConnectionDuplicateClientName” message
158          * was received.
159          * 
160          * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
161          *      CloseConnectionDuplicateClientName)
162          * @param closeConnectionDuplicateClientName
163          *            The “CloseConnectionDuplicateClientName” message
164          */
165         private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
166                 for (FcpListener fcpListener: fcpListeners) {
167                         fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
168                 }
169         }
170
171         /**
172          * Notifies listeners that a “SSKKeypair” message was received.
173          * 
174          * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
175          * @param sskKeypair
176          *            The “SSKKeypair” message
177          */
178         private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
179                 for (FcpListener fcpListener: fcpListeners) {
180                         fcpListener.receivedSSKKeypair(this, sskKeypair);
181                 }
182         }
183
184         /**
185          * Notifies listeners that a “Peer” message was received.
186          * 
187          * @see FcpListener#receivedPeer(FcpConnection, Peer)
188          * @param peer
189          *            The “Peer” message
190          */
191         private void fireReceivedPeer(Peer peer) {
192                 for (FcpListener fcpListener: fcpListeners) {
193                         fcpListener.receivedPeer(this, peer);
194                 }
195         }
196
197         /**
198          * Notifies all listeners that an “EndListPeers” message was received.
199          * 
200          * @see FcpListener#receivedEndListPeers(FcpConnection, EndListPeers)
201          * @param endListPeers
202          *            The “EndListPeers” message
203          */
204         private void fireReceivedEndListPeers(EndListPeers endListPeers) {
205                 for (FcpListener fcpListener: fcpListeners) {
206                         fcpListener.receivedEndListPeers(this, endListPeers);
207                 }
208         }
209
210         /**
211          * Notifies all listeners that a “PeerNote” message was received.
212          * 
213          * @see FcpListener#receivedPeerNote(FcpConnection, PeerNote)
214          * @param peerNote
215          */
216         private void fireReceivedPeerNote(PeerNote peerNote) {
217                 for (FcpListener fcpListener: fcpListeners) {
218                         fcpListener.receivedPeerNote(this, peerNote);
219                 }
220         }
221
222         /**
223          * Notifies all listeners that an “EndListPeerNotes” message was received.
224          * 
225          * @see FcpListener#receivedEndListPeerNotes(FcpConnection,
226          *      EndListPeerNotes)
227          * @param endListPeerNotes
228          *            The “EndListPeerNotes” message
229          */
230         private void fireReceivedEndListPeerNotes(EndListPeerNotes endListPeerNotes) {
231                 for (FcpListener fcpListener: fcpListeners) {
232                         fcpListener.receivedEndListPeerNotes(this, endListPeerNotes);
233                 }
234         }
235
236         /**
237          * Notifies all listeners that a “PeerRemoved” message was received.
238          * 
239          * @see FcpListener#receivedPeerRemoved(FcpConnection, PeerRemoved)
240          * @param peerRemoved
241          *            The “PeerRemoved” message
242          */
243         private void fireReceivedPeerRemoved(PeerRemoved peerRemoved) {
244                 for (FcpListener fcpListener: fcpListeners) {
245                         fcpListener.receivedPeerRemoved(this, peerRemoved);
246                 }
247         }
248
249         /**
250          * Notifies all listeners that a “NodeData” message was received.
251          * 
252          * @see FcpListener#receivedNodeData(FcpConnection, NodeData)
253          * @param nodeData
254          *            The “NodeData” message
255          */
256         private void fireReceivedNodeData(NodeData nodeData) {
257                 for (FcpListener fcpListener: fcpListeners) {
258                         fcpListener.receivedNodeData(this, nodeData);
259                 }
260         }
261
262         /**
263          * Notifies all listeners that a “TestDDAReply” message was received.
264          * 
265          * @see FcpListener#receivedTestDDAReply(FcpConnection, TestDDAReply)
266          * @param testDDAReply
267          *            The “TestDDAReply” message
268          */
269         private void fireReceivedTestDDAReply(TestDDAReply testDDAReply) {
270                 for (FcpListener fcpListener: fcpListeners) {
271                         fcpListener.receivedTestDDAReply(this, testDDAReply);
272                 }
273         }
274
275         /**
276          * Notifies all listeners that a “TestDDAComplete” message was received.
277          * 
278          * @see FcpListener#receivedTestDDAComplete(FcpConnection, TestDDAComplete)
279          * @param testDDAComplete
280          *            The “TestDDAComplete” message
281          */
282         private void fireReceivedTestDDAComplete(TestDDAComplete testDDAComplete) {
283                 for (FcpListener fcpListener: fcpListeners) {
284                         fcpListener.receivedTestDDAComplete(this, testDDAComplete);
285                 }
286         }
287
288         /**
289          * Notifies all listeners that a “PersistentPut” message was received.
290          * 
291          * @see FcpListener#receivedPersistentPut(FcpConnection, PersistentPut)
292          * @param persistentPut
293          *            The “PersistentPut” message
294          */
295         private void fireReceivedPersistentPut(PersistentPut persistentPut) {
296                 for (FcpListener fcpListener: fcpListeners) {
297                         fcpListener.receivedPersistentPut(this, persistentPut);
298                 }
299         }
300
301         /**
302          * Notifies all listeners that a “EndListPersistentRequests” message was
303          * received.
304          * 
305          * @param endListPersistentRequests
306          *            The “EndListPersistentRequests” message
307          */
308         private void fireReceivedEndListPersistentRequests(EndListPersistentRequests endListPersistentRequests) {
309                 for (FcpListener fcpListener: fcpListeners) {
310                         fcpListener.receivedEndListPersistentRequests(this, endListPersistentRequests);
311                 }
312         }
313
314         /**
315          * Notifies all listeners that a “URIGenerated” message was received.
316          * 
317          * @param uriGenerated
318          *            The “URIGenerated” message
319          */
320         private void fireReceivedURIGenerated(URIGenerated uriGenerated) {
321                 for (FcpListener fcpListener: fcpListeners) {
322                         fcpListener.receivedURIGenerated(this, uriGenerated);
323                 }
324         }
325
326         /**
327          * Notifies all listeners that an “AllData” message was received.
328          * 
329          * @param allData
330          *            The “AllData” message
331          */
332         private void fireReceivedAllData(AllData allData) {
333                 for (FcpListener fcpListener: fcpListeners) {
334                         fcpListener.receivedAllData(this, allData);
335                 }
336         }
337
338         /**
339          * Notifies all listeners that a “ProtocolError” message was received.
340          * 
341          * @param protocolError
342          *            The “ProtocolError” message
343          */
344         private void fireReceivedProtocolError(ProtocolError protocolError) {
345                 for (FcpListener fcpListener: fcpListeners) {
346                         fcpListener.receivedProtocolError(this, protocolError);
347                 }
348         }
349
350         /**
351          * Notifies all registered listeners that a message has been received.
352          * 
353          * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
354          * @param fcpMessage
355          *            The message that was received
356          */
357         private void fireMessageReceived(FcpMessage fcpMessage) {
358                 for (FcpListener fcpListener: fcpListeners) {
359                         fcpListener.receivedMessage(this, fcpMessage);
360                 }
361         }
362
363         //
364         // ACTIONS
365         //
366
367         /**
368          * Connects to the node.
369          * 
370          * @throws IOException
371          *             if an I/O error occurs
372          * @throws IllegalStateException
373          *             if there is already a connection to the node
374          */
375         public synchronized void connect() throws IOException, IllegalStateException {
376                 if (connectionHandler != null) {
377                         throw new IllegalStateException("already connected, disconnect first");
378                 }
379                 remoteSocket = new Socket(address, port);
380                 remoteInputStream = remoteSocket.getInputStream();
381                 remoteOutputStream = remoteSocket.getOutputStream();
382                 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
383         }
384
385         /**
386          * Disconnects from the node. If there is no connection to the node, this
387          * method does nothing.
388          */
389         public synchronized void disconnect() {
390                 if (connectionHandler == null) {
391                         return;
392                 }
393                 Closer.close(remoteSocket);
394                 connectionHandler.stop();
395                 connectionHandler = null;
396         }
397
398         /**
399          * Sends the given FCP message.
400          * 
401          * @param fcpMessage
402          *            The FCP message to send
403          * @throws IOException
404          *             if an I/O error occurs
405          */
406         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
407                 System.out.println("sending message: " + fcpMessage.getName());
408                 fcpMessage.write(remoteOutputStream);
409         }
410
411         //
412         // PACKAGE-PRIVATE METHODS
413         //
414
415         /**
416          * Handles the given message, notifying listeners. This message should only
417          * be called by {@link FcpConnectionHandler}.
418          * 
419          * @param fcpMessage
420          *            The received message
421          */
422         void handleMessage(FcpMessage fcpMessage) {
423                 String messageName = fcpMessage.getName();
424                 if ("ProtocolError".equals(messageName)) {
425                         fireReceivedProtocolError(new ProtocolError(fcpMessage));
426                 } else if ("PersistentPut".equals(messageName)) {
427                         fireReceivedPersistentPut(new PersistentPut(fcpMessage));
428                 } else if ("URIGenerated".equals(messageName)) {
429                         fireReceivedURIGenerated(new URIGenerated(fcpMessage));
430                 } else if ("EndListPersistentRequests".equals(messageName)) {
431                         fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
432                 } else if ("Peer".equals(messageName)) {
433                         fireReceivedPeer(new Peer(fcpMessage));
434                 } else if ("PeerNote".equals(messageName)) {
435                         fireReceivedPeerNote(new PeerNote(fcpMessage));
436                 } else if ("AllData".equals(messageName)) {
437                         long dataLength;
438                         try {
439                                 dataLength = Long.valueOf(fcpMessage.getField("DataLength"));
440                         } catch (NumberFormatException nfe1) {
441                                 dataLength = -1;
442                         }
443                         LimitedInputStream payloadInputStream = new LimitedInputStream(remoteInputStream, dataLength);
444                         fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
445                         try {
446                                 payloadInputStream.consume();
447                         } catch (IOException ioe1) {
448                                 /* FIXME - what now? */
449                                 /* well, ignore. when the connection handler fails, all fails. */
450                         }
451                 } else if ("EndListPeerNotes".equals(messageName)) {
452                         fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
453                 } else if ("EndListPeers".equals(messageName)) {
454                         fireReceivedEndListPeers(new EndListPeers(fcpMessage));
455                 } else if ("SSKKeypair".equals(messageName)) {
456                         fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
457                 } else if ("PeerRemoved".equals(messageName)) {
458                         fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
459                 } else if ("NodeData".equals(messageName)) {
460                         fireReceivedNodeData(new NodeData(fcpMessage));
461                 } else if ("TestDDAReply".equals(messageName)) {
462                         fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
463                 } else if ("TestDDAComplete".equals(messageName)) {
464                         fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
465                 } else if ("NodeHello".equals(messageName)) {
466                         fireReceivedNodeHello(new NodeHello(fcpMessage));
467                 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
468                         fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
469                 } else {
470                         fireMessageReceived(fcpMessage);
471                 }
472         }
473
474 }