2 * jSite2 - FpcConnection.java -
3 * Copyright © 2008 David Roden
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.
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.
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.
20 package net.pterodactylus.util.fcp;
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;
34 import net.pterodactylus.util.io.Closer;
35 import net.pterodactylus.util.io.LimitedInputStream;
38 * An FCP connection to a Freenet node.
40 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
43 public class FcpConnection {
45 /** The default port for FCP v2. */
46 public static final int DEFAULT_PORT = 9481;
48 /** The list of FCP listeners. */
49 private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
51 /** The address of the node. */
52 private final InetAddress address;
54 /** The port number of the node’s FCP port. */
55 private final int port;
57 /** The remote socket. */
58 private Socket remoteSocket;
60 /** The input stream from the node. */
61 private InputStream remoteInputStream;
63 /** The output stream to the node. */
64 private OutputStream remoteOutputStream;
66 /** The connection handler. */
67 private FcpConnectionHandler connectionHandler;
69 /** Incoming message statistics. */
70 private Map<String, Integer> incomingMessageStatistics = Collections.synchronizedMap(new HashMap<String, Integer>());
73 * Creates a new FCP connection to the freenet node running on localhost,
74 * using the default port.
76 * @throws UnknownHostException
77 * if the hostname can not be resolved
79 public FcpConnection() throws UnknownHostException {
80 this(InetAddress.getLocalHost());
84 * Creates a new FCP connection to the Freenet node running on the given
85 * host, listening on the default port.
88 * The hostname of the Freenet node
89 * @throws UnknownHostException
90 * if <code>host</code> can not be resolved
92 public FcpConnection(String host) throws UnknownHostException {
93 this(host, DEFAULT_PORT);
97 * Creates a new FCP connection to the Freenet node running on the given
98 * host, listening on the given port.
101 * The hostname of the Freenet node
103 * The port number of the node’s FCP port
104 * @throws UnknownHostException
105 * if <code>host</code> can not be resolved
107 public FcpConnection(String host, int port) throws UnknownHostException {
108 this(InetAddress.getByName(host), port);
112 * Creates a new FCP connection to the Freenet node running at the given
113 * address, listening on the default port.
116 * The address of the Freenet node
118 public FcpConnection(InetAddress address) {
119 this(address, DEFAULT_PORT);
123 * Creates a new FCP connection to the Freenet node running at the given
124 * address, listening on the given port.
127 * The address of the Freenet node
129 * The port number of the node’s FCP port
131 public FcpConnection(InetAddress address, int port) {
132 this.address = address;
137 // LISTENER MANAGEMENT
141 * Adds the given listener to the list of listeners.
144 * The listener to add
146 public void addFcpListener(FcpListener fcpListener) {
147 fcpListeners.add(fcpListener);
151 * Removes the given listener from the list of listeners.
154 * The listener to remove
156 public void removeFcpListener(FcpListener fcpListener) {
157 fcpListeners.remove(fcpListener);
161 * Notifies listeners that a “NodeHello” message was received.
163 * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
165 * The “NodeHello” message
167 private void fireReceivedNodeHello(NodeHello nodeHello) {
168 for (FcpListener fcpListener: fcpListeners) {
169 fcpListener.receivedNodeHello(this, nodeHello);
174 * Notifies listeners that a “CloseConnectionDuplicateClientName” message
177 * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
178 * CloseConnectionDuplicateClientName)
179 * @param closeConnectionDuplicateClientName
180 * The “CloseConnectionDuplicateClientName” message
182 private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
183 for (FcpListener fcpListener: fcpListeners) {
184 fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
189 * Notifies listeners that a “SSKKeypair” message was received.
191 * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
193 * The “SSKKeypair” message
195 private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
196 for (FcpListener fcpListener: fcpListeners) {
197 fcpListener.receivedSSKKeypair(this, sskKeypair);
202 * Notifies listeners that a “Peer” message was received.
204 * @see FcpListener#receivedPeer(FcpConnection, Peer)
208 private void fireReceivedPeer(Peer peer) {
209 for (FcpListener fcpListener: fcpListeners) {
210 fcpListener.receivedPeer(this, peer);
215 * Notifies all listeners that an “EndListPeers” message was received.
217 * @see FcpListener#receivedEndListPeers(FcpConnection, EndListPeers)
218 * @param endListPeers
219 * The “EndListPeers” message
221 private void fireReceivedEndListPeers(EndListPeers endListPeers) {
222 for (FcpListener fcpListener: fcpListeners) {
223 fcpListener.receivedEndListPeers(this, endListPeers);
228 * Notifies all listeners that a “PeerNote” message was received.
230 * @see FcpListener#receivedPeerNote(FcpConnection, PeerNote)
233 private void fireReceivedPeerNote(PeerNote peerNote) {
234 for (FcpListener fcpListener: fcpListeners) {
235 fcpListener.receivedPeerNote(this, peerNote);
240 * Notifies all listeners that an “EndListPeerNotes” message was received.
242 * @see FcpListener#receivedEndListPeerNotes(FcpConnection,
244 * @param endListPeerNotes
245 * The “EndListPeerNotes” message
247 private void fireReceivedEndListPeerNotes(EndListPeerNotes endListPeerNotes) {
248 for (FcpListener fcpListener: fcpListeners) {
249 fcpListener.receivedEndListPeerNotes(this, endListPeerNotes);
254 * Notifies all listeners that a “PeerRemoved” message was received.
256 * @see FcpListener#receivedPeerRemoved(FcpConnection, PeerRemoved)
258 * The “PeerRemoved” message
260 private void fireReceivedPeerRemoved(PeerRemoved peerRemoved) {
261 for (FcpListener fcpListener: fcpListeners) {
262 fcpListener.receivedPeerRemoved(this, peerRemoved);
267 * Notifies all listeners that a “NodeData” message was received.
269 * @see FcpListener#receivedNodeData(FcpConnection, NodeData)
271 * The “NodeData” message
273 private void fireReceivedNodeData(NodeData nodeData) {
274 for (FcpListener fcpListener: fcpListeners) {
275 fcpListener.receivedNodeData(this, nodeData);
280 * Notifies all listeners that a “TestDDAReply” message was received.
282 * @see FcpListener#receivedTestDDAReply(FcpConnection, TestDDAReply)
283 * @param testDDAReply
284 * The “TestDDAReply” message
286 private void fireReceivedTestDDAReply(TestDDAReply testDDAReply) {
287 for (FcpListener fcpListener: fcpListeners) {
288 fcpListener.receivedTestDDAReply(this, testDDAReply);
293 * Notifies all listeners that a “TestDDAComplete” message was received.
295 * @see FcpListener#receivedTestDDAComplete(FcpConnection, TestDDAComplete)
296 * @param testDDAComplete
297 * The “TestDDAComplete” message
299 private void fireReceivedTestDDAComplete(TestDDAComplete testDDAComplete) {
300 for (FcpListener fcpListener: fcpListeners) {
301 fcpListener.receivedTestDDAComplete(this, testDDAComplete);
306 * Notifies all listeners that a “PersistentGet” message was received.
308 * @param persistentGet
309 * The “PersistentGet” message
311 private void fireReceivedPersistentGet(PersistentGet persistentGet) {
312 for (FcpListener fcpListener: fcpListeners) {
313 fcpListener.receivedPersistentGet(this, persistentGet);
318 * Notifies all listeners that a “PersistentPut” message was received.
320 * @see FcpListener#receivedPersistentPut(FcpConnection, PersistentPut)
321 * @param persistentPut
322 * The “PersistentPut” message
324 private void fireReceivedPersistentPut(PersistentPut persistentPut) {
325 for (FcpListener fcpListener: fcpListeners) {
326 fcpListener.receivedPersistentPut(this, persistentPut);
331 * Notifies all listeners that a “EndListPersistentRequests” message was
334 * @param endListPersistentRequests
335 * The “EndListPersistentRequests” message
337 private void fireReceivedEndListPersistentRequests(EndListPersistentRequests endListPersistentRequests) {
338 for (FcpListener fcpListener: fcpListeners) {
339 fcpListener.receivedEndListPersistentRequests(this, endListPersistentRequests);
344 * Notifies all listeners that a “URIGenerated” message was received.
346 * @param uriGenerated
347 * The “URIGenerated” message
349 private void fireReceivedURIGenerated(URIGenerated uriGenerated) {
350 for (FcpListener fcpListener: fcpListeners) {
351 fcpListener.receivedURIGenerated(this, uriGenerated);
356 * Notifies all listeners that a “DataFound” message was received.
359 * The “DataFound” message
361 private void fireReceivedDataFound(DataFound dataFound) {
362 for (FcpListener fcpListener: fcpListeners) {
363 fcpListener.receivedDataFound(this, dataFound);
368 * Notifies all listeners that an “AllData” message was received.
371 * The “AllData” message
373 private void fireReceivedAllData(AllData allData) {
374 for (FcpListener fcpListener: fcpListeners) {
375 fcpListener.receivedAllData(this, allData);
380 * Notifies all listeners that a “SimpleProgress” message was received.
382 * @param simpleProgress
383 * The “SimpleProgress” message
385 private void fireReceivedSimpleProgress(SimpleProgress simpleProgress) {
386 for (FcpListener fcpListener: fcpListeners) {
387 fcpListener.receivedSimpleProgress(this, simpleProgress);
392 * Notifies all listeners that a “StartedCompression” message was received.
394 * @param startedCompression
395 * The “StartedCompression” message
397 private void fireReceivedStartedCompression(StartedCompression startedCompression) {
398 for (FcpListener fcpListener: fcpListeners) {
399 fcpListener.receivedStartedCompression(this, startedCompression);
404 * Notifies all listeners that a “FinishedCompression” message was received.
406 * @param finishedCompression
407 * The “FinishedCompression” message
409 private void fireReceivedFinishedCompression(FinishedCompression finishedCompression) {
410 for (FcpListener fcpListener: fcpListeners) {
411 fcpListener.receviedFinishedCompression(this, finishedCompression);
416 * Notifies all listeners that an “UnknownPeerNoteType” message was
419 * @param unknownPeerNoteType
420 * The “UnknownPeerNoteType” message
422 private void fireReceivedUnknownPeerNoteType(UnknownPeerNoteType unknownPeerNoteType) {
423 for (FcpListener fcpListener: fcpListeners) {
424 fcpListener.receivedUnknownPeerNoteType(this, unknownPeerNoteType);
429 * Notifies all listeners that a “ProtocolError” message was received.
431 * @param protocolError
432 * The “ProtocolError” message
434 private void fireReceivedProtocolError(ProtocolError protocolError) {
435 for (FcpListener fcpListener: fcpListeners) {
436 fcpListener.receivedProtocolError(this, protocolError);
441 * Notifies all registered listeners that a message has been received.
443 * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
445 * The message that was received
447 private void fireMessageReceived(FcpMessage fcpMessage) {
448 for (FcpListener fcpListener: fcpListeners) {
449 fcpListener.receivedMessage(this, fcpMessage);
458 * Connects to the node.
460 * @throws IOException
461 * if an I/O error occurs
462 * @throws IllegalStateException
463 * if there is already a connection to the node
465 public synchronized void connect() throws IOException, IllegalStateException {
466 if (connectionHandler != null) {
467 throw new IllegalStateException("already connected, disconnect first");
469 remoteSocket = new Socket(address, port);
470 remoteInputStream = remoteSocket.getInputStream();
471 remoteOutputStream = remoteSocket.getOutputStream();
472 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
476 * Disconnects from the node. If there is no connection to the node, this
477 * method does nothing.
479 public synchronized void disconnect() {
480 if (connectionHandler == null) {
483 Closer.close(remoteSocket);
484 connectionHandler.stop();
485 connectionHandler = null;
489 * Sends the given FCP message.
492 * The FCP message to send
493 * @throws IOException
494 * if an I/O error occurs
496 public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
497 System.out.println("sending message: " + fcpMessage.getName());
498 fcpMessage.write(remoteOutputStream);
502 // PACKAGE-PRIVATE METHODS
506 * Handles the given message, notifying listeners. This message should only
507 * be called by {@link FcpConnectionHandler}.
510 * The received message
512 void handleMessage(FcpMessage fcpMessage) {
513 String messageName = fcpMessage.getName();
514 countMessage(messageName);
515 if ("SimpleProgress".equals(messageName)) {
516 fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
517 } else if ("ProtocolError".equals(messageName)) {
518 fireReceivedProtocolError(new ProtocolError(fcpMessage));
519 } else if ("PersistentGet".equals(messageName)) {
520 fireReceivedPersistentGet(new PersistentGet(fcpMessage));
521 } else if ("PersistentPut".equals(messageName)) {
522 fireReceivedPersistentPut(new PersistentPut(fcpMessage));
523 } else if ("URIGenerated".equals(messageName)) {
524 fireReceivedURIGenerated(new URIGenerated(fcpMessage));
525 } else if ("EndListPersistentRequests".equals(messageName)) {
526 fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
527 } else if ("Peer".equals(messageName)) {
528 fireReceivedPeer(new Peer(fcpMessage));
529 } else if ("PeerNote".equals(messageName)) {
530 fireReceivedPeerNote(new PeerNote(fcpMessage));
531 } else if ("StartedCompression".equals(messageName)) {
532 fireReceivedStartedCompression(new StartedCompression(fcpMessage));
533 } else if ("FinishedCompression".equals(messageName)) {
534 fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
535 } else if ("DataFound".equals(messageName)) {
536 fireReceivedDataFound(new DataFound(fcpMessage));
537 } else if ("AllData".equals(messageName)) {
540 dataLength = Long.valueOf(fcpMessage.getField("DataLength"));
541 } catch (NumberFormatException nfe1) {
544 LimitedInputStream payloadInputStream = new LimitedInputStream(remoteInputStream, dataLength);
545 fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
547 payloadInputStream.consume();
548 } catch (IOException ioe1) {
549 /* FIXME - what now? */
550 /* well, ignore. when the connection handler fails, all fails. */
552 } else if ("EndListPeerNotes".equals(messageName)) {
553 fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
554 } else if ("EndListPeers".equals(messageName)) {
555 fireReceivedEndListPeers(new EndListPeers(fcpMessage));
556 } else if ("SSKKeypair".equals(messageName)) {
557 fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
558 } else if ("PeerRemoved".equals(messageName)) {
559 fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
560 } else if ("UnknownPeerNoteType".equals(messageName)) {
561 fireReceivedUnknownPeerNoteType(new UnknownPeerNoteType(fcpMessage));
562 } else if ("NodeData".equals(messageName)) {
563 fireReceivedNodeData(new NodeData(fcpMessage));
564 } else if ("TestDDAReply".equals(messageName)) {
565 fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
566 } else if ("TestDDAComplete".equals(messageName)) {
567 fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
568 } else if ("NodeHello".equals(messageName)) {
569 fireReceivedNodeHello(new NodeHello(fcpMessage));
570 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
571 fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
573 fireMessageReceived(fcpMessage);
578 * Handles a disconnect from the node.
580 synchronized void handleDisconnect() {
581 Closer.close(remoteInputStream);
582 Closer.close(remoteOutputStream);
583 Closer.close(remoteSocket);
584 connectionHandler = null;
592 * Incremets the counter in {@link #incomingMessageStatistics} by <cod>1</code>
593 * for the given message name.
596 * The name of the message to count
598 private void countMessage(String name) {
600 if (incomingMessageStatistics.containsKey(name)) {
601 oldValue = incomingMessageStatistics.get(name);
603 incomingMessageStatistics.put(name, oldValue + 1);