2 * jFCPlib - FcpConnection.java - Copyright © 2008–2016 David Roden
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 3 of the License, or
7 * (at your option) any later version.
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.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.fcp;
20 import java.io.Closeable;
21 import java.io.FilterInputStream;
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.Collections;
29 import java.util.HashMap;
31 import java.util.logging.Logger;
33 import net.pterodactylus.fcp.FcpUtils.TempInputStream;
36 * An FCP connection to a Freenet node.
38 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
40 public class FcpConnection implements Closeable {
43 private static final Logger logger = Logger.getLogger(FcpConnection.class.getName());
45 /** The default port for FCP v2. */
46 public static final int DEFAULT_PORT = 9481;
48 /** Listener management. */
49 private final FcpListenerManager fcpListenerManager = new FcpListenerManager(this);
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 static final 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 fcpListenerManager.addListener(fcpListener);
151 * Removes the given listener from the list of listeners.
154 * The listener to remove
156 public void removeFcpListener(FcpListener fcpListener) {
157 fcpListenerManager.removeListener(fcpListener);
160 public synchronized boolean isClosed() {
161 return connectionHandler == null;
169 * Connects to the node.
171 * @throws IOException
172 * if an I/O error occurs
173 * @throws IllegalStateException
174 * if there is already a connection to the node
176 public synchronized void connect() throws IOException, IllegalStateException {
177 if (connectionHandler != null) {
178 throw new IllegalStateException("already connected, disconnect first");
180 logger.info("connecting to " + address + ":" + port + "…");
181 remoteSocket = new Socket(address, port);
182 remoteInputStream = remoteSocket.getInputStream();
183 remoteOutputStream = remoteSocket.getOutputStream();
184 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
188 * Disconnects from the node. If there is no connection to the node, this
189 * method does nothing.
191 * @deprecated Use {@link #close()} instead
194 public synchronized void disconnect() {
199 * Closes the connection. If there is no connection to the node, this
200 * method does nothing.
203 public void close() {
204 handleDisconnect(null);
208 * Sends the given FCP message.
211 * The FCP message to send
212 * @throws IOException
213 * if an I/O error occurs
215 public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
216 logger.fine("sending message: " + fcpMessage.getName());
217 fcpMessage.write(remoteOutputStream);
221 // PACKAGE-PRIVATE METHODS
225 * Handles the given message, notifying listeners. This message should only
226 * be called by {@link FcpConnectionHandler}.
229 * The received message
231 void handleMessage(FcpMessage fcpMessage) throws IOException{
232 logger.fine("received message: " + fcpMessage.getName());
233 String messageName = fcpMessage.getName();
234 countMessage(messageName);
235 if ("SimpleProgress".equals(messageName)) {
236 fcpListenerManager.fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
237 } else if ("ProtocolError".equals(messageName)) {
238 fcpListenerManager.fireReceivedProtocolError(new ProtocolError(fcpMessage));
239 } else if ("PersistentGet".equals(messageName)) {
240 fcpListenerManager.fireReceivedPersistentGet(new PersistentGet(fcpMessage));
241 } else if ("PersistentPut".equals(messageName)) {
242 fcpListenerManager.fireReceivedPersistentPut(new PersistentPut(fcpMessage));
243 } else if ("PersistentPutDir".equals(messageName)) {
244 fcpListenerManager.fireReceivedPersistentPutDir(new PersistentPutDir(fcpMessage));
245 } else if ("URIGenerated".equals(messageName)) {
246 fcpListenerManager.fireReceivedURIGenerated(new URIGenerated(fcpMessage));
247 } else if ("EndListPersistentRequests".equals(messageName)) {
248 fcpListenerManager.fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
249 } else if ("Peer".equals(messageName)) {
250 fcpListenerManager.fireReceivedPeer(new Peer(fcpMessage));
251 } else if ("PeerNote".equals(messageName)) {
252 fcpListenerManager.fireReceivedPeerNote(new PeerNote(fcpMessage));
253 } else if ("StartedCompression".equals(messageName)) {
254 fcpListenerManager.fireReceivedStartedCompression(new StartedCompression(fcpMessage));
255 } else if ("FinishedCompression".equals(messageName)) {
256 fcpListenerManager.fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
257 } else if ("GetFailed".equals(messageName)) {
258 fcpListenerManager.fireReceivedGetFailed(new GetFailed(fcpMessage));
259 } else if ("PutFetchable".equals(messageName)) {
260 fcpListenerManager.fireReceivedPutFetchable(new PutFetchable(fcpMessage));
261 } else if ("PutSuccessful".equals(messageName)) {
262 fcpListenerManager.fireReceivedPutSuccessful(new PutSuccessful(fcpMessage));
263 } else if ("PutFailed".equals(messageName)) {
264 fcpListenerManager.fireReceivedPutFailed(new PutFailed(fcpMessage));
265 } else if ("DataFound".equals(messageName)) {
266 fcpListenerManager.fireReceivedDataFound(new DataFound(fcpMessage));
267 } else if ("SubscribedUSKUpdate".equals(messageName)) {
268 fcpListenerManager.fireReceivedSubscribedUSKUpdate(new SubscribedUSKUpdate(fcpMessage));
269 } else if ("SubscribedUSK".equals(messageName)) {
270 fcpListenerManager.fireReceivedSubscribedUSK(new SubscribedUSK(fcpMessage));
271 } else if ("IdentifierCollision".equals(messageName)) {
272 fcpListenerManager.fireReceivedIdentifierCollision(new IdentifierCollision(fcpMessage));
273 } else if ("AllData".equals(messageName)) {
274 InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
275 fcpListenerManager.fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
276 } else if ("EndListPeerNotes".equals(messageName)) {
277 fcpListenerManager.fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
278 } else if ("EndListPeers".equals(messageName)) {
279 fcpListenerManager.fireReceivedEndListPeers(new EndListPeers(fcpMessage));
280 } else if ("SSKKeypair".equals(messageName)) {
281 fcpListenerManager.fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
282 } else if ("PeerRemoved".equals(messageName)) {
283 fcpListenerManager.fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
284 } else if ("PersistentRequestModified".equals(messageName)) {
285 fcpListenerManager.fireReceivedPersistentRequestModified(new PersistentRequestModified(fcpMessage));
286 } else if ("PersistentRequestRemoved".equals(messageName)) {
287 fcpListenerManager.fireReceivedPersistentRequestRemoved(new PersistentRequestRemoved(fcpMessage));
288 } else if ("UnknownPeerNoteType".equals(messageName)) {
289 fcpListenerManager.fireReceivedUnknownPeerNoteType(new UnknownPeerNoteType(fcpMessage));
290 } else if ("UnknownNodeIdentifier".equals(messageName)) {
291 fcpListenerManager.fireReceivedUnknownNodeIdentifier(new UnknownNodeIdentifier(fcpMessage));
292 } else if ("FCPPluginReply".equals(messageName)) {
293 InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength"), 0));
294 fcpListenerManager.fireReceivedFCPPluginReply(new FCPPluginReply(fcpMessage, payloadInputStream));
295 } else if ("PluginInfo".equals(messageName)) {
296 fcpListenerManager.fireReceivedPluginInfo(new PluginInfo(fcpMessage));
297 } else if ("PluginRemoved".equals(messageName)) {
298 fcpListenerManager.fireReceivedPluginRemoved(new PluginRemoved(fcpMessage));
299 } else if ("NodeData".equals(messageName)) {
300 fcpListenerManager.fireReceivedNodeData(new NodeData(fcpMessage));
301 } else if ("TestDDAReply".equals(messageName)) {
302 fcpListenerManager.fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
303 } else if ("TestDDAComplete".equals(messageName)) {
304 fcpListenerManager.fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
305 } else if ("ConfigData".equals(messageName)) {
306 fcpListenerManager.fireReceivedConfigData(new ConfigData(fcpMessage));
307 } else if ("NodeHello".equals(messageName)) {
308 fcpListenerManager.fireReceivedNodeHello(new NodeHello(fcpMessage));
309 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
310 fcpListenerManager.fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
311 } else if ("SentFeed".equals(messageName)) {
312 fcpListenerManager.fireSentFeed(new SentFeed(fcpMessage));
313 } else if ("ReceivedBookmarkFeed".equals(messageName)) {
314 fcpListenerManager.fireReceivedBookmarkFeed(new ReceivedBookmarkFeed(fcpMessage));
316 fcpListenerManager.fireMessageReceived(fcpMessage);
321 * Handles a disconnect from the node.
324 * The exception that caused the disconnect, or
325 * <code>null</code> if there was no exception
327 synchronized void handleDisconnect(Throwable throwable) {
328 FcpUtils.close(remoteInputStream);
329 FcpUtils.close(remoteOutputStream);
330 FcpUtils.close(remoteSocket);
331 if (connectionHandler != null) {
332 connectionHandler.stop();
333 connectionHandler = null;
334 fcpListenerManager.fireConnectionClosed(throwable);
343 * Incremets the counter in {@link #incomingMessageStatistics} by
344 * <cod>1</code> for the given message name.
347 * The name of the message to count
349 private void countMessage(String name) {
351 if (incomingMessageStatistics.containsKey(name)) {
352 oldValue = incomingMessageStatistics.get(name);
354 incomingMessageStatistics.put(name, oldValue + 1);
355 logger.finest("count for " + name + ": " + (oldValue + 1));
358 private synchronized InputStream getInputStream(long dataLength) throws IOException {
359 return new TempInputStream(remoteInputStream, dataLength);