Copy payload in AllData/FCPPluginReply handlers
[jFCPlib.git] / src / main / java / net / pterodactylus / fcp / FcpConnection.java
1 /*
2  * jFCPlib - FpcConnection.java - Copyright © 2008 David Roden
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 package net.pterodactylus.fcp;
20
21 import java.io.Closeable;
22 import java.io.FilterInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.net.InetAddress;
27 import java.net.Socket;
28 import java.net.UnknownHostException;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.logging.Logger;
33
34 import net.pterodactylus.fcp.FcpUtils.TempInputStream;
35
36 /**
37  * An FCP connection to a Freenet node.
38  *
39  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
40  */
41 public class FcpConnection implements Closeable {
42
43         /** Logger. */
44         private static final Logger logger = Logger.getLogger(FcpConnection.class.getName());
45
46         /** The default port for FCP v2. */
47         public static final int DEFAULT_PORT = 9481;
48
49         /** Listener management. */
50         private final FcpListenerManager fcpListenerManager = new FcpListenerManager(this);
51
52         /** The address of the node. */
53         private final InetAddress address;
54
55         /** The port number of the node’s FCP port. */
56         private final int port;
57
58         /** The remote socket. */
59         private Socket remoteSocket;
60
61         /** The input stream from the node. */
62         private InputStream remoteInputStream;
63
64         /** The output stream to the node. */
65         private OutputStream remoteOutputStream;
66
67         /** The connection handler. */
68         private FcpConnectionHandler connectionHandler;
69
70         /** Incoming message statistics. */
71         private static final Map<String, Integer> incomingMessageStatistics = Collections.synchronizedMap(new HashMap<String, Integer>());
72
73         /**
74          * Creates a new FCP connection to the freenet node running on localhost,
75          * using the default port.
76          *
77          * @throws UnknownHostException
78          *             if the hostname can not be resolved
79          */
80         public FcpConnection() throws UnknownHostException {
81                 this(InetAddress.getLocalHost());
82         }
83
84         /**
85          * Creates a new FCP connection to the Freenet node running on the given
86          * host, listening on the default port.
87          *
88          * @param host
89          *            The hostname of the Freenet node
90          * @throws UnknownHostException
91          *             if <code>host</code> can not be resolved
92          */
93         public FcpConnection(String host) throws UnknownHostException {
94                 this(host, DEFAULT_PORT);
95         }
96
97         /**
98          * Creates a new FCP connection to the Freenet node running on the given
99          * host, listening on the given port.
100          *
101          * @param host
102          *            The hostname of the Freenet node
103          * @param port
104          *            The port number of the node’s FCP port
105          * @throws UnknownHostException
106          *             if <code>host</code> can not be resolved
107          */
108         public FcpConnection(String host, int port) throws UnknownHostException {
109                 this(InetAddress.getByName(host), port);
110         }
111
112         /**
113          * Creates a new FCP connection to the Freenet node running at the given
114          * address, listening on the default port.
115          *
116          * @param address
117          *            The address of the Freenet node
118          */
119         public FcpConnection(InetAddress address) {
120                 this(address, DEFAULT_PORT);
121         }
122
123         /**
124          * Creates a new FCP connection to the Freenet node running at the given
125          * address, listening on the given port.
126          *
127          * @param address
128          *            The address of the Freenet node
129          * @param port
130          *            The port number of the node’s FCP port
131          */
132         public FcpConnection(InetAddress address, int port) {
133                 this.address = address;
134                 this.port = port;
135         }
136
137         //
138         // LISTENER MANAGEMENT
139         //
140
141         /**
142          * Adds the given listener to the list of listeners.
143          *
144          * @param fcpListener
145          *            The listener to add
146          */
147         public void addFcpListener(FcpListener fcpListener) {
148                 fcpListenerManager.addListener(fcpListener);
149         }
150
151         /**
152          * Removes the given listener from the list of listeners.
153          *
154          * @param fcpListener
155          *            The listener to remove
156          */
157         public void removeFcpListener(FcpListener fcpListener) {
158                 fcpListenerManager.removeListener(fcpListener);
159         }
160
161         public synchronized boolean isClosed() {
162                 return connectionHandler == null;
163         }
164
165         //
166         // ACTIONS
167         //
168
169         /**
170          * Connects to the node.
171          *
172          * @throws IOException
173          *             if an I/O error occurs
174          * @throws IllegalStateException
175          *             if there is already a connection to the node
176          */
177         public synchronized void connect() throws IOException, IllegalStateException {
178                 if (connectionHandler != null) {
179                         throw new IllegalStateException("already connected, disconnect first");
180                 }
181                 logger.info("connecting to " + address + ":" + port + "…");
182                 remoteSocket = new Socket(address, port);
183                 remoteInputStream = remoteSocket.getInputStream();
184                 remoteOutputStream = remoteSocket.getOutputStream();
185                 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
186         }
187
188         /**
189          * Disconnects from the node. If there is no connection to the node, this
190          * method does nothing.
191          *
192          * @deprecated Use {@link #close()} instead
193          */
194         @Deprecated
195         public synchronized void disconnect() {
196                 close();
197         }
198
199         /**
200          * Closes the connection. If there is no connection to the node, this
201          * method does nothing.
202          */
203         @Override
204         public void close() {
205                 handleDisconnect(null);
206         }
207
208         /**
209          * Sends the given FCP message.
210          *
211          * @param fcpMessage
212          *            The FCP message to send
213          * @throws IOException
214          *             if an I/O error occurs
215          */
216         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
217                 logger.fine("sending message: " + fcpMessage.getName());
218                 fcpMessage.write(remoteOutputStream);
219         }
220
221         //
222         // PACKAGE-PRIVATE METHODS
223         //
224
225         /**
226          * Handles the given message, notifying listeners. This message should only
227          * be called by {@link FcpConnectionHandler}.
228          *
229          * @param fcpMessage
230          *            The received message
231          */
232         void handleMessage(FcpMessage fcpMessage) throws IOException{
233                 logger.fine("received message: " + fcpMessage.getName());
234                 String messageName = fcpMessage.getName();
235                 countMessage(messageName);
236                 if ("SimpleProgress".equals(messageName)) {
237                         fcpListenerManager.fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
238                 } else if ("ProtocolError".equals(messageName)) {
239                         fcpListenerManager.fireReceivedProtocolError(new ProtocolError(fcpMessage));
240                 } else if ("PersistentGet".equals(messageName)) {
241                         fcpListenerManager.fireReceivedPersistentGet(new PersistentGet(fcpMessage));
242                 } else if ("PersistentPut".equals(messageName)) {
243                         fcpListenerManager.fireReceivedPersistentPut(new PersistentPut(fcpMessage));
244                 } else if ("PersistentPutDir".equals(messageName)) {
245                         fcpListenerManager.fireReceivedPersistentPutDir(new PersistentPutDir(fcpMessage));
246                 } else if ("URIGenerated".equals(messageName)) {
247                         fcpListenerManager.fireReceivedURIGenerated(new URIGenerated(fcpMessage));
248                 } else if ("EndListPersistentRequests".equals(messageName)) {
249                         fcpListenerManager.fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
250                 } else if ("Peer".equals(messageName)) {
251                         fcpListenerManager.fireReceivedPeer(new Peer(fcpMessage));
252                 } else if ("PeerNote".equals(messageName)) {
253                         fcpListenerManager.fireReceivedPeerNote(new PeerNote(fcpMessage));
254                 } else if ("StartedCompression".equals(messageName)) {
255                         fcpListenerManager.fireReceivedStartedCompression(new StartedCompression(fcpMessage));
256                 } else if ("FinishedCompression".equals(messageName)) {
257                         fcpListenerManager.fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
258                 } else if ("GetFailed".equals(messageName)) {
259                         fcpListenerManager.fireReceivedGetFailed(new GetFailed(fcpMessage));
260                 } else if ("PutFetchable".equals(messageName)) {
261                         fcpListenerManager.fireReceivedPutFetchable(new PutFetchable(fcpMessage));
262                 } else if ("PutSuccessful".equals(messageName)) {
263                         fcpListenerManager.fireReceivedPutSuccessful(new PutSuccessful(fcpMessage));
264                 } else if ("PutFailed".equals(messageName)) {
265                         fcpListenerManager.fireReceivedPutFailed(new PutFailed(fcpMessage));
266                 } else if ("DataFound".equals(messageName)) {
267                         fcpListenerManager.fireReceivedDataFound(new DataFound(fcpMessage));
268                 } else if ("SubscribedUSKUpdate".equals(messageName)) {
269                         fcpListenerManager.fireReceivedSubscribedUSKUpdate(new SubscribedUSKUpdate(fcpMessage));
270                 } else if ("SubscribedUSK".equals(messageName)) {
271                         fcpListenerManager.fireReceivedSubscribedUSK(new SubscribedUSK(fcpMessage));
272                 } else if ("IdentifierCollision".equals(messageName)) {
273                         fcpListenerManager.fireReceivedIdentifierCollision(new IdentifierCollision(fcpMessage));
274                 } else if ("AllData".equals(messageName)) {
275                         InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
276                         fcpListenerManager.fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
277                 } else if ("EndListPeerNotes".equals(messageName)) {
278                         fcpListenerManager.fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
279                 } else if ("EndListPeers".equals(messageName)) {
280                         fcpListenerManager.fireReceivedEndListPeers(new EndListPeers(fcpMessage));
281                 } else if ("SSKKeypair".equals(messageName)) {
282                         fcpListenerManager.fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
283                 } else if ("PeerRemoved".equals(messageName)) {
284                         fcpListenerManager.fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
285                 } else if ("PersistentRequestModified".equals(messageName)) {
286                         fcpListenerManager.fireReceivedPersistentRequestModified(new PersistentRequestModified(fcpMessage));
287                 } else if ("PersistentRequestRemoved".equals(messageName)) {
288                         fcpListenerManager.fireReceivedPersistentRequestRemoved(new PersistentRequestRemoved(fcpMessage));
289                 } else if ("UnknownPeerNoteType".equals(messageName)) {
290                         fcpListenerManager.fireReceivedUnknownPeerNoteType(new UnknownPeerNoteType(fcpMessage));
291                 } else if ("UnknownNodeIdentifier".equals(messageName)) {
292                         fcpListenerManager.fireReceivedUnknownNodeIdentifier(new UnknownNodeIdentifier(fcpMessage));
293                 } else if ("FCPPluginReply".equals(messageName)) {
294                         InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
295                         fcpListenerManager.fireReceivedFCPPluginReply(new FCPPluginReply(fcpMessage, payloadInputStream));
296                 } else if ("PluginInfo".equals(messageName)) {
297                         fcpListenerManager.fireReceivedPluginInfo(new PluginInfo(fcpMessage));
298                 } else if ("PluginRemoved".equals(messageName)) {
299                         fcpListenerManager.fireReceivedPluginRemoved(new PluginRemoved(fcpMessage));
300                 } else if ("NodeData".equals(messageName)) {
301                         fcpListenerManager.fireReceivedNodeData(new NodeData(fcpMessage));
302                 } else if ("TestDDAReply".equals(messageName)) {
303                         fcpListenerManager.fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
304                 } else if ("TestDDAComplete".equals(messageName)) {
305                         fcpListenerManager.fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
306                 } else if ("ConfigData".equals(messageName)) {
307                         fcpListenerManager.fireReceivedConfigData(new ConfigData(fcpMessage));
308                 } else if ("NodeHello".equals(messageName)) {
309                         fcpListenerManager.fireReceivedNodeHello(new NodeHello(fcpMessage));
310                 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
311                         fcpListenerManager.fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
312                 } else if ("SentFeed".equals(messageName)) {
313                         fcpListenerManager.fireSentFeed(new SentFeed(fcpMessage));
314                 } else if ("ReceivedBookmarkFeed".equals(messageName)) {
315                         fcpListenerManager.fireReceivedBookmarkFeed(new ReceivedBookmarkFeed(fcpMessage));
316                 } else {
317                         fcpListenerManager.fireMessageReceived(fcpMessage);
318                 }
319         }
320
321         /**
322          * Handles a disconnect from the node.
323          *
324          * @param throwable
325          *            The exception that caused the disconnect, or
326          *            <code>null</code> if there was no exception
327          */
328         synchronized void handleDisconnect(Throwable throwable) {
329                 FcpUtils.close(remoteInputStream);
330                 FcpUtils.close(remoteOutputStream);
331                 FcpUtils.close(remoteSocket);
332                 if (connectionHandler != null) {
333                         connectionHandler.stop();
334                         connectionHandler = null;
335                         fcpListenerManager.fireConnectionClosed(throwable);
336                 }
337         }
338
339         //
340         // PRIVATE METHODS
341         //
342
343         /**
344          * Incremets the counter in {@link #incomingMessageStatistics} by
345          * <cod>1</code> for the given message name.
346          *
347          * @param name
348          *            The name of the message to count
349          */
350         private void countMessage(String name) {
351                 int oldValue = 0;
352                 if (incomingMessageStatistics.containsKey(name)) {
353                         oldValue = incomingMessageStatistics.get(name);
354                 }
355                 incomingMessageStatistics.put(name, oldValue + 1);
356                 logger.finest("count for " + name + ": " + (oldValue + 1));
357         }
358
359         private synchronized InputStream getInputStream(long dataLength) throws IOException {
360                 return new TempInputStream(remoteInputStream, dataLength);
361         }
362
363 }