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 the given
74 * host, listening on the default port.
77 * The hostname of the Freenet node
78 * @throws UnknownHostException
79 * if <code>host</code> can not be resolved
81 public FcpConnection(String host) throws UnknownHostException {
82 this(host, DEFAULT_PORT);
86 * Creates a new FCP connection to the Freenet node running on the given
87 * host, listening on the given port.
90 * The hostname of the Freenet node
92 * The port number of the node’s FCP port
93 * @throws UnknownHostException
94 * if <code>host</code> can not be resolved
96 public FcpConnection(String host, int port) throws UnknownHostException {
97 this(InetAddress.getByName(host), port);
101 * Creates a new FCP connection to the Freenet node running at the given
102 * address, listening on the default port.
105 * The address of the Freenet node
107 public FcpConnection(InetAddress address) {
108 this(address, DEFAULT_PORT);
112 * Creates a new FCP connection to the Freenet node running at the given
113 * address, listening on the given port.
116 * The address of the Freenet node
118 * The port number of the node’s FCP port
120 public FcpConnection(InetAddress address, int port) {
121 this.address = address;
126 // LISTENER MANAGEMENT
130 * Adds the given listener to the list of listeners.
133 * The listener to add
135 public void addFcpListener(FcpListener fcpListener) {
136 fcpListeners.add(fcpListener);
140 * Removes the given listener from the list of listeners.
143 * The listener to remove
145 public void removeFcpListener(FcpListener fcpListener) {
146 fcpListeners.remove(fcpListener);
150 * Notifies listeners that a “NodeHello” message was received.
152 * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
154 * The “NodeHello” message
156 private void fireReceivedNodeHello(NodeHello nodeHello) {
157 for (FcpListener fcpListener: fcpListeners) {
158 fcpListener.receivedNodeHello(this, nodeHello);
163 * Notifies listeners that a “CloseConnectionDuplicateClientName” message
166 * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
167 * CloseConnectionDuplicateClientName)
168 * @param closeConnectionDuplicateClientName
169 * The “CloseConnectionDuplicateClientName” message
171 private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
172 for (FcpListener fcpListener: fcpListeners) {
173 fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
178 * Notifies listeners that a “SSKKeypair” message was received.
180 * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
182 * The “SSKKeypair” message
184 private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
185 for (FcpListener fcpListener: fcpListeners) {
186 fcpListener.receivedSSKKeypair(this, sskKeypair);
191 * Notifies listeners that a “Peer” message was received.
193 * @see FcpListener#receivedPeer(FcpConnection, Peer)
197 private void fireReceivedPeer(Peer peer) {
198 for (FcpListener fcpListener: fcpListeners) {
199 fcpListener.receivedPeer(this, peer);
204 * Notifies all listeners that an “EndListPeers” message was received.
206 * @see FcpListener#receivedEndListPeers(FcpConnection, EndListPeers)
207 * @param endListPeers
208 * The “EndListPeers” message
210 private void fireReceivedEndListPeers(EndListPeers endListPeers) {
211 for (FcpListener fcpListener: fcpListeners) {
212 fcpListener.receivedEndListPeers(this, endListPeers);
217 * Notifies all listeners that a “PeerNote” message was received.
219 * @see FcpListener#receivedPeerNote(FcpConnection, PeerNote)
222 private void fireReceivedPeerNote(PeerNote peerNote) {
223 for (FcpListener fcpListener: fcpListeners) {
224 fcpListener.receivedPeerNote(this, peerNote);
229 * Notifies all listeners that an “EndListPeerNotes” message was received.
231 * @see FcpListener#receivedEndListPeerNotes(FcpConnection,
233 * @param endListPeerNotes
234 * The “EndListPeerNotes” message
236 private void fireReceivedEndListPeerNotes(EndListPeerNotes endListPeerNotes) {
237 for (FcpListener fcpListener: fcpListeners) {
238 fcpListener.receivedEndListPeerNotes(this, endListPeerNotes);
243 * Notifies all listeners that a “PeerRemoved” message was received.
245 * @see FcpListener#receivedPeerRemoved(FcpConnection, PeerRemoved)
247 * The “PeerRemoved” message
249 private void fireReceivedPeerRemoved(PeerRemoved peerRemoved) {
250 for (FcpListener fcpListener: fcpListeners) {
251 fcpListener.receivedPeerRemoved(this, peerRemoved);
256 * Notifies all listeners that a “NodeData” message was received.
258 * @see FcpListener#receivedNodeData(FcpConnection, NodeData)
260 * The “NodeData” message
262 private void fireReceivedNodeData(NodeData nodeData) {
263 for (FcpListener fcpListener: fcpListeners) {
264 fcpListener.receivedNodeData(this, nodeData);
269 * Notifies all listeners that a “TestDDAReply” message was received.
271 * @see FcpListener#receivedTestDDAReply(FcpConnection, TestDDAReply)
272 * @param testDDAReply
273 * The “TestDDAReply” message
275 private void fireReceivedTestDDAReply(TestDDAReply testDDAReply) {
276 for (FcpListener fcpListener: fcpListeners) {
277 fcpListener.receivedTestDDAReply(this, testDDAReply);
282 * Notifies all listeners that a “TestDDAComplete” message was received.
284 * @see FcpListener#receivedTestDDAComplete(FcpConnection, TestDDAComplete)
285 * @param testDDAComplete
286 * The “TestDDAComplete” message
288 private void fireReceivedTestDDAComplete(TestDDAComplete testDDAComplete) {
289 for (FcpListener fcpListener: fcpListeners) {
290 fcpListener.receivedTestDDAComplete(this, testDDAComplete);
295 * Notifies all listeners that a “PersistentGet” message was received.
297 * @param persistentGet
298 * The “PersistentGet” message
300 private void fireReceivedPersistentGet(PersistentGet persistentGet) {
301 for (FcpListener fcpListener: fcpListeners) {
302 fcpListener.receivedPersistentGet(this, persistentGet);
307 * Notifies all listeners that a “PersistentPut” message was received.
309 * @see FcpListener#receivedPersistentPut(FcpConnection, PersistentPut)
310 * @param persistentPut
311 * The “PersistentPut” message
313 private void fireReceivedPersistentPut(PersistentPut persistentPut) {
314 for (FcpListener fcpListener: fcpListeners) {
315 fcpListener.receivedPersistentPut(this, persistentPut);
320 * Notifies all listeners that a “EndListPersistentRequests” message was
323 * @param endListPersistentRequests
324 * The “EndListPersistentRequests” message
326 private void fireReceivedEndListPersistentRequests(EndListPersistentRequests endListPersistentRequests) {
327 for (FcpListener fcpListener: fcpListeners) {
328 fcpListener.receivedEndListPersistentRequests(this, endListPersistentRequests);
333 * Notifies all listeners that a “URIGenerated” message was received.
335 * @param uriGenerated
336 * The “URIGenerated” message
338 private void fireReceivedURIGenerated(URIGenerated uriGenerated) {
339 for (FcpListener fcpListener: fcpListeners) {
340 fcpListener.receivedURIGenerated(this, uriGenerated);
345 * Notifies all listeners that a “DataFound” message was received.
348 * The “DataFound” message
350 private void fireReceivedDataFound(DataFound dataFound) {
351 for (FcpListener fcpListener: fcpListeners) {
352 fcpListener.receivedDataFound(this, dataFound);
357 * Notifies all listeners that an “AllData” message was received.
360 * The “AllData” message
362 private void fireReceivedAllData(AllData allData) {
363 for (FcpListener fcpListener: fcpListeners) {
364 fcpListener.receivedAllData(this, allData);
369 * Notifies all listeners that a “SimpleProgress” message was received.
371 * @param simpleProgress
372 * The “SimpleProgress” message
374 private void fireReceivedSimpleProgress(SimpleProgress simpleProgress) {
375 for (FcpListener fcpListener: fcpListeners) {
376 fcpListener.receivedSimpleProgress(this, simpleProgress);
381 * Notifies all listeners that a “StartedCompression” message was received.
383 * @param startedCompression
384 * The “StartedCompression” message
386 private void fireReceivedStartedCompression(StartedCompression startedCompression) {
387 for (FcpListener fcpListener: fcpListeners) {
388 fcpListener.receivedStartedCompression(this, startedCompression);
393 * Notifies all listeners that a “FinishedCompression” message was received.
395 * @param finishedCompression
396 * The “FinishedCompression” message
398 private void fireReceivedFinishedCompression(FinishedCompression finishedCompression) {
399 for (FcpListener fcpListener: fcpListeners) {
400 fcpListener.receviedFinishedCompression(this, finishedCompression);
405 * Notifies all listeners that a “ProtocolError” message was received.
407 * @param protocolError
408 * The “ProtocolError” message
410 private void fireReceivedProtocolError(ProtocolError protocolError) {
411 for (FcpListener fcpListener: fcpListeners) {
412 fcpListener.receivedProtocolError(this, protocolError);
417 * Notifies all registered listeners that a message has been received.
419 * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
421 * The message that was received
423 private void fireMessageReceived(FcpMessage fcpMessage) {
424 for (FcpListener fcpListener: fcpListeners) {
425 fcpListener.receivedMessage(this, fcpMessage);
434 * Connects to the node.
436 * @throws IOException
437 * if an I/O error occurs
438 * @throws IllegalStateException
439 * if there is already a connection to the node
441 public synchronized void connect() throws IOException, IllegalStateException {
442 if (connectionHandler != null) {
443 throw new IllegalStateException("already connected, disconnect first");
445 remoteSocket = new Socket(address, port);
446 remoteInputStream = remoteSocket.getInputStream();
447 remoteOutputStream = remoteSocket.getOutputStream();
448 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
452 * Disconnects from the node. If there is no connection to the node, this
453 * method does nothing.
455 public synchronized void disconnect() {
456 if (connectionHandler == null) {
459 Closer.close(remoteSocket);
460 connectionHandler.stop();
461 connectionHandler = null;
465 * Sends the given FCP message.
468 * The FCP message to send
469 * @throws IOException
470 * if an I/O error occurs
472 public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
473 System.out.println("sending message: " + fcpMessage.getName());
474 fcpMessage.write(remoteOutputStream);
478 // PACKAGE-PRIVATE METHODS
482 * Handles the given message, notifying listeners. This message should only
483 * be called by {@link FcpConnectionHandler}.
486 * The received message
488 void handleMessage(FcpMessage fcpMessage) {
489 String messageName = fcpMessage.getName();
490 countMessage(messageName);
491 if ("SimpleProgress".equals(messageName)) {
492 fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
493 } else if ("ProtocolError".equals(messageName)) {
494 fireReceivedProtocolError(new ProtocolError(fcpMessage));
495 } else if ("PersistentGet".equals(messageName)) {
496 fireReceivedPersistentGet(new PersistentGet(fcpMessage));
497 } else if ("PersistentPut".equals(messageName)) {
498 fireReceivedPersistentPut(new PersistentPut(fcpMessage));
499 } else if ("URIGenerated".equals(messageName)) {
500 fireReceivedURIGenerated(new URIGenerated(fcpMessage));
501 } else if ("EndListPersistentRequests".equals(messageName)) {
502 fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
503 } else if ("Peer".equals(messageName)) {
504 fireReceivedPeer(new Peer(fcpMessage));
505 } else if ("PeerNote".equals(messageName)) {
506 fireReceivedPeerNote(new PeerNote(fcpMessage));
507 } else if ("StartedCompression".equals(messageName)) {
508 fireReceivedStartedCompression(new StartedCompression(fcpMessage));
509 } else if ("FinishedCompression".equals(messageName)) {
510 fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
511 } else if ("DataFound".equals(messageName)) {
512 fireReceivedDataFound(new DataFound(fcpMessage));
513 } else if ("AllData".equals(messageName)) {
516 dataLength = Long.valueOf(fcpMessage.getField("DataLength"));
517 } catch (NumberFormatException nfe1) {
520 LimitedInputStream payloadInputStream = new LimitedInputStream(remoteInputStream, dataLength);
521 fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
523 payloadInputStream.consume();
524 } catch (IOException ioe1) {
525 /* FIXME - what now? */
526 /* well, ignore. when the connection handler fails, all fails. */
528 } else if ("EndListPeerNotes".equals(messageName)) {
529 fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
530 } else if ("EndListPeers".equals(messageName)) {
531 fireReceivedEndListPeers(new EndListPeers(fcpMessage));
532 } else if ("SSKKeypair".equals(messageName)) {
533 fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
534 } else if ("PeerRemoved".equals(messageName)) {
535 fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
536 } else if ("NodeData".equals(messageName)) {
537 fireReceivedNodeData(new NodeData(fcpMessage));
538 } else if ("TestDDAReply".equals(messageName)) {
539 fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
540 } else if ("TestDDAComplete".equals(messageName)) {
541 fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
542 } else if ("NodeHello".equals(messageName)) {
543 fireReceivedNodeHello(new NodeHello(fcpMessage));
544 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
545 fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
547 fireMessageReceived(fcpMessage);
552 * Handles a disconnect from the node.
554 synchronized void handleDisconnect() {
555 Closer.close(remoteInputStream);
556 Closer.close(remoteOutputStream);
557 Closer.close(remoteSocket);
558 connectionHandler = null;
566 * Incremets the counter in {@link #incomingMessageStatistics} by <cod>1</code>
567 * for the given message name.
570 * The name of the message to count
572 private void countMessage(String name) {
574 if (incomingMessageStatistics.containsKey(name)) {
575 oldValue = incomingMessageStatistics.get(name);
577 incomingMessageStatistics.put(name, oldValue + 1);