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