add ProtocolError message
[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.List;
30
31 import net.pterodactylus.util.io.Closer;
32
33 /**
34  * An FCP connection to a Freenet node.
35  * 
36  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
37  * @version $Id$
38  */
39 public class FcpConnection {
40
41         /** The default port for FCP v2. */
42         public static final int DEFAULT_PORT = 9481;
43
44         /** The list of FCP listeners. */
45         private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
46
47         /** The address of the node. */
48         private final InetAddress address;
49
50         /** The port number of the node’s FCP port. */
51         private final int port;
52
53         /** The remote socket. */
54         private Socket remoteSocket;
55
56         /** The input stream from the node. */
57         private InputStream remoteInputStream;
58
59         /** The output stream to the node. */
60         private OutputStream remoteOutputStream;
61
62         /** The connection handler. */
63         private FcpConnectionHandler connectionHandler;
64
65         /**
66          * Creates a new FCP connection to the Freenet node running on the given
67          * host, listening on the default port.
68          * 
69          * @param host
70          *            The hostname of the Freenet node
71          * @throws UnknownHostException
72          *             if <code>host</code> can not be resolved
73          */
74         public FcpConnection(String host) throws UnknownHostException {
75                 this(host, DEFAULT_PORT);
76         }
77
78         /**
79          * Creates a new FCP connection to the Freenet node running on the given
80          * host, listening on the given port.
81          * 
82          * @param host
83          *            The hostname of the Freenet node
84          * @param port
85          *            The port number of the node’s FCP port
86          * @throws UnknownHostException
87          *             if <code>host</code> can not be resolved
88          */
89         public FcpConnection(String host, int port) throws UnknownHostException {
90                 this(InetAddress.getByName(host), port);
91         }
92
93         /**
94          * Creates a new FCP connection to the Freenet node running at the given
95          * address, listening on the default port.
96          * 
97          * @param address
98          *            The address of the Freenet node
99          */
100         public FcpConnection(InetAddress address) {
101                 this(address, DEFAULT_PORT);
102         }
103
104         /**
105          * Creates a new FCP connection to the Freenet node running at the given
106          * address, listening on the given port.
107          * 
108          * @param address
109          *            The address of the Freenet node
110          * @param port
111          *            The port number of the node’s FCP port
112          */
113         public FcpConnection(InetAddress address, int port) {
114                 this.address = address;
115                 this.port = port;
116         }
117
118         //
119         // LISTENER MANAGEMENT
120         //
121
122         /**
123          * Adds the given listener to the list of listeners.
124          * 
125          * @param fcpListener
126          *            The listener to add
127          */
128         public void addFcpListener(FcpListener fcpListener) {
129                 fcpListeners.add(fcpListener);
130         }
131
132         /**
133          * Removes the given listener from the list of listeners.
134          * 
135          * @param fcpListener
136          *            The listener to remove
137          */
138         public void removeFcpListener(FcpListener fcpListener) {
139                 fcpListeners.remove(fcpListener);
140         }
141
142         /**
143          * Notifies listeners that a “NodeHello” message was received.
144          * 
145          * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
146          * @param nodeHello
147          *            The “NodeHello” message
148          */
149         private void fireReceivedNodeHello(NodeHello nodeHello) {
150                 for (FcpListener fcpListener: fcpListeners) {
151                         fcpListener.receivedNodeHello(this, nodeHello);
152                 }
153         }
154
155         /**
156          * Notifies listeners that a “CloseConnectionDuplicateClientName” message
157          * was received.
158          * 
159          * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
160          *      CloseConnectionDuplicateClientName)
161          * @param closeConnectionDuplicateClientName
162          *            The “CloseConnectionDuplicateClientName” message
163          */
164         private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
165                 for (FcpListener fcpListener: fcpListeners) {
166                         fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
167                 }
168         }
169
170         /**
171          * Notifies listeners that a “SSKKeypair” message was received.
172          * 
173          * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
174          * @param sskKeypair
175          *            The “SSKKeypair” message
176          */
177         private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
178                 for (FcpListener fcpListener: fcpListeners) {
179                         fcpListener.receivedSSKKeypair(this, sskKeypair);
180                 }
181         }
182
183         /**
184          * Notifies listeners that a “Peer” message was received.
185          * 
186          * @see FcpListener#receivedPeer(FcpConnection, Peer)
187          * @param peer
188          *            The “Peer” message
189          */
190         private void fireReceivedPeer(Peer peer) {
191                 for (FcpListener fcpListener: fcpListeners) {
192                         fcpListener.receivedPeer(this, peer);
193                 }
194         }
195
196         /**
197          * Notifies all listeners that an “EndListPeers” message was received.
198          * 
199          * @see FcpListener#receivedEndListPeers(FcpConnection, EndListPeers)
200          * @param endListPeers
201          *            The “EndListPeers” message
202          */
203         private void fireReceivedEndListPeers(EndListPeers endListPeers) {
204                 for (FcpListener fcpListener: fcpListeners) {
205                         fcpListener.receivedEndListPeers(this, endListPeers);
206                 }
207         }
208
209         /**
210          * Notifies all listeners that a “PeerNote” message was received.
211          * 
212          * @see FcpListener#receivedPeerNote(FcpConnection, PeerNote)
213          * @param peerNote
214          */
215         private void fireReceivedPeerNote(PeerNote peerNote) {
216                 for (FcpListener fcpListener: fcpListeners) {
217                         fcpListener.receivedPeerNote(this, peerNote);
218                 }
219         }
220
221         /**
222          * Notifies all listeners that an “EndListPeerNotes” message was received.
223          * 
224          * @see FcpListener#receivedEndListPeerNotes(FcpConnection,
225          *      EndListPeerNotes)
226          * @param endListPeerNotes
227          *            The “EndListPeerNotes” message
228          */
229         private void fireReceivedEndListPeerNotes(EndListPeerNotes endListPeerNotes) {
230                 for (FcpListener fcpListener: fcpListeners) {
231                         fcpListener.receivedEndListPeerNotes(this, endListPeerNotes);
232                 }
233         }
234
235         /**
236          * Notifies all listeners that a “PeerRemoved” message was received.
237          * 
238          * @see FcpListener#receivedPeerRemoved(FcpConnection, PeerRemoved)
239          * @param peerRemoved
240          *            The “PeerRemoved” message
241          */
242         private void fireReceivedPeerRemoved(PeerRemoved peerRemoved) {
243                 for (FcpListener fcpListener: fcpListeners) {
244                         fcpListener.receivedPeerRemoved(this, peerRemoved);
245                 }
246         }
247
248         /**
249          * Notifies all listeners that a “NodeData” message was received.
250          * 
251          * @see FcpListener#receivedNodeData(FcpConnection, NodeData)
252          * @param nodeData
253          *            The “NodeData” message
254          */
255         private void fireReceivedNodeData(NodeData nodeData) {
256                 for (FcpListener fcpListener: fcpListeners) {
257                         fcpListener.receivedNodeData(this, nodeData);
258                 }
259         }
260
261         /**
262          * Notifies all listeners that a “TestDDAReply” message was received.
263          * 
264          * @param testDDAReply
265          *            The “TestDDAReply” message
266          */
267         private void fireReceivedTestDDAReply(TestDDAReply testDDAReply) {
268                 for (FcpListener fcpListener: fcpListeners) {
269                         fcpListener.receivedTestDDAReply(this, testDDAReply);
270                 }
271         }
272
273         /**
274          * Notifies all listeners that a “TestDDAComplete” message was received.
275          * 
276          * @param testDDAComplete
277          *            The “TestDDAComplete” message
278          */
279         private void fireReceivedTestDDAComplete(TestDDAComplete testDDAComplete) {
280                 for (FcpListener fcpListener: fcpListeners) {
281                         fcpListener.receivedTestDDAComplete(this, testDDAComplete);
282                 }
283         }
284
285         /**
286          * Notifies all listeners that a “ProtocolError” message was received.
287          * 
288          * @param protocolError
289          *            The “ProtocolError” message
290          */
291         private void fireReceivedProtocolError(ProtocolError protocolError) {
292                 for (FcpListener fcpListener: fcpListeners) {
293                         fcpListener.receivedProtocolError(this, protocolError);
294                 }
295         }
296
297         /**
298          * Notifies all registered listeners that a message has been received.
299          * 
300          * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
301          * @param fcpMessage
302          *            The message that was received
303          */
304         private void fireMessageReceived(FcpMessage fcpMessage) {
305                 for (FcpListener fcpListener: fcpListeners) {
306                         fcpListener.receivedMessage(this, fcpMessage);
307                 }
308         }
309
310         //
311         // ACTIONS
312         //
313
314         /**
315          * Connects to the node.
316          * 
317          * @throws IOException
318          *             if an I/O error occurs
319          * @throws IllegalStateException
320          *             if there is already a connection to the node
321          */
322         public synchronized void connect() throws IOException, IllegalStateException {
323                 if (connectionHandler != null) {
324                         throw new IllegalStateException("already connected, disconnect first");
325                 }
326                 remoteSocket = new Socket(address, port);
327                 remoteInputStream = remoteSocket.getInputStream();
328                 remoteOutputStream = remoteSocket.getOutputStream();
329                 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
330         }
331
332         /**
333          * Disconnects from the node. If there is no connection to the node, this
334          * method does nothing.
335          */
336         public synchronized void disconnect() {
337                 if (connectionHandler == null) {
338                         return;
339                 }
340                 Closer.close(remoteSocket);
341                 connectionHandler.stop();
342                 connectionHandler = null;
343         }
344
345         /**
346          * Sends the given FCP message.
347          * 
348          * @param fcpMessage
349          *            The FCP message to send
350          * @throws IOException
351          *             if an I/O error occurs
352          */
353         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
354                 System.out.println("sending message: " + fcpMessage.getName());
355                 fcpMessage.write(remoteOutputStream);
356         }
357
358         //
359         // PACKAGE-PRIVATE METHODS
360         //
361
362         /**
363          * Handles the given message, notifying listeners. This message should only
364          * be called by {@link FcpConnectionHandler}.
365          * 
366          * @param fcpMessage
367          *            The received message
368          */
369         void handleMessage(FcpMessage fcpMessage) {
370                 String messageName = fcpMessage.getName();
371                 if ("ProtocolError".equals(messageName)) {
372                         fireReceivedProtocolError(new ProtocolError(fcpMessage));
373                 } else if ("Peer".equals(messageName)) {
374                         fireReceivedPeer(new Peer(fcpMessage));
375                 } else if ("PeerNote".equals(messageName)) {
376                         fireReceivedPeerNote(new PeerNote(fcpMessage));
377                 } else if ("EndListPeerNotes".equals(messageName)) {
378                         fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
379                 } else if ("EndListPeers".equals(messageName)) {
380                         fireReceivedEndListPeers(new EndListPeers(fcpMessage));
381                 } else if ("SSKKeypair".equals(messageName)) {
382                         fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
383                 } else if ("PeerRemoved".equals(messageName)) {
384                         fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
385                 } else if ("NodeData".equals(messageName)) {
386                         fireReceivedNodeData(new NodeData(fcpMessage));
387                 } else if ("TestDDAReply".equals(messageName)) {
388                         fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
389                 } else if ("TestDDAComplete".equals(messageName)) {
390                         fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
391                 } else if ("NodeHello".equals(messageName)) {
392                         fireReceivedNodeHello(new NodeHello(fcpMessage));
393                 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
394                         fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
395                 } else {
396                         fireMessageReceived(fcpMessage);
397                 }
398         }
399
400 }