027b41574abb751b10d579aff60542cdc6365af9
[jFCPlib.git] / src / net / pterodactylus / fcp / highlevel / FcpClient.java
1 /*
2  * jFCPlib - FcpClient.java -
3  * Copyright © 2009 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.fcp.highlevel;
21
22 import java.io.IOException;
23 import java.net.InetAddress;
24 import java.net.URL;
25 import java.net.UnknownHostException;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.concurrent.CountDownLatch;
33
34 import net.pterodactylus.fcp.AddPeer;
35 import net.pterodactylus.fcp.ClientHello;
36 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
37 import net.pterodactylus.fcp.DataFound;
38 import net.pterodactylus.fcp.EndListPeerNotes;
39 import net.pterodactylus.fcp.EndListPeers;
40 import net.pterodactylus.fcp.EndListPersistentRequests;
41 import net.pterodactylus.fcp.FcpAdapter;
42 import net.pterodactylus.fcp.FcpConnection;
43 import net.pterodactylus.fcp.FcpListener;
44 import net.pterodactylus.fcp.GenerateSSK;
45 import net.pterodactylus.fcp.GetFailed;
46 import net.pterodactylus.fcp.ListPeerNotes;
47 import net.pterodactylus.fcp.ListPeers;
48 import net.pterodactylus.fcp.ListPersistentRequests;
49 import net.pterodactylus.fcp.ModifyPeer;
50 import net.pterodactylus.fcp.ModifyPeerNote;
51 import net.pterodactylus.fcp.NodeHello;
52 import net.pterodactylus.fcp.NodeRef;
53 import net.pterodactylus.fcp.Peer;
54 import net.pterodactylus.fcp.PeerNote;
55 import net.pterodactylus.fcp.PeerRemoved;
56 import net.pterodactylus.fcp.PersistentGet;
57 import net.pterodactylus.fcp.PersistentPut;
58 import net.pterodactylus.fcp.ProtocolError;
59 import net.pterodactylus.fcp.RemovePeer;
60 import net.pterodactylus.fcp.SSKKeypair;
61 import net.pterodactylus.fcp.SimpleProgress;
62 import net.pterodactylus.fcp.WatchGlobal;
63 import net.pterodactylus.util.thread.ObjectWrapper;
64
65 /**
66  * High-level FCP client that hides the details of the underlying FCP
67  * implementation.
68  *
69  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
70  */
71 public class FcpClient {
72
73         /** Object used for synchronization. */
74         private final Object syncObject = new Object();
75
76         /** The name of this client. */
77         private final String name;
78
79         /** The underlying FCP connection. */
80         private final FcpConnection fcpConnection;
81
82         /**
83          * Creates an FCP client with the given name.
84          *
85          * @param name
86          *            The name of the FCP client
87          * @throws UnknownHostException
88          *             if the hostname “localhost” is unknown
89          */
90         public FcpClient(String name) throws UnknownHostException {
91                 this(name, "localhost");
92         }
93
94         /**
95          * Creates an FCP client.
96          *
97          * @param name
98          *            The name of the FCP client
99          * @param hostname
100          *            The hostname of the Freenet node
101          * @throws UnknownHostException
102          *             if the given hostname can not be resolved
103          */
104         public FcpClient(String name, String hostname) throws UnknownHostException {
105                 this(name, hostname, FcpConnection.DEFAULT_PORT);
106         }
107
108         /**
109          * Creates an FCP client.
110          *
111          * @param name
112          *            The name of the FCP client
113          * @param hostname
114          *            The hostname of the Freenet node
115          * @param port
116          *            The Freenet node’s FCP port
117          * @throws UnknownHostException
118          *             if the given hostname can not be resolved
119          */
120         public FcpClient(String name, String hostname, int port) throws UnknownHostException {
121                 this(name, InetAddress.getByName(hostname), port);
122         }
123
124         /**
125          * Creates an FCP client.
126          *
127          * @param name
128          *            The name of the FCP client
129          * @param host
130          *            The host address of the Freenet node
131          */
132         public FcpClient(String name, InetAddress host) {
133                 this(name, host, FcpConnection.DEFAULT_PORT);
134         }
135
136         /**
137          * Creates an FCP client.
138          *
139          * @param name
140          *            The name of the FCP client
141          * @param host
142          *            The host address of the Freenet node
143          * @param port
144          *            The Freenet node’s FCP port
145          */
146         public FcpClient(String name, InetAddress host, int port) {
147                 this.name = name;
148                 fcpConnection = new FcpConnection(host, port);
149         }
150
151         //
152         // ACTIONS
153         //
154
155         /**
156          * Connects the FCP client.
157          *
158          * @throws IOException
159          *             if an I/O error occurs
160          * @throws FcpException
161          *             if an FCP error occurs
162          */
163         public void connect() throws IOException, FcpException {
164                 new ExtendedFcpAdapter() {
165
166                         /**
167                          * {@inheritDoc}
168                          */
169                         @Override
170                         @SuppressWarnings("synthetic-access")
171                         public void run() throws IOException {
172                                 fcpConnection.connect();
173                                 ClientHello clientHello = new ClientHello(name);
174                                 fcpConnection.sendMessage(clientHello);
175                                 WatchGlobal watchGlobal = new WatchGlobal(true);
176                                 fcpConnection.sendMessage(watchGlobal);
177                         }
178
179                         /**
180                          * {@inheritDoc}
181                          */
182                         @Override
183                         public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
184                                 completionLatch.countDown();
185                         }
186                 }.execute();
187         }
188
189         /**
190          * Disconnects the FCP client.
191          */
192         public void disconnect() {
193                 synchronized (syncObject) {
194                         fcpConnection.close();
195                         syncObject.notifyAll();
196                 }
197         }
198
199         //
200         // PEER MANAGEMENT
201         //
202
203         /**
204          * Returns all peers that the node has.
205          *
206          * @param withMetadata
207          *            <code>true</code> to include peer metadata
208          * @param withVolatile
209          *            <code>true</code> to include volatile peer data
210          * @return A set containing the node’s peers
211          * @throws IOException
212          *             if an I/O error occurs
213          * @throws FcpException
214          *             if an FCP error occurs
215          */
216         public Set<Peer> getPeers(final boolean withMetadata, final boolean withVolatile) throws IOException, FcpException {
217                 final Set<Peer> peers = Collections.synchronizedSet(new HashSet<Peer>());
218                 new ExtendedFcpAdapter() {
219
220                         /** The ID of the “ListPeers” request. */
221                         @SuppressWarnings("synthetic-access")
222                         private String identifier = createIdentifier("list-peers");
223
224                         /**
225                          * {@inheritDoc}
226                          */
227                         @Override
228                         @SuppressWarnings("synthetic-access")
229                         public void run() throws IOException {
230                                 fcpConnection.sendMessage(new ListPeers(identifier, withMetadata, withVolatile));
231                         }
232
233                         /**
234                          * {@inheritDoc}
235                          */
236                         @Override
237                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
238                                 if (peer.getIdentifier().equals(identifier)) {
239                                         peers.add(peer);
240                                 }
241                         }
242
243                         /**
244                          * {@inheritDoc}
245                          */
246                         @Override
247                         public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
248                                 if (endListPeers.getIdentifier().equals(identifier)) {
249                                         completionLatch.countDown();
250                                 }
251                         }
252                 }.execute();
253                 return peers;
254         }
255
256         /**
257          * Adds the given peer to the node.
258          *
259          * @param peer
260          *            The peer to add
261          * @throws IOException
262          *             if an I/O error occurs
263          * @throws FcpException
264          *             if an FCP error occurs
265          */
266         public void addPeer(Peer peer) throws IOException, FcpException {
267                 addPeer(peer.getNodeRef());
268         }
269
270         /**
271          * Adds the peer defined by the noderef to the node.
272          *
273          * @param nodeRef
274          *            The noderef that defines the new peer
275          * @throws IOException
276          *             if an I/O error occurs
277          * @throws FcpException
278          *             if an FCP error occurs
279          */
280         public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
281                 addPeer(new AddPeer(nodeRef));
282         }
283
284         /**
285          * Adds a peer, reading the noderef from the given URL.
286          *
287          * @param url
288          *            The URL to read the noderef from
289          * @throws IOException
290          *             if an I/O error occurs
291          * @throws FcpException
292          *             if an FCP error occurs
293          */
294         public void addPeer(URL url) throws IOException, FcpException {
295                 addPeer(new AddPeer(url));
296         }
297
298         /**
299          * Adds a peer, reading the noderef of the peer from the given file.
300          * <strong>Note:</strong> the file to read the noderef from has to reside on
301          * the same machine as the node!
302          *
303          * @param file
304          *            The name of the file containing the peer’s noderef
305          * @throws IOException
306          *             if an I/O error occurs
307          * @throws FcpException
308          *             if an FCP error occurs
309          */
310         public void addPeer(String file) throws IOException, FcpException {
311                 addPeer(new AddPeer(file));
312         }
313
314         /**
315          * Sends the given {@link AddPeer} message to the node. This method should
316          * not be called directly. Use one of {@link #addPeer(Peer)},
317          * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
318          * {@link #addPeer(String)} instead.
319          *
320          * @param addPeer
321          *            The “AddPeer” message
322          * @throws IOException
323          *             if an I/O error occurs
324          * @throws FcpException
325          *             if an FCP error occurs
326          */
327         private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
328                 new ExtendedFcpAdapter() {
329
330                         /**
331                          * {@inheritDoc}
332                          */
333                         @Override
334                         @SuppressWarnings("synthetic-access")
335                         public void run() throws IOException {
336                                 fcpConnection.sendMessage(addPeer);
337                         }
338
339                         /**
340                          * {@inheritDoc}
341                          */
342                         @Override
343                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
344                                 completionLatch.countDown();
345                         }
346                 }.execute();
347         }
348
349         /**
350          * Modifies the given peer.
351          *
352          * @param peer
353          *            The peer to modify
354          * @param allowLocalAddresses
355          *            <code>true</code> to allow local address, <code>false</code>
356          *            to not allow local address, <code>null</code> to not change
357          *            the setting
358          * @param disabled
359          *            <code>true</code> to disable the peer, <code>false</code> to
360          *            enable the peer, <code>null</code> to not change the setting
361          * @param listenOnly
362          *            <code>true</code> to enable “listen only” for the peer,
363          *            <code>false</code> to disable it, <code>null</code> to not
364          *            change it
365          * @throws IOException
366          *             if an I/O error occurs
367          * @throws FcpException
368          *             if an FCP error occurs
369          */
370         public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
371                 new ExtendedFcpAdapter() {
372
373                         /**
374                          * {@inheritDoc}
375                          */
376                         @Override
377                         @SuppressWarnings("synthetic-access")
378                         public void run() throws IOException {
379                                 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
380                         }
381
382                         /**
383                          * {@inheritDoc}
384                          */
385                         @Override
386                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
387                                 completionLatch.countDown();
388                         }
389                 }.execute();
390         }
391
392         /**
393          * Removes the given peer.
394          *
395          * @param peer
396          *            The peer to remove
397          * @throws IOException
398          *             if an I/O error occurs
399          * @throws FcpException
400          *             if an FCP error occurs
401          */
402         public void removePeer(final Peer peer) throws IOException, FcpException {
403                 new ExtendedFcpAdapter() {
404
405                         /**
406                          * {@inheritDoc}
407                          */
408                         @Override
409                         @SuppressWarnings("synthetic-access")
410                         public void run() throws IOException {
411                                 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
412                         }
413
414                         /**
415                          * {@inheritDoc}
416                          */
417                         @Override
418                         public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
419                                 completionLatch.countDown();
420                         }
421                 }.execute();
422         }
423
424         //
425         // PEER NOTES MANAGEMENT
426         //
427
428         /**
429          * Returns the peer note of the given peer.
430          *
431          * @param peer
432          *            The peer to get the note for
433          * @return The peer’s note
434          * @throws IOException
435          *             if an I/O error occurs
436          * @throws FcpException
437          *             if an FCP error occurs
438          */
439         public PeerNote getPeerNote(final Peer peer) throws IOException, FcpException {
440                 final ObjectWrapper<PeerNote> objectWrapper = new ObjectWrapper<PeerNote>();
441                 new ExtendedFcpAdapter() {
442
443                         /**
444                          * {@inheritDoc}
445                          */
446                         @Override
447                         @SuppressWarnings("synthetic-access")
448                         public void run() throws IOException {
449                                 fcpConnection.sendMessage(new ListPeerNotes(peer.getIdentity()));
450                         }
451
452                         /**
453                          * {@inheritDoc}
454                          */
455                         @Override
456                         public void receivedPeerNote(FcpConnection fcpConnection, PeerNote peerNote) {
457                                 if (peerNote.getNodeIdentifier().equals(peer.getIdentity())) {
458                                         objectWrapper.set(peerNote);
459                                 }
460                         }
461
462                         /**
463                          * {@inheritDoc}
464                          */
465                         @Override
466                         public void receivedEndListPeerNotes(FcpConnection fcpConnection, EndListPeerNotes endListPeerNotes) {
467                                 completionLatch.countDown();
468                         }
469                 }.execute();
470                 return objectWrapper.get();
471         }
472
473         /**
474          * Replaces the peer note for the given peer.
475          *
476          * @param peer
477          *            The peer
478          * @param noteText
479          *            The new base64-encoded note text
480          * @param noteType
481          *            The type of the note (currently only <code>1</code> is
482          *            allowed)
483          * @throws IOException
484          *             if an I/O error occurs
485          * @throws FcpException
486          *             if an FCP error occurs
487          */
488         public void modifyPeerNote(final Peer peer, final String noteText, final int noteType) throws IOException, FcpException {
489                 new ExtendedFcpAdapter() {
490
491                         /**
492                          * {@inheritDoc}
493                          */
494                         @Override
495                         @SuppressWarnings("synthetic-access")
496                         public void run() throws IOException {
497                                 fcpConnection.sendMessage(new ModifyPeerNote(peer.getIdentity(), noteText, noteType));
498                         }
499
500                         /**
501                          * {@inheritDoc}
502                          */
503                         @Override
504                         public void receivedPeer(FcpConnection fcpConnection, Peer receivedPeer) {
505                                 if (receivedPeer.getIdentity().equals(peer.getIdentity())) {
506                                         completionLatch.countDown();
507                                 }
508                         }
509                 }.execute();
510         }
511
512         //
513         // KEY GENERATION
514         //
515
516         /**
517          * Generates a new SSK key pair.
518          *
519          * @return The generated key pair
520          * @throws IOException
521          *             if an I/O error occurs
522          * @throws FcpException
523          *             if an FCP error occurs
524          */
525         public SSKKeypair generateKeyPair() throws IOException, FcpException {
526                 final ObjectWrapper<SSKKeypair> sskKeypairWrapper = new ObjectWrapper<SSKKeypair>();
527                 new ExtendedFcpAdapter() {
528
529                         /**
530                          * {@inheritDoc}
531                          */
532                         @Override
533                         @SuppressWarnings("synthetic-access")
534                         public void run() throws IOException {
535                                 fcpConnection.sendMessage(new GenerateSSK());
536                         }
537
538                         /**
539                          * {@inheritDoc}
540                          */
541                         @Override
542                         public void receivedSSKKeypair(FcpConnection fcpConnection, SSKKeypair sskKeypair) {
543                                 sskKeypairWrapper.set(sskKeypair);
544                                 completionLatch.countDown();
545                         }
546                 }.execute();
547                 return sskKeypairWrapper.get();
548         }
549
550         //
551         // REQUEST MANAGEMENT
552         //
553
554         /**
555          * Returns all currently visible persistent get requests.
556          *
557          * @param global
558          *            <code>true</code> to return get requests from the global
559          *            queue, <code>false</code> to only show requests from the
560          *            client-local queue
561          * @return All get requests
562          * @throws IOException
563          *             if an I/O error occurs
564          * @throws FcpException
565          *             if an FCP error occurs
566          */
567         public Collection<Request> getGetRequests(final boolean global) throws IOException, FcpException {
568                 return getRequests(global);
569         }
570
571         public Collection<Request> getRequests(final boolean global) throws IOException, FcpException {
572                 final Map<String, Request> requests = Collections.synchronizedMap(new HashMap<String, Request>());
573                 new ExtendedFcpAdapter() {
574
575                         /**
576                          * {@inheritDoc}
577                          */
578                         @Override
579                         @SuppressWarnings("synthetic-access")
580                         public void run() throws IOException {
581                                 fcpConnection.sendMessage(new ListPersistentRequests());
582                         }
583
584                         /**
585                          * {@inheritDoc}
586                          */
587                         @Override
588                         public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
589                                 if (!persistentGet.isGlobal() || global) {
590                                         GetRequest getRequest = new GetRequest(persistentGet);
591                                         requests.put(persistentGet.getIdentifier(), getRequest);
592                                 }
593                         }
594
595                         /**
596                          * {@inheritDoc}
597                          *
598                          * @see net.pterodactylus.fcp.FcpAdapter#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
599                          *      net.pterodactylus.fcp.DataFound)
600                          */
601                         @Override
602                         public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
603                                 Request getRequest = requests.get(dataFound.getIdentifier());
604                                 if (getRequest == null) {
605                                         return;
606                                 }
607                                 getRequest.setComplete(true);
608                                 getRequest.setLength(dataFound.getDataLength());
609                                 getRequest.setContentType(dataFound.getMetadataContentType());
610                         }
611
612                         /**
613                          * {@inheritDoc}
614                          *
615                          * @see net.pterodactylus.fcp.FcpAdapter#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
616                          *      net.pterodactylus.fcp.GetFailed)
617                          */
618                         @Override
619                         public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
620                                 Request getRequest = requests.get(getFailed.getIdentifier());
621                                 if (getRequest == null) {
622                                         return;
623                                 }
624                                 getRequest.setComplete(true);
625                                 getRequest.setFailed(true);
626                                 getRequest.setFatal(getFailed.isFatal());
627                                 getRequest.setErrorCode(getFailed.getCode());
628                         }
629
630                         /**
631                          * {@inheritDoc}
632                          *
633                          * @see net.pterodactylus.fcp.FcpAdapter#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
634                          *      net.pterodactylus.fcp.PersistentPut)
635                          */
636                         @Override
637                         public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
638                                 if (!persistentPut.isGlobal() || global) {
639                                         PutRequest putRequest = new PutRequest(persistentPut);
640                                         requests.put(persistentPut.getIdentifier(), putRequest);
641                                 }
642                         }
643
644                         /**
645                          * {@inheritDoc}
646                          *
647                          * @see net.pterodactylus.fcp.FcpAdapter#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
648                          *      net.pterodactylus.fcp.SimpleProgress)
649                          */
650                         @Override
651                         public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
652                                 Request request = requests.get(simpleProgress.getIdentifier());
653                                 if (request == null) {
654                                         return;
655                                 }
656                                 request.setTotalBlocks(simpleProgress.getTotal());
657                                 request.setRequiredBlocks(simpleProgress.getRequired());
658                                 request.setFailedBlocks(simpleProgress.getFailed());
659                                 request.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
660                                 request.setSucceededBlocks(simpleProgress.getSucceeded());
661                                 request.setFinalizedTotal(simpleProgress.isFinalizedTotal());
662                         }
663
664                         /**
665                          * {@inheritDoc}
666                          */
667                         @Override
668                         public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
669                                 completionLatch.countDown();
670                         }
671                 }.execute();
672                 return requests.values();
673         }
674
675         //
676         // PRIVATE METHODS
677         //
678
679         /**
680          * Creates a unique request identifier.
681          *
682          * @param basename
683          *            The basename of the request
684          * @return The created request identifier
685          */
686         private String createIdentifier(String basename) {
687                 return basename + "-" + System.currentTimeMillis() + "-" + (int) (Math.random() * Integer.MAX_VALUE);
688         }
689
690         /**
691          * Implementation of an {@link FcpListener} that can store an
692          * {@link FcpException} and wait for the arrival of a certain command.
693          *
694          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
695          */
696         private abstract class ExtendedFcpAdapter extends FcpAdapter {
697
698                 /** The count down latch used to wait for completion. */
699                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
700
701                 /** The FCP exception, if any. */
702                 protected FcpException fcpException;
703
704                 /**
705                  * Creates a new extended FCP adapter.
706                  */
707                 public ExtendedFcpAdapter() {
708                         /* do nothing. */
709                 }
710
711                 /**
712                  * Executes the FCP commands in {@link #run()}, wrapping the execution
713                  * and catching exceptions.
714                  *
715                  * @throws IOException
716                  *             if an I/O error occurs
717                  * @throws FcpException
718                  *             if an FCP error occurs
719                  */
720                 @SuppressWarnings("synthetic-access")
721                 public void execute() throws IOException, FcpException {
722                         fcpConnection.addFcpListener(this);
723                         try {
724                                 run();
725                                 while (true) {
726                                         try {
727                                                 completionLatch.await();
728                                                 break;
729                                         } catch (InterruptedException ie1) {
730                                                 /* ignore, we’ll loop. */
731                                         }
732                                 }
733                         } finally {
734                                 fcpConnection.removeFcpListener(this);
735                         }
736                         if (fcpException != null) {
737                                 throw fcpException;
738                         }
739                 }
740
741                 /**
742                  * The FCP commands that actually get executed.
743                  *
744                  * @throws IOException
745                  *             if an I/O error occurs
746                  */
747                 public abstract void run() throws IOException;
748
749                 /**
750                  * {@inheritDoc}
751                  */
752                 @Override
753                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
754                         fcpException = new FcpException("Connection closed", throwable);
755                         completionLatch.countDown();
756                 }
757
758                 /**
759                  * {@inheritDoc}
760                  */
761                 @Override
762                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
763                         fcpException = new FcpException("Connection closed, duplicate client name");
764                         completionLatch.countDown();
765                 }
766
767                 /**
768                  * {@inheritDoc}
769                  */
770                 @Override
771                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
772                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
773                         completionLatch.countDown();
774                 }
775
776         }
777
778 }