683c25a023de6bc3b72d5325f9c5b905b8e01d11
[jFCPlib.git] / src / test / java / net / pterodactylus / fcp / quelaton / DefaultFcpClientTest.java
1 package net.pterodactylus.fcp.quelaton;
2
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.contains;
5 import static org.hamcrest.Matchers.containsInAnyOrder;
6 import static org.hamcrest.Matchers.hasSize;
7 import static org.hamcrest.Matchers.is;
8 import static org.hamcrest.Matchers.not;
9 import static org.hamcrest.Matchers.notNullValue;
10 import static org.hamcrest.Matchers.startsWith;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.File;
14 import java.io.IOException;
15 import java.net.URL;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.concurrent.CopyOnWriteArrayList;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.Future;
25 import java.util.stream.Collectors;
26
27 import net.pterodactylus.fcp.ARK;
28 import net.pterodactylus.fcp.ConfigData;
29 import net.pterodactylus.fcp.DSAGroup;
30 import net.pterodactylus.fcp.FcpKeyPair;
31 import net.pterodactylus.fcp.Key;
32 import net.pterodactylus.fcp.NodeData;
33 import net.pterodactylus.fcp.NodeRef;
34 import net.pterodactylus.fcp.Peer;
35 import net.pterodactylus.fcp.PeerNote;
36 import net.pterodactylus.fcp.PluginInfo;
37 import net.pterodactylus.fcp.Priority;
38 import net.pterodactylus.fcp.fake.FakeTcpServer;
39 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
40
41 import com.google.common.io.ByteStreams;
42 import com.google.common.io.Files;
43 import com.nitorcreations.junit.runners.NestedRunner;
44 import org.hamcrest.Description;
45 import org.hamcrest.Matcher;
46 import org.hamcrest.TypeSafeDiagnosingMatcher;
47 import org.junit.After;
48 import org.junit.Assert;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51
52 /**
53  * Unit test for {@link DefaultFcpClient}.
54  *
55  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
56  */
57 @RunWith(NestedRunner.class)
58 public class DefaultFcpClientTest {
59
60         private static final String INSERT_URI =
61                 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
62         private static final String REQUEST_URI =
63                 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
64
65         private int threadCounter = 0;
66         private final ExecutorService threadPool =
67                 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
68         private final FakeTcpServer fcpServer;
69         private final DefaultFcpClient fcpClient;
70
71         public DefaultFcpClientTest() throws IOException {
72                 fcpServer = new FakeTcpServer(threadPool);
73                 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
74         }
75
76         @After
77         public void tearDown() throws IOException {
78                 fcpServer.close();
79                 threadPool.shutdown();
80         }
81
82         @Test(expected = ExecutionException.class)
83         public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
84         throws IOException, ExecutionException, InterruptedException {
85                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
86                 fcpServer.connect().get();
87                 fcpServer.collectUntil(is("EndMessage"));
88                 fcpServer.writeLine(
89                         "CloseConnectionDuplicateClientName",
90                         "EndMessage"
91                 );
92                 keyPairFuture.get();
93         }
94
95         @Test(expected = ExecutionException.class)
96         public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
97         throws IOException, ExecutionException, InterruptedException {
98                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
99                 fcpServer.connect().get();
100                 fcpServer.collectUntil(is("EndMessage"));
101                 fcpServer.close();
102                 keyPairFuture.get();
103         }
104
105         @Test
106         public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
107                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
108                 connectNode();
109                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
110                 String identifier = extractIdentifier(lines);
111                 fcpServer.writeLine("SSKKeypair",
112                         "InsertURI=" + INSERT_URI + "",
113                         "RequestURI=" + REQUEST_URI + "",
114                         "Identifier=" + identifier,
115                         "EndMessage");
116                 FcpKeyPair keyPair = keyPairFuture.get();
117                 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
118                 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
119         }
120
121         private void connectNode() throws InterruptedException, ExecutionException, IOException {
122                 fcpServer.connect().get();
123                 fcpServer.collectUntil(is("EndMessage"));
124                 fcpServer.writeLine("NodeHello",
125                         "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
126                         "Revision=build01466",
127                         "Testnet=false",
128                         "Version=Fred,0.7,1.0,1466",
129                         "Build=1466",
130                         "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
131                         "Node=Fred",
132                         "ExtBuild=29",
133                         "FCPVersion=2.0",
134                         "NodeLanguage=ENGLISH",
135                         "ExtRevision=v29",
136                         "EndMessage"
137                 );
138         }
139
140         @Test
141         public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
142                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
143                 connectNode();
144                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
145                 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
146                 String identifier = extractIdentifier(lines);
147                 fcpServer.writeLine(
148                         "AllData",
149                         "Identifier=" + identifier,
150                         "DataLength=6",
151                         "StartupTime=1435610539000",
152                         "CompletionTime=1435610540000",
153                         "Metadata.ContentType=text/plain;charset=utf-8",
154                         "Data",
155                         "Hello"
156                 );
157                 Optional<Data> data = dataFuture.get();
158                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
159                 assertThat(data.get().size(), is(6L));
160                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
161                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
162         }
163
164         private String extractIdentifier(List<String> lines) {
165                 return lines.stream()
166                         .filter(s -> s.startsWith("Identifier="))
167                         .map(s -> s.substring(s.indexOf('=') + 1))
168                         .findFirst()
169                         .orElse("");
170         }
171
172         @Test
173         public void clientGetDownloadsDataForCorrectIdentifier()
174         throws InterruptedException, ExecutionException, IOException {
175                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
176                 connectNode();
177                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
178                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
179                 String identifier = extractIdentifier(lines);
180                 fcpServer.writeLine(
181                         "AllData",
182                         "Identifier=not-test",
183                         "DataLength=12",
184                         "StartupTime=1435610539000",
185                         "CompletionTime=1435610540000",
186                         "Metadata.ContentType=text/plain;charset=latin-9",
187                         "Data",
188                         "Hello World"
189                 );
190                 fcpServer.writeLine(
191                         "AllData",
192                         "Identifier=" + identifier,
193                         "DataLength=6",
194                         "StartupTime=1435610539000",
195                         "CompletionTime=1435610540000",
196                         "Metadata.ContentType=text/plain;charset=utf-8",
197                         "Data",
198                         "Hello"
199                 );
200                 Optional<Data> data = dataFuture.get();
201                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
202                 assertThat(data.get().size(), is(6L));
203                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
204                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
205         }
206
207         @Test
208         public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
209                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
210                 connectNode();
211                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
212                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
213                 String identifier = extractIdentifier(lines);
214                 fcpServer.writeLine(
215                         "GetFailed",
216                         "Identifier=" + identifier,
217                         "Code=3",
218                         "EndMessage"
219                 );
220                 Optional<Data> data = dataFuture.get();
221                 assertThat(data.isPresent(), is(false));
222         }
223
224         @Test
225         public void clientGetRecognizesGetFailedForCorrectIdentifier()
226         throws InterruptedException, ExecutionException, IOException {
227                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
228                 connectNode();
229                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
230                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
231                 String identifier = extractIdentifier(lines);
232                 fcpServer.writeLine(
233                         "GetFailed",
234                         "Identifier=not-test",
235                         "Code=3",
236                         "EndMessage"
237                 );
238                 fcpServer.writeLine(
239                         "GetFailed",
240                         "Identifier=" + identifier,
241                         "Code=3",
242                         "EndMessage"
243                 );
244                 Optional<Data> data = dataFuture.get();
245                 assertThat(data.isPresent(), is(false));
246         }
247
248         @Test(expected = ExecutionException.class)
249         public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
250                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
251                 connectNode();
252                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
253                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
254                 fcpServer.close();
255                 dataFuture.get();
256         }
257
258         @Test
259         public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
260                 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
261                 connectNode();
262                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
263                 String identifier = extractIdentifier(lines);
264                 fcpServer.writeLine(
265                         "SSKKeypair",
266                         "InsertURI=" + INSERT_URI + "",
267                         "RequestURI=" + REQUEST_URI + "",
268                         "Identifier=" + identifier,
269                         "EndMessage"
270                 );
271                 keyPair.get();
272                 keyPair = fcpClient.generateKeypair().execute();
273                 lines = fcpServer.collectUntil(is("EndMessage"));
274                 identifier = extractIdentifier(lines);
275                 fcpServer.writeLine(
276                         "SSKKeypair",
277                         "InsertURI=" + INSERT_URI + "",
278                         "RequestURI=" + REQUEST_URI + "",
279                         "Identifier=" + identifier,
280                         "EndMessage"
281                 );
282                 keyPair.get();
283         }
284
285         @Test
286         public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
287         throws InterruptedException, ExecutionException, IOException {
288                 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
289                 connectNode();
290                 fcpServer.collectUntil(is("EndMessage"));
291                 fcpServer.close();
292                 try {
293                         keyPair.get();
294                         Assert.fail();
295                 } catch (ExecutionException e) {
296                 }
297                 keyPair = fcpClient.generateKeypair().execute();
298                 connectNode();
299                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
300                 String identifier = extractIdentifier(lines);
301                 fcpServer.writeLine(
302                         "SSKKeypair",
303                         "InsertURI=" + INSERT_URI + "",
304                         "RequestURI=" + REQUEST_URI + "",
305                         "Identifier=" + identifier,
306                         "EndMessage"
307                 );
308                 keyPair.get();
309         }
310
311         @Test
312         public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
313         throws InterruptedException, ExecutionException, IOException {
314                 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
315                 connectNode();
316                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
317                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
318         }
319
320         @Test
321         public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
322         throws InterruptedException, ExecutionException, IOException {
323                 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
324                 connectNode();
325                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
326                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
327         }
328
329         @Test
330         public void clientGetWithMaxSizeSettingSendsCorrectCommands()
331         throws InterruptedException, ExecutionException, IOException {
332                 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
333                 connectNode();
334                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
335                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
336         }
337
338         @Test
339         public void clientGetWithPrioritySettingSendsCorrectCommands()
340         throws InterruptedException, ExecutionException, IOException {
341                 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
342                 connectNode();
343                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
344                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
345         }
346
347         @Test
348         public void clientGetWithRealTimeSettingSendsCorrectCommands()
349         throws InterruptedException, ExecutionException, IOException {
350                 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
351                 connectNode();
352                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
353                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
354         }
355
356         @Test
357         public void clientGetWithGlobalSettingSendsCorrectCommands()
358         throws InterruptedException, ExecutionException, IOException {
359                 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
360                 connectNode();
361                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
362                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
363         }
364
365         private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
366                 return new TypeSafeDiagnosingMatcher<List<String>>() {
367                         @Override
368                         protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
369                                 if (!item.get(0).equals(name)) {
370                                         mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
371                                         return false;
372                                 }
373                                 for (String requiredLine : requiredLines) {
374                                         if (item.indexOf(requiredLine) < 1) {
375                                                 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
376                                                 return false;
377                                         }
378                                 }
379                                 return true;
380                         }
381
382                         @Override
383                         public void describeTo(Description description) {
384                                 description.appendText("FCP message named ").appendValue(name);
385                                 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
386                         }
387                 };
388         }
389
390         @Test
391         public void clientPutWithDirectDataSendsCorrectCommand()
392         throws IOException, ExecutionException, InterruptedException {
393                 fcpClient.clientPut()
394                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
395                         .length(6)
396                         .uri("KSK@foo.txt")
397                         .execute();
398                 connectNode();
399                 List<String> lines = fcpServer.collectUntil(is("Hello"));
400                 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
401         }
402
403         @Test
404         public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
405         throws InterruptedException, ExecutionException, IOException {
406                 Future<Optional<Key>> key = fcpClient.clientPut()
407                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
408                         .length(6)
409                         .uri("KSK@foo.txt")
410                         .execute();
411                 connectNode();
412                 List<String> lines = fcpServer.collectUntil(is("Hello"));
413                 String identifier = extractIdentifier(lines);
414                 fcpServer.writeLine(
415                         "PutFailed",
416                         "Identifier=not-the-right-one",
417                         "EndMessage"
418                 );
419                 fcpServer.writeLine(
420                         "PutSuccessful",
421                         "URI=KSK@foo.txt",
422                         "Identifier=" + identifier,
423                         "EndMessage"
424                 );
425                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
426         }
427
428         @Test
429         public void clientPutWithDirectDataFailsOnCorrectIdentifier()
430         throws InterruptedException, ExecutionException, IOException {
431                 Future<Optional<Key>> key = fcpClient.clientPut()
432                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
433                         .length(6)
434                         .uri("KSK@foo.txt")
435                         .execute();
436                 connectNode();
437                 List<String> lines = fcpServer.collectUntil(is("Hello"));
438                 String identifier = extractIdentifier(lines);
439                 fcpServer.writeLine(
440                         "PutSuccessful",
441                         "Identifier=not-the-right-one",
442                         "URI=KSK@foo.txt",
443                         "EndMessage"
444                 );
445                 fcpServer.writeLine(
446                         "PutFailed",
447                         "Identifier=" + identifier,
448                         "EndMessage"
449                 );
450                 assertThat(key.get().isPresent(), is(false));
451         }
452
453         @Test
454         public void clientPutWithRenamedDirectDataSendsCorrectCommand()
455         throws InterruptedException, ExecutionException, IOException {
456                 fcpClient.clientPut()
457                         .named("otherName.txt")
458                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
459                         .length(6)
460                         .uri("KSK@foo.txt")
461                         .execute();
462                 connectNode();
463                 List<String> lines = fcpServer.collectUntil(is("Hello"));
464                 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
465                         "DataLength=6", "URI=KSK@foo.txt"));
466         }
467
468         @Test
469         public void clientPutWithRedirectSendsCorrectCommand()
470         throws IOException, ExecutionException, InterruptedException {
471                 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
472                 connectNode();
473                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
474                 assertThat(lines,
475                         matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
476         }
477
478         @Test
479         public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
480                 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
481                 connectNode();
482                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
483                 assertThat(lines,
484                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
485         }
486
487         @Test
488         public void clientPutWithFileCanCompleteTestDdaSequence()
489         throws IOException, ExecutionException, InterruptedException {
490                 File tempFile = createTempFile();
491                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
492                 connectNode();
493                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
494                 String identifier = extractIdentifier(lines);
495                 fcpServer.writeLine(
496                         "ProtocolError",
497                         "Identifier=" + identifier,
498                         "Code=25",
499                         "EndMessage"
500                 );
501                 lines = fcpServer.collectUntil(is("EndMessage"));
502                 assertThat(lines, matchesFcpMessage(
503                         "TestDDARequest",
504                         "Directory=" + tempFile.getParent(),
505                         "WantReadDirectory=true",
506                         "WantWriteDirectory=false",
507                         "EndMessage"
508                 ));
509                 fcpServer.writeLine(
510                         "TestDDAReply",
511                         "Directory=" + tempFile.getParent(),
512                         "ReadFilename=" + tempFile,
513                         "EndMessage"
514                 );
515                 lines = fcpServer.collectUntil(is("EndMessage"));
516                 assertThat(lines, matchesFcpMessage(
517                         "TestDDAResponse",
518                         "Directory=" + tempFile.getParent(),
519                         "ReadContent=test-content",
520                         "EndMessage"
521                 ));
522                 fcpServer.writeLine(
523                         "TestDDAComplete",
524                         "Directory=" + tempFile.getParent(),
525                         "ReadDirectoryAllowed=true",
526                         "EndMessage"
527                 );
528                 lines = fcpServer.collectUntil(is("EndMessage"));
529                 assertThat(lines,
530                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
531                                 "Filename=" + new File(tempFile.getParent(), "test.dat")));
532         }
533
534         private File createTempFile() throws IOException {
535                 File tempFile = File.createTempFile("test-dda-", ".dat");
536                 tempFile.deleteOnExit();
537                 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
538                 return tempFile;
539         }
540
541         @Test
542         public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
543         throws InterruptedException, ExecutionException, IOException {
544                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
545                 connectNode();
546                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
547                 String identifier = extractIdentifier(lines);
548                 fcpServer.writeLine(
549                         "ProtocolError",
550                         "Identifier=not-the-right-one",
551                         "Code=25",
552                         "EndMessage"
553                 );
554                 fcpServer.writeLine(
555                         "PutSuccessful",
556                         "Identifier=" + identifier,
557                         "URI=KSK@foo.txt",
558                         "EndMessage"
559                 );
560                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
561         }
562
563         @Test
564         public void clientPutAbortsOnProtocolErrorOtherThan25()
565         throws InterruptedException, ExecutionException, IOException {
566                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
567                 connectNode();
568                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
569                 String identifier = extractIdentifier(lines);
570                 fcpServer.writeLine(
571                         "ProtocolError",
572                         "Identifier=" + identifier,
573                         "Code=1",
574                         "EndMessage"
575                 );
576                 assertThat(key.get().isPresent(), is(false));
577         }
578
579         @Test
580         public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
581         InterruptedException {
582                 File tempFile = createTempFile();
583                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
584                 connectNode();
585                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
586                 String identifier = extractIdentifier(lines);
587                 fcpServer.writeLine(
588                         "ProtocolError",
589                         "Identifier=" + identifier,
590                         "Code=25",
591                         "EndMessage"
592                 );
593                 lines = fcpServer.collectUntil(is("EndMessage"));
594                 assertThat(lines, matchesFcpMessage(
595                         "TestDDARequest",
596                         "Directory=" + tempFile.getParent(),
597                         "WantReadDirectory=true",
598                         "WantWriteDirectory=false",
599                         "EndMessage"
600                 ));
601                 fcpServer.writeLine(
602                         "TestDDAReply",
603                         "Directory=/some-other-directory",
604                         "ReadFilename=" + tempFile,
605                         "EndMessage"
606                 );
607                 fcpServer.writeLine(
608                         "TestDDAReply",
609                         "Directory=" + tempFile.getParent(),
610                         "ReadFilename=" + tempFile,
611                         "EndMessage"
612                 );
613                 lines = fcpServer.collectUntil(is("EndMessage"));
614                 assertThat(lines, matchesFcpMessage(
615                         "TestDDAResponse",
616                         "Directory=" + tempFile.getParent(),
617                         "ReadContent=test-content",
618                         "EndMessage"
619                 ));
620         }
621
622         @Test
623         public void clientPutSendsResponseEvenIfFileCanNotBeRead()
624         throws IOException, ExecutionException, InterruptedException {
625                 File tempFile = createTempFile();
626                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
627                 connectNode();
628                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
629                 String identifier = extractIdentifier(lines);
630                 fcpServer.writeLine(
631                         "ProtocolError",
632                         "Identifier=" + identifier,
633                         "Code=25",
634                         "EndMessage"
635                 );
636                 lines = fcpServer.collectUntil(is("EndMessage"));
637                 assertThat(lines, matchesFcpMessage(
638                         "TestDDARequest",
639                         "Directory=" + tempFile.getParent(),
640                         "WantReadDirectory=true",
641                         "WantWriteDirectory=false",
642                         "EndMessage"
643                 ));
644                 fcpServer.writeLine(
645                         "TestDDAReply",
646                         "Directory=" + tempFile.getParent(),
647                         "ReadFilename=" + tempFile + ".foo",
648                         "EndMessage"
649                 );
650                 lines = fcpServer.collectUntil(is("EndMessage"));
651                 assertThat(lines, matchesFcpMessage(
652                         "TestDDAResponse",
653                         "Directory=" + tempFile.getParent(),
654                         "ReadContent=failed-to-read",
655                         "EndMessage"
656                 ));
657         }
658
659         @Test
660         public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
661         throws IOException, ExecutionException, InterruptedException {
662                 File tempFile = createTempFile();
663                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
664                 connectNode();
665                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
666                 String identifier = extractIdentifier(lines);
667                 fcpServer.writeLine(
668                         "TestDDAComplete",
669                         "Directory=/some-other-directory",
670                         "EndMessage"
671                 );
672                 fcpServer.writeLine(
673                         "ProtocolError",
674                         "Identifier=" + identifier,
675                         "Code=25",
676                         "EndMessage"
677                 );
678                 lines = fcpServer.collectUntil(is("EndMessage"));
679                 assertThat(lines, matchesFcpMessage(
680                         "TestDDARequest",
681                         "Directory=" + tempFile.getParent(),
682                         "WantReadDirectory=true",
683                         "WantWriteDirectory=false",
684                         "EndMessage"
685                 ));
686         }
687
688         @Test
689         public void clientPutSendsNotificationsForGeneratedKeys()
690         throws InterruptedException, ExecutionException, IOException {
691                 List<String> generatedKeys = new CopyOnWriteArrayList<>();
692                 Future<Optional<Key>> key = fcpClient.clientPut()
693                         .onKeyGenerated(generatedKeys::add)
694                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
695                         .length(6)
696                         .uri("KSK@foo.txt")
697                         .execute();
698                 connectNode();
699                 List<String> lines = fcpServer.collectUntil(is("Hello"));
700                 String identifier = extractIdentifier(lines);
701                 fcpServer.writeLine(
702                         "URIGenerated",
703                         "Identifier=" + identifier,
704                         "URI=KSK@foo.txt",
705                         "EndMessage"
706                 );
707                 fcpServer.writeLine(
708                         "PutSuccessful",
709                         "URI=KSK@foo.txt",
710                         "Identifier=" + identifier,
711                         "EndMessage"
712                 );
713                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
714                 assertThat(generatedKeys, contains("KSK@foo.txt"));
715         }
716
717         @Test
718         public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
719                 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
720                 connectNode();
721                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
722                 assertThat(lines, matchesFcpMessage(
723                         "ListPeers",
724                         "WithVolatile=false",
725                         "WithMetadata=false",
726                         "EndMessage"
727                 ));
728                 String identifier = extractIdentifier(lines);
729                 fcpServer.writeLine(
730                         "Peer",
731                         "Identifier=" + identifier,
732                         "identity=id1",
733                         "EndMessage"
734                 );
735                 fcpServer.writeLine(
736                         "Peer",
737                         "Identifier=" + identifier,
738                         "identity=id2",
739                         "EndMessage"
740                 );
741                 fcpServer.writeLine(
742                         "EndListPeers",
743                         "Identifier=" + identifier,
744                         "EndMessage"
745                 );
746                 assertThat(peers.get(), hasSize(2));
747                 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
748                         containsInAnyOrder("id1", "id2"));
749         }
750
751         @Test
752         public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
753                 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
754                 connectNode();
755                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
756                 assertThat(lines, matchesFcpMessage(
757                         "ListPeers",
758                         "WithVolatile=false",
759                         "WithMetadata=true",
760                         "EndMessage"
761                 ));
762                 String identifier = extractIdentifier(lines);
763                 fcpServer.writeLine(
764                         "Peer",
765                         "Identifier=" + identifier,
766                         "identity=id1",
767                         "metadata.foo=bar1",
768                         "EndMessage"
769                 );
770                 fcpServer.writeLine(
771                         "Peer",
772                         "Identifier=" + identifier,
773                         "identity=id2",
774                         "metadata.foo=bar2",
775                         "EndMessage"
776                 );
777                 fcpServer.writeLine(
778                         "EndListPeers",
779                         "Identifier=" + identifier,
780                         "EndMessage"
781                 );
782                 assertThat(peers.get(), hasSize(2));
783                 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
784                         containsInAnyOrder("bar1", "bar2"));
785         }
786
787         @Test
788         public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
789                 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
790                 connectNode();
791                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
792                 assertThat(lines, matchesFcpMessage(
793                         "ListPeers",
794                         "WithVolatile=true",
795                         "WithMetadata=false",
796                         "EndMessage"
797                 ));
798                 String identifier = extractIdentifier(lines);
799                 fcpServer.writeLine(
800                         "Peer",
801                         "Identifier=" + identifier,
802                         "identity=id1",
803                         "volatile.foo=bar1",
804                         "EndMessage"
805                 );
806                 fcpServer.writeLine(
807                         "Peer",
808                         "Identifier=" + identifier,
809                         "identity=id2",
810                         "volatile.foo=bar2",
811                         "EndMessage"
812                 );
813                 fcpServer.writeLine(
814                         "EndListPeers",
815                         "Identifier=" + identifier,
816                         "EndMessage"
817                 );
818                 assertThat(peers.get(), hasSize(2));
819                 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
820                         containsInAnyOrder("bar1", "bar2"));
821         }
822
823         @Test
824         public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
825                 Future<NodeData> nodeData = fcpClient.getNode().execute();
826                 connectNode();
827                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
828                 String identifier = extractIdentifier(lines);
829                 assertThat(lines, matchesFcpMessage(
830                         "GetNode",
831                         "Identifier=" + identifier,
832                         "GiveOpennetRef=false",
833                         "WithPrivate=false",
834                         "WithVolatile=false",
835                         "EndMessage"
836                 ));
837                 fcpServer.writeLine(
838                         "NodeData",
839                         "Identifier=" + identifier,
840                         "ark.pubURI=SSK@3YEf.../ark",
841                         "ark.number=78",
842                         "auth.negTypes=2",
843                         "version=Fred,0.7,1.0,1466",
844                         "lastGoodVersion=Fred,0.7,1.0,1466",
845                         "EndMessage"
846                 );
847                 assertThat(nodeData.get(), notNullValue());
848         }
849
850         @Test
851         public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
852         throws InterruptedException, ExecutionException, IOException {
853                 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
854                 connectNode();
855                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
856                 String identifier = extractIdentifier(lines);
857                 assertThat(lines, matchesFcpMessage(
858                         "GetNode",
859                         "Identifier=" + identifier,
860                         "GiveOpennetRef=true",
861                         "WithPrivate=false",
862                         "WithVolatile=false",
863                         "EndMessage"
864                 ));
865                 fcpServer.writeLine(
866                         "NodeData",
867                         "Identifier=" + identifier,
868                         "opennet=true",
869                         "ark.pubURI=SSK@3YEf.../ark",
870                         "ark.number=78",
871                         "auth.negTypes=2",
872                         "version=Fred,0.7,1.0,1466",
873                         "lastGoodVersion=Fred,0.7,1.0,1466",
874                         "EndMessage"
875                 );
876                 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
877         }
878
879         @Test
880         public void defaultFcpClientCanGetNodeInformationWithPrivateData()
881         throws InterruptedException, ExecutionException, IOException {
882                 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
883                 connectNode();
884                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
885                 String identifier = extractIdentifier(lines);
886                 assertThat(lines, matchesFcpMessage(
887                         "GetNode",
888                         "Identifier=" + identifier,
889                         "GiveOpennetRef=false",
890                         "WithPrivate=true",
891                         "WithVolatile=false",
892                         "EndMessage"
893                 ));
894                 fcpServer.writeLine(
895                         "NodeData",
896                         "Identifier=" + identifier,
897                         "opennet=false",
898                         "ark.pubURI=SSK@3YEf.../ark",
899                         "ark.number=78",
900                         "auth.negTypes=2",
901                         "version=Fred,0.7,1.0,1466",
902                         "lastGoodVersion=Fred,0.7,1.0,1466",
903                         "ark.privURI=SSK@XdHMiRl",
904                         "EndMessage"
905                 );
906                 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
907         }
908
909         @Test
910         public void defaultFcpClientCanGetNodeInformationWithVolatileData()
911         throws InterruptedException, ExecutionException, IOException {
912                 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
913                 connectNode();
914                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
915                 String identifier = extractIdentifier(lines);
916                 assertThat(lines, matchesFcpMessage(
917                         "GetNode",
918                         "Identifier=" + identifier,
919                         "GiveOpennetRef=false",
920                         "WithPrivate=false",
921                         "WithVolatile=true",
922                         "EndMessage"
923                 ));
924                 fcpServer.writeLine(
925                         "NodeData",
926                         "Identifier=" + identifier,
927                         "opennet=false",
928                         "ark.pubURI=SSK@3YEf.../ark",
929                         "ark.number=78",
930                         "auth.negTypes=2",
931                         "version=Fred,0.7,1.0,1466",
932                         "lastGoodVersion=Fred,0.7,1.0,1466",
933                         "volatile.freeJavaMemory=205706528",
934                         "EndMessage"
935                 );
936                 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
937         }
938
939         @Test
940         public void defaultFcpClientCanListSinglePeerByIdentity()
941         throws InterruptedException, ExecutionException, IOException {
942                 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
943                 connectNode();
944                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
945                 String identifier = extractIdentifier(lines);
946                 assertThat(lines, matchesFcpMessage(
947                         "ListPeer",
948                         "Identifier=" + identifier,
949                         "NodeIdentifier=id1",
950                         "EndMessage"
951                 ));
952                 fcpServer.writeLine(
953                         "Peer",
954                         "Identifier=" + identifier,
955                         "identity=id1",
956                         "opennet=false",
957                         "ark.pubURI=SSK@3YEf.../ark",
958                         "ark.number=78",
959                         "auth.negTypes=2",
960                         "version=Fred,0.7,1.0,1466",
961                         "lastGoodVersion=Fred,0.7,1.0,1466",
962                         "EndMessage"
963                 );
964                 assertThat(peer.get().get().getIdentity(), is("id1"));
965         }
966
967         @Test
968         public void defaultFcpClientCanListSinglePeerByHostAndPort()
969         throws InterruptedException, ExecutionException, IOException {
970                 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
971                 connectNode();
972                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
973                 String identifier = extractIdentifier(lines);
974                 assertThat(lines, matchesFcpMessage(
975                         "ListPeer",
976                         "Identifier=" + identifier,
977                         "NodeIdentifier=host.free.net:12345",
978                         "EndMessage"
979                 ));
980                 fcpServer.writeLine(
981                         "Peer",
982                         "Identifier=" + identifier,
983                         "identity=id1",
984                         "opennet=false",
985                         "ark.pubURI=SSK@3YEf.../ark",
986                         "ark.number=78",
987                         "auth.negTypes=2",
988                         "version=Fred,0.7,1.0,1466",
989                         "lastGoodVersion=Fred,0.7,1.0,1466",
990                         "EndMessage"
991                 );
992                 assertThat(peer.get().get().getIdentity(), is("id1"));
993         }
994
995         @Test
996         public void defaultFcpClientCanListSinglePeerByName()
997         throws InterruptedException, ExecutionException, IOException {
998                 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
999                 connectNode();
1000                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1001                 String identifier = extractIdentifier(lines);
1002                 assertThat(lines, matchesFcpMessage(
1003                         "ListPeer",
1004                         "Identifier=" + identifier,
1005                         "NodeIdentifier=FriendNode",
1006                         "EndMessage"
1007                 ));
1008                 fcpServer.writeLine(
1009                         "Peer",
1010                         "Identifier=" + identifier,
1011                         "identity=id1",
1012                         "opennet=false",
1013                         "ark.pubURI=SSK@3YEf.../ark",
1014                         "ark.number=78",
1015                         "auth.negTypes=2",
1016                         "version=Fred,0.7,1.0,1466",
1017                         "lastGoodVersion=Fred,0.7,1.0,1466",
1018                         "EndMessage"
1019                 );
1020                 assertThat(peer.get().get().getIdentity(), is("id1"));
1021         }
1022
1023         @Test
1024         public void defaultFcpClientRecognizesUnknownNodeIdentifiers()
1025         throws InterruptedException, ExecutionException, IOException {
1026                 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
1027                 connectNode();
1028                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1029                 String identifier = extractIdentifier(lines);
1030                 assertThat(lines, matchesFcpMessage(
1031                         "ListPeer",
1032                         "Identifier=" + identifier,
1033                         "NodeIdentifier=id2",
1034                         "EndMessage"
1035                 ));
1036                 fcpServer.writeLine(
1037                         "UnknownNodeIdentifier",
1038                         "Identifier=" + identifier,
1039                         "NodeIdentifier=id2",
1040                         "EndMessage"
1041                 );
1042                 assertThat(peer.get().isPresent(), is(false));
1043         }
1044
1045         @Test
1046         public void defaultFcpClientCanAddPeerFromFile() throws InterruptedException, ExecutionException, IOException {
1047                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1048                 connectNode();
1049                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1050                 String identifier = extractIdentifier(lines);
1051                 assertThat(lines, matchesFcpMessage(
1052                         "AddPeer",
1053                         "Identifier=" + identifier,
1054                         "File=/tmp/ref.txt",
1055                         "EndMessage"
1056                 ));
1057                 fcpServer.writeLine(
1058                         "Peer",
1059                         "Identifier=" + identifier,
1060                         "identity=id1",
1061                         "opennet=false",
1062                         "ark.pubURI=SSK@3YEf.../ark",
1063                         "ark.number=78",
1064                         "auth.negTypes=2",
1065                         "version=Fred,0.7,1.0,1466",
1066                         "lastGoodVersion=Fred,0.7,1.0,1466",
1067                         "EndMessage"
1068                 );
1069                 assertThat(peer.get().get().getIdentity(), is("id1"));
1070         }
1071
1072         @Test
1073         public void defaultFcpClientCanAddPeerFromURL() throws InterruptedException, ExecutionException, IOException {
1074                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1075                 connectNode();
1076                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1077                 String identifier = extractIdentifier(lines);
1078                 assertThat(lines, matchesFcpMessage(
1079                         "AddPeer",
1080                         "Identifier=" + identifier,
1081                         "URL=http://node.ref/",
1082                         "EndMessage"
1083                 ));
1084                 fcpServer.writeLine(
1085                         "Peer",
1086                         "Identifier=" + identifier,
1087                         "identity=id1",
1088                         "opennet=false",
1089                         "ark.pubURI=SSK@3YEf.../ark",
1090                         "ark.number=78",
1091                         "auth.negTypes=2",
1092                         "version=Fred,0.7,1.0,1466",
1093                         "lastGoodVersion=Fred,0.7,1.0,1466",
1094                         "EndMessage"
1095                 );
1096                 assertThat(peer.get().get().getIdentity(), is("id1"));
1097         }
1098
1099         @Test
1100         public void defaultFcpClientCanAddPeerFromNodeRef() throws InterruptedException, ExecutionException, IOException {
1101                 NodeRef nodeRef = new NodeRef();
1102                 nodeRef.setIdentity("id1");
1103                 nodeRef.setName("name");
1104                 nodeRef.setARK(new ARK("public", "1"));
1105                 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1106                 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1107                 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1108                 nodeRef.setDSAPublicKey("dsa-public");
1109                 nodeRef.setSignature("sig");
1110                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1111                 connectNode();
1112                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1113                 String identifier = extractIdentifier(lines);
1114                 assertThat(lines, matchesFcpMessage(
1115                         "AddPeer",
1116                         "Identifier=" + identifier,
1117                         "identity=id1",
1118                         "myName=name",
1119                         "ark.pubURI=public",
1120                         "ark.number=1",
1121                         "dsaGroup.g=base",
1122                         "dsaGroup.p=prime",
1123                         "dsaGroup.q=subprime",
1124                         "dsaPubKey.y=dsa-public",
1125                         "physical.udp=1.2.3.4:5678",
1126                         "auth.negTypes=3;5",
1127                         "sig=sig",
1128                         "EndMessage"
1129                 ));
1130                 fcpServer.writeLine(
1131                         "Peer",
1132                         "Identifier=" + identifier,
1133                         "identity=id1",
1134                         "opennet=false",
1135                         "ark.pubURI=SSK@3YEf.../ark",
1136                         "ark.number=78",
1137                         "auth.negTypes=2",
1138                         "version=Fred,0.7,1.0,1466",
1139                         "lastGoodVersion=Fred,0.7,1.0,1466",
1140                         "EndMessage"
1141                 );
1142                 assertThat(peer.get().get().getIdentity(), is("id1"));
1143         }
1144
1145         @Test
1146         public void listPeerNotesCanGetPeerNotesByNodeName() throws InterruptedException, ExecutionException, IOException {
1147                 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1148                 connectNode();
1149                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1150                 String identifier = extractIdentifier(lines);
1151                 assertThat(lines, matchesFcpMessage(
1152                         "ListPeerNotes",
1153                         "NodeIdentifier=Friend1",
1154                         "EndMessage"
1155                 ));
1156                 fcpServer.writeLine(
1157                         "PeerNote",
1158                         "Identifier=" + identifier,
1159                         "NodeIdentifier=Friend1",
1160                         "NoteText=RXhhbXBsZSBUZXh0Lg==",
1161                         "PeerNoteType=1",
1162                         "EndMessage"
1163                 );
1164                 fcpServer.writeLine(
1165                         "EndListPeerNotes",
1166                         "Identifier=" + identifier,
1167                         "EndMessage"
1168                 );
1169                 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1170                 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1171         }
1172
1173         @Test
1174         public void listPeerNotesReturnsEmptyOptionalWhenNodeIdenfierUnknown()
1175         throws InterruptedException, ExecutionException,
1176         IOException {
1177                 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1178                 connectNode();
1179                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1180                 String identifier = extractIdentifier(lines);
1181                 assertThat(lines, matchesFcpMessage(
1182                         "ListPeerNotes",
1183                         "NodeIdentifier=Friend1",
1184                         "EndMessage"
1185                 ));
1186                 fcpServer.writeLine(
1187                         "UnknownNodeIdentifier",
1188                         "Identifier=" + identifier,
1189                         "NodeIdentifier=Friend1",
1190                         "EndMessage"
1191                 );
1192                 assertThat(peerNote.get().isPresent(), is(false));
1193         }
1194
1195         @Test
1196         public void listPeerNotesCanGetPeerNotesByNodeIdentifier()
1197         throws InterruptedException, ExecutionException, IOException {
1198                 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
1199                 connectNode();
1200                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1201                 String identifier = extractIdentifier(lines);
1202                 assertThat(lines, matchesFcpMessage(
1203                         "ListPeerNotes",
1204                         "NodeIdentifier=id1",
1205                         "EndMessage"
1206                 ));
1207                 fcpServer.writeLine(
1208                         "PeerNote",
1209                         "Identifier=" + identifier,
1210                         "NodeIdentifier=id1",
1211                         "NoteText=RXhhbXBsZSBUZXh0Lg==",
1212                         "PeerNoteType=1",
1213                         "EndMessage"
1214                 );
1215                 fcpServer.writeLine(
1216                         "EndListPeerNotes",
1217                         "Identifier=" + identifier,
1218                         "EndMessage"
1219                 );
1220                 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1221                 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1222         }
1223
1224         @Test
1225         public void listPeerNotesCanGetPeerNotesByHostNameAndPortNumber()
1226         throws InterruptedException, ExecutionException, IOException {
1227                 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
1228                 connectNode();
1229                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1230                 String identifier = extractIdentifier(lines);
1231                 assertThat(lines, matchesFcpMessage(
1232                         "ListPeerNotes",
1233                         "NodeIdentifier=1.2.3.4:5678",
1234                         "EndMessage"
1235                 ));
1236                 fcpServer.writeLine(
1237                         "PeerNote",
1238                         "Identifier=" + identifier,
1239                         "NodeIdentifier=id1",
1240                         "NoteText=RXhhbXBsZSBUZXh0Lg==",
1241                         "PeerNoteType=1",
1242                         "EndMessage"
1243                 );
1244                 fcpServer.writeLine(
1245                         "EndListPeerNotes",
1246                         "Identifier=" + identifier,
1247                         "EndMessage"
1248                 );
1249                 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1250                 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1251         }
1252
1253         @Test
1254         public void defaultFcpClientCanEnablePeerByName() throws InterruptedException, ExecutionException, IOException {
1255                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("Friend1").execute();
1256                 connectNode();
1257                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1258                 String identifier = extractIdentifier(lines);
1259                 assertThat(lines, matchesFcpMessage(
1260                         "ModifyPeer",
1261                         "Identifier=" + identifier,
1262                         "NodeIdentifier=Friend1",
1263                         "IsDisabled=false",
1264                         "EndMessage"
1265                 ));
1266                 fcpServer.writeLine(
1267                         "Peer",
1268                         "Identifier=" + identifier,
1269                         "NodeIdentifier=Friend1",
1270                         "identity=id1",
1271                         "EndMessage"
1272                 );
1273                 assertThat(peer.get().get().getIdentity(), is("id1"));
1274         }
1275
1276         @Test
1277         public void defaultFcpClientCanDisablePeerByName() throws InterruptedException, ExecutionException, IOException {
1278                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("Friend1").execute();
1279                 connectNode();
1280                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1281                 String identifier = extractIdentifier(lines);
1282                 assertThat(lines, matchesFcpMessage(
1283                         "ModifyPeer",
1284                         "Identifier=" + identifier,
1285                         "NodeIdentifier=Friend1",
1286                         "IsDisabled=true",
1287                         "EndMessage"
1288                 ));
1289                 fcpServer.writeLine(
1290                         "Peer",
1291                         "Identifier=" + identifier,
1292                         "NodeIdentifier=Friend1",
1293                         "identity=id1",
1294                         "EndMessage"
1295                 );
1296                 assertThat(peer.get().get().getIdentity(), is("id1"));
1297         }
1298
1299         @Test
1300         public void defaultFcpClientCanEnablePeerByIdentity() throws InterruptedException, ExecutionException, IOException {
1301                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1302                 connectNode();
1303                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1304                 String identifier = extractIdentifier(lines);
1305                 assertThat(lines, matchesFcpMessage(
1306                         "ModifyPeer",
1307                         "Identifier=" + identifier,
1308                         "NodeIdentifier=id1",
1309                         "IsDisabled=false",
1310                         "EndMessage"
1311                 ));
1312                 fcpServer.writeLine(
1313                         "Peer",
1314                         "Identifier=" + identifier,
1315                         "NodeIdentifier=Friend1",
1316                         "identity=id1",
1317                         "EndMessage"
1318                 );
1319                 assertThat(peer.get().get().getIdentity(), is("id1"));
1320         }
1321
1322         @Test
1323         public void defaultFcpClientCanEnablePeerByHostAndPort()
1324         throws InterruptedException, ExecutionException, IOException {
1325                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
1326                 connectNode();
1327                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1328                 String identifier = extractIdentifier(lines);
1329                 assertThat(lines, matchesFcpMessage(
1330                         "ModifyPeer",
1331                         "Identifier=" + identifier,
1332                         "NodeIdentifier=1.2.3.4:5678",
1333                         "IsDisabled=false",
1334                         "EndMessage"
1335                 ));
1336                 fcpServer.writeLine(
1337                         "Peer",
1338                         "Identifier=" + identifier,
1339                         "NodeIdentifier=Friend1",
1340                         "identity=id1",
1341                         "EndMessage"
1342                 );
1343                 assertThat(peer.get().get().getIdentity(), is("id1"));
1344         }
1345
1346         @Test
1347         public void defaultFcpClientCanNotModifyPeerOfUnknownNode()
1348         throws InterruptedException, ExecutionException, IOException {
1349                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1350                 connectNode();
1351                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1352                 String identifier = extractIdentifier(lines);
1353                 assertThat(lines, matchesFcpMessage(
1354                         "ModifyPeer",
1355                         "Identifier=" + identifier,
1356                         "NodeIdentifier=id1",
1357                         "IsDisabled=false",
1358                         "EndMessage"
1359                 ));
1360                 fcpServer.writeLine(
1361                         "UnknownNodeIdentifier",
1362                         "Identifier=" + identifier,
1363                         "NodeIdentifier=id1",
1364                         "EndMessage"
1365                 );
1366                 assertThat(peer.get().isPresent(), is(false));
1367         }
1368
1369         @Test
1370         public void defaultFcpClientCanAllowLocalAddressesOfPeer()
1371         throws InterruptedException, ExecutionException, IOException {
1372                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
1373                 connectNode();
1374                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1375                 String identifier = extractIdentifier(lines);
1376                 assertThat(lines, matchesFcpMessage(
1377                         "ModifyPeer",
1378                         "Identifier=" + identifier,
1379                         "NodeIdentifier=id1",
1380                         "AllowLocalAddresses=true",
1381                         "EndMessage"
1382                 ));
1383                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1384                 fcpServer.writeLine(
1385                         "Peer",
1386                         "Identifier=" + identifier,
1387                         "NodeIdentifier=Friend1",
1388                         "identity=id1",
1389                         "EndMessage"
1390                 );
1391                 assertThat(peer.get().get().getIdentity(), is("id1"));
1392         }
1393
1394         @Test
1395         public void defaultFcpClientCanDisallowLocalAddressesOfPeer()
1396         throws InterruptedException, ExecutionException, IOException {
1397                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
1398                 connectNode();
1399                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1400                 String identifier = extractIdentifier(lines);
1401                 assertThat(lines, matchesFcpMessage(
1402                         "ModifyPeer",
1403                         "Identifier=" + identifier,
1404                         "NodeIdentifier=id1",
1405                         "AllowLocalAddresses=false",
1406                         "EndMessage"
1407                 ));
1408                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1409                 fcpServer.writeLine(
1410                         "Peer",
1411                         "Identifier=" + identifier,
1412                         "NodeIdentifier=Friend1",
1413                         "identity=id1",
1414                         "EndMessage"
1415                 );
1416                 assertThat(peer.get().get().getIdentity(), is("id1"));
1417         }
1418
1419         @Test
1420         public void defaultFcpClientCanSetBurstOnlyForPeer()
1421         throws InterruptedException, ExecutionException, IOException {
1422                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
1423                 connectNode();
1424                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1425                 String identifier = extractIdentifier(lines);
1426                 assertThat(lines, matchesFcpMessage(
1427                         "ModifyPeer",
1428                         "Identifier=" + identifier,
1429                         "NodeIdentifier=id1",
1430                         "IsBurstOnly=true",
1431                         "EndMessage"
1432                 ));
1433                 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1434                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1435                 fcpServer.writeLine(
1436                         "Peer",
1437                         "Identifier=" + identifier,
1438                         "NodeIdentifier=Friend1",
1439                         "identity=id1",
1440                         "EndMessage"
1441                 );
1442                 assertThat(peer.get().get().getIdentity(), is("id1"));
1443         }
1444
1445         @Test
1446         public void defaultFcpClientCanClearBurstOnlyForPeer()
1447         throws InterruptedException, ExecutionException, IOException {
1448                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
1449                 connectNode();
1450                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1451                 String identifier = extractIdentifier(lines);
1452                 assertThat(lines, matchesFcpMessage(
1453                         "ModifyPeer",
1454                         "Identifier=" + identifier,
1455                         "NodeIdentifier=id1",
1456                         "IsBurstOnly=false",
1457                         "EndMessage"
1458                 ));
1459                 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1460                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1461                 fcpServer.writeLine(
1462                         "Peer",
1463                         "Identifier=" + identifier,
1464                         "NodeIdentifier=Friend1",
1465                         "identity=id1",
1466                         "EndMessage"
1467                 );
1468                 assertThat(peer.get().get().getIdentity(), is("id1"));
1469         }
1470
1471         @Test
1472         public void defaultFcpClientCanSetListenOnlyForPeer()
1473         throws InterruptedException, ExecutionException, IOException {
1474                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
1475                 connectNode();
1476                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1477                 String identifier = extractIdentifier(lines);
1478                 assertThat(lines, matchesFcpMessage(
1479                         "ModifyPeer",
1480                         "Identifier=" + identifier,
1481                         "NodeIdentifier=id1",
1482                         "IsListenOnly=true",
1483                         "EndMessage"
1484                 ));
1485                 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1486                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1487                 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1488                 fcpServer.writeLine(
1489                         "Peer",
1490                         "Identifier=" + identifier,
1491                         "NodeIdentifier=Friend1",
1492                         "identity=id1",
1493                         "EndMessage"
1494                 );
1495                 assertThat(peer.get().get().getIdentity(), is("id1"));
1496         }
1497
1498         @Test
1499         public void defaultFcpClientCanClearListenOnlyForPeer()
1500         throws InterruptedException, ExecutionException, IOException {
1501                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
1502                 connectNode();
1503                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1504                 String identifier = extractIdentifier(lines);
1505                 assertThat(lines, matchesFcpMessage(
1506                         "ModifyPeer",
1507                         "Identifier=" + identifier,
1508                         "NodeIdentifier=id1",
1509                         "IsListenOnly=false",
1510                         "EndMessage"
1511                 ));
1512                 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1513                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1514                 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1515                 fcpServer.writeLine(
1516                         "Peer",
1517                         "Identifier=" + identifier,
1518                         "NodeIdentifier=Friend1",
1519                         "identity=id1",
1520                         "EndMessage"
1521                 );
1522                 assertThat(peer.get().get().getIdentity(), is("id1"));
1523         }
1524
1525         @Test
1526         public void defaultFcpClientCanIgnoreSourceForPeer()
1527         throws InterruptedException, ExecutionException, IOException {
1528                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
1529                 connectNode();
1530                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1531                 String identifier = extractIdentifier(lines);
1532                 assertThat(lines, matchesFcpMessage(
1533                         "ModifyPeer",
1534                         "Identifier=" + identifier,
1535                         "NodeIdentifier=id1",
1536                         "IgnoreSourcePort=true",
1537                         "EndMessage"
1538                 ));
1539                 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1540                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1541                 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1542                 assertThat(lines, not(contains(startsWith("IsListenOnly="))));
1543                 fcpServer.writeLine(
1544                         "Peer",
1545                         "Identifier=" + identifier,
1546                         "NodeIdentifier=Friend1",
1547                         "identity=id1",
1548                         "EndMessage"
1549                 );
1550                 assertThat(peer.get().get().getIdentity(), is("id1"));
1551         }
1552
1553         @Test
1554         public void defaultFcpClientCanUseSourceForPeer()
1555         throws InterruptedException, ExecutionException, IOException {
1556                 Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
1557                 connectNode();
1558                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1559                 String identifier = extractIdentifier(lines);
1560                 assertThat(lines, matchesFcpMessage(
1561                         "ModifyPeer",
1562                         "Identifier=" + identifier,
1563                         "NodeIdentifier=id1",
1564                         "IgnoreSourcePort=false",
1565                         "EndMessage"
1566                 ));
1567                 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1568                 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1569                 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1570                 assertThat(lines, not(contains(startsWith("IsListenOnly="))));
1571                 fcpServer.writeLine(
1572                         "Peer",
1573                         "Identifier=" + identifier,
1574                         "NodeIdentifier=Friend1",
1575                         "identity=id1",
1576                         "EndMessage"
1577                 );
1578                 assertThat(peer.get().get().getIdentity(), is("id1"));
1579         }
1580
1581         @Test
1582         public void defaultFcpClientCanRemovePeerByName() throws InterruptedException, ExecutionException, IOException {
1583                 Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
1584                 connectNode();
1585                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1586                 String identifier = extractIdentifier(lines);
1587                 assertThat(lines, matchesFcpMessage(
1588                         "RemovePeer",
1589                         "Identifier=" + identifier,
1590                         "NodeIdentifier=Friend1",
1591                         "EndMessage"
1592                 ));
1593                 fcpServer.writeLine(
1594                         "PeerRemoved",
1595                         "Identifier=" + identifier,
1596                         "NodeIdentifier=Friend1",
1597                         "EndMessage"
1598                 );
1599                 assertThat(peer.get(), is(true));
1600         }
1601
1602         @Test
1603         public void defaultFcpClientCanNotRemovePeerByInvalidName()
1604         throws InterruptedException, ExecutionException, IOException {
1605                 Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
1606                 connectNode();
1607                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1608                 String identifier = extractIdentifier(lines);
1609                 assertThat(lines, matchesFcpMessage(
1610                         "RemovePeer",
1611                         "Identifier=" + identifier,
1612                         "NodeIdentifier=NotFriend1",
1613                         "EndMessage"
1614                 ));
1615                 fcpServer.writeLine(
1616                         "UnknownNodeIdentifier",
1617                         "Identifier=" + identifier,
1618                         "EndMessage"
1619                 );
1620                 assertThat(peer.get(), is(false));
1621         }
1622
1623         @Test
1624         public void defaultFcpClientCanRemovePeerByIdentity() throws InterruptedException, ExecutionException, IOException {
1625                 Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
1626                 connectNode();
1627                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1628                 String identifier = extractIdentifier(lines);
1629                 assertThat(lines, matchesFcpMessage(
1630                         "RemovePeer",
1631                         "Identifier=" + identifier,
1632                         "NodeIdentifier=id1",
1633                         "EndMessage"
1634                 ));
1635                 fcpServer.writeLine(
1636                         "PeerRemoved",
1637                         "Identifier=" + identifier,
1638                         "NodeIdentifier=Friend1",
1639                         "EndMessage"
1640                 );
1641                 assertThat(peer.get(), is(true));
1642         }
1643
1644         @Test
1645         public void defaultFcpClientCanRemovePeerByHostAndPort()
1646         throws InterruptedException, ExecutionException, IOException {
1647                 Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
1648                 connectNode();
1649                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1650                 String identifier = extractIdentifier(lines);
1651                 assertThat(lines, matchesFcpMessage(
1652                         "RemovePeer",
1653                         "Identifier=" + identifier,
1654                         "NodeIdentifier=1.2.3.4:5678",
1655                         "EndMessage"
1656                 ));
1657                 fcpServer.writeLine(
1658                         "PeerRemoved",
1659                         "Identifier=" + identifier,
1660                         "NodeIdentifier=Friend1",
1661                         "EndMessage"
1662                 );
1663                 assertThat(peer.get(), is(true));
1664         }
1665
1666         @Test
1667         public void defaultFcpClientCanModifyPeerNoteByName()
1668         throws InterruptedException, ExecutionException, IOException {
1669                 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1670                 connectNode();
1671                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1672                 String identifier = extractIdentifier(lines);
1673                 assertThat(lines, matchesFcpMessage(
1674                         "ModifyPeerNote",
1675                         "Identifier=" + identifier,
1676                         "NodeIdentifier=Friend1",
1677                         "PeerNoteType=1",
1678                         "NoteText=Zm9v",
1679                         "EndMessage"
1680                 ));
1681                 fcpServer.writeLine(
1682                         "PeerNote",
1683                         "Identifier=" + identifier,
1684                         "NodeIdentifier=Friend1",
1685                         "NoteText=Zm9v",
1686                         "PeerNoteType=1",
1687                         "EndMessage"
1688                 );
1689                 assertThat(noteUpdated.get(), is(true));
1690         }
1691
1692         @Test
1693         public void defaultFcpClientKnowsPeerNoteWasNotModifiedOnUnknownNodeIdentifier()
1694         throws InterruptedException, ExecutionException, IOException {
1695                 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1696                 connectNode();
1697                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1698                 String identifier = extractIdentifier(lines);
1699                 assertThat(lines, matchesFcpMessage(
1700                         "ModifyPeerNote",
1701                         "Identifier=" + identifier,
1702                         "NodeIdentifier=Friend1",
1703                         "PeerNoteType=1",
1704                         "NoteText=Zm9v",
1705                         "EndMessage"
1706                 ));
1707                 fcpServer.writeLine(
1708                         "UnknownNodeIdentifier",
1709                         "Identifier=" + identifier,
1710                         "NodeIdentifier=Friend1",
1711                         "EndMessage"
1712                 );
1713                 assertThat(noteUpdated.get(), is(false));
1714         }
1715
1716         @Test
1717         public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
1718         throws InterruptedException, ExecutionException, IOException {
1719                 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
1720                 assertThat(noteUpdated.get(), is(false));
1721         }
1722
1723         @Test
1724         public void defaultFcpClientCanModifyPeerNoteByIdentifier()
1725         throws InterruptedException, ExecutionException, IOException {
1726                 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
1727                 connectNode();
1728                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1729                 String identifier = extractIdentifier(lines);
1730                 assertThat(lines, matchesFcpMessage(
1731                         "ModifyPeerNote",
1732                         "Identifier=" + identifier,
1733                         "NodeIdentifier=id1",
1734                         "PeerNoteType=1",
1735                         "NoteText=Zm9v",
1736                         "EndMessage"
1737                 ));
1738                 fcpServer.writeLine(
1739                         "PeerNote",
1740                         "Identifier=" + identifier,
1741                         "NodeIdentifier=id1",
1742                         "NoteText=Zm9v",
1743                         "PeerNoteType=1",
1744                         "EndMessage"
1745                 );
1746                 assertThat(noteUpdated.get(), is(true));
1747         }
1748
1749         @Test
1750         public void defaultFcpClientCanModifyPeerNoteByHostAndPort()
1751         throws InterruptedException, ExecutionException, IOException {
1752                 Future<Boolean> noteUpdated =
1753                         fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
1754                 connectNode();
1755                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1756                 String identifier = extractIdentifier(lines);
1757                 assertThat(lines, matchesFcpMessage(
1758                         "ModifyPeerNote",
1759                         "Identifier=" + identifier,
1760                         "NodeIdentifier=1.2.3.4:5678",
1761                         "PeerNoteType=1",
1762                         "NoteText=Zm9v",
1763                         "EndMessage"
1764                 ));
1765                 fcpServer.writeLine(
1766                         "PeerNote",
1767                         "Identifier=" + identifier,
1768                         "NodeIdentifier=1.2.3.4:5678",
1769                         "NoteText=Zm9v",
1770                         "PeerNoteType=1",
1771                         "EndMessage"
1772                 );
1773                 assertThat(noteUpdated.get(), is(true));
1774         }
1775
1776         @Test
1777         public void defaultFcpClientCanGetConfigWithoutDetails()
1778         throws InterruptedException, ExecutionException, IOException {
1779                 Future<ConfigData> configData = fcpClient.getConfig().execute();
1780                 connectNode();
1781                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1782                 String identifier = extractIdentifier(lines);
1783                 assertThat(lines, matchesFcpMessage(
1784                         "GetConfig",
1785                         "Identifier=" + identifier,
1786                         "EndMessage"
1787                 ));
1788                 fcpServer.writeLine(
1789                         "ConfigData",
1790                         "Identifier=" + identifier,
1791                         "EndMessage"
1792                 );
1793                 assertThat(configData.get(), notNullValue());
1794         }
1795
1796         @Test
1797         public void defaultFcpClientCanGetConfigWithCurrent()
1798         throws InterruptedException, ExecutionException, IOException {
1799                 Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
1800                 connectNode();
1801                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1802                 String identifier = extractIdentifier(lines);
1803                 assertThat(lines, matchesFcpMessage(
1804                         "GetConfig",
1805                         "Identifier=" + identifier,
1806                         "WithCurrent=true",
1807                         "EndMessage"
1808                 ));
1809                 fcpServer.writeLine(
1810                         "ConfigData",
1811                         "Identifier=" + identifier,
1812                         "current.foo=bar",
1813                         "EndMessage"
1814                 );
1815                 assertThat(configData.get().getCurrent("foo"), is("bar"));
1816         }
1817
1818         @Test
1819         public void defaultFcpClientCanGetConfigWithDefaults()
1820         throws InterruptedException, ExecutionException, IOException {
1821                 Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
1822                 connectNode();
1823                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1824                 String identifier = extractIdentifier(lines);
1825                 assertThat(lines, matchesFcpMessage(
1826                         "GetConfig",
1827                         "Identifier=" + identifier,
1828                         "WithDefaults=true",
1829                         "EndMessage"
1830                 ));
1831                 fcpServer.writeLine(
1832                         "ConfigData",
1833                         "Identifier=" + identifier,
1834                         "default.foo=bar",
1835                         "EndMessage"
1836                 );
1837                 assertThat(configData.get().getDefault("foo"), is("bar"));
1838         }
1839
1840         @Test
1841         public void defaultFcpClientCanGetConfigWithSortOrder()
1842         throws InterruptedException, ExecutionException, IOException {
1843                 Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
1844                 connectNode();
1845                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1846                 String identifier = extractIdentifier(lines);
1847                 assertThat(lines, matchesFcpMessage(
1848                         "GetConfig",
1849                         "Identifier=" + identifier,
1850                         "WithSortOrder=true",
1851                         "EndMessage"
1852                 ));
1853                 fcpServer.writeLine(
1854                         "ConfigData",
1855                         "Identifier=" + identifier,
1856                         "sortOrder.foo=17",
1857                         "EndMessage"
1858                 );
1859                 assertThat(configData.get().getSortOrder("foo"), is(17));
1860         }
1861
1862         @Test
1863         public void defaultFcpClientCanGetConfigWithExpertFlag()
1864         throws InterruptedException, ExecutionException, IOException {
1865                 Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
1866                 connectNode();
1867                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1868                 String identifier = extractIdentifier(lines);
1869                 assertThat(lines, matchesFcpMessage(
1870                         "GetConfig",
1871                         "Identifier=" + identifier,
1872                         "WithExpertFlag=true",
1873                         "EndMessage"
1874                 ));
1875                 fcpServer.writeLine(
1876                         "ConfigData",
1877                         "Identifier=" + identifier,
1878                         "expertFlag.foo=true",
1879                         "EndMessage"
1880                 );
1881                 assertThat(configData.get().getExpertFlag("foo"), is(true));
1882         }
1883
1884         @Test
1885         public void defaultFcpClientCanGetConfigWithForceWriteFlag()
1886         throws InterruptedException, ExecutionException, IOException {
1887                 Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
1888                 connectNode();
1889                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1890                 String identifier = extractIdentifier(lines);
1891                 assertThat(lines, matchesFcpMessage(
1892                         "GetConfig",
1893                         "Identifier=" + identifier,
1894                         "WithForceWriteFlag=true",
1895                         "EndMessage"
1896                 ));
1897                 fcpServer.writeLine(
1898                         "ConfigData",
1899                         "Identifier=" + identifier,
1900                         "forceWriteFlag.foo=true",
1901                         "EndMessage"
1902                 );
1903                 assertThat(configData.get().getForceWriteFlag("foo"), is(true));
1904         }
1905
1906         @Test
1907         public void defaultFcpClientCanGetConfigWithShortDescription()
1908         throws InterruptedException, ExecutionException, IOException {
1909                 Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
1910                 connectNode();
1911                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1912                 String identifier = extractIdentifier(lines);
1913                 assertThat(lines, matchesFcpMessage(
1914                         "GetConfig",
1915                         "Identifier=" + identifier,
1916                         "WithShortDescription=true",
1917                         "EndMessage"
1918                 ));
1919                 fcpServer.writeLine(
1920                         "ConfigData",
1921                         "Identifier=" + identifier,
1922                         "shortDescription.foo=bar",
1923                         "EndMessage"
1924                 );
1925                 assertThat(configData.get().getShortDescription("foo"), is("bar"));
1926         }
1927
1928         @Test
1929         public void defaultFcpClientCanGetConfigWithLongDescription()
1930         throws InterruptedException, ExecutionException, IOException {
1931                 Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
1932                 connectNode();
1933                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1934                 String identifier = extractIdentifier(lines);
1935                 assertThat(lines, matchesFcpMessage(
1936                         "GetConfig",
1937                         "Identifier=" + identifier,
1938                         "WithLongDescription=true",
1939                         "EndMessage"
1940                 ));
1941                 fcpServer.writeLine(
1942                         "ConfigData",
1943                         "Identifier=" + identifier,
1944                         "longDescription.foo=bar",
1945                         "EndMessage"
1946                 );
1947                 assertThat(configData.get().getLongDescription("foo"), is("bar"));
1948         }
1949
1950         @Test
1951         public void defaultFcpClientCanGetConfigWithDataTypes()
1952         throws InterruptedException, ExecutionException, IOException {
1953                 Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
1954                 connectNode();
1955                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1956                 String identifier = extractIdentifier(lines);
1957                 assertThat(lines, matchesFcpMessage(
1958                         "GetConfig",
1959                         "Identifier=" + identifier,
1960                         "WithDataTypes=true",
1961                         "EndMessage"
1962                 ));
1963                 fcpServer.writeLine(
1964                         "ConfigData",
1965                         "Identifier=" + identifier,
1966                         "dataType.foo=number",
1967                         "EndMessage"
1968                 );
1969                 assertThat(configData.get().getDataType("foo"), is("number"));
1970         }
1971
1972         @Test
1973         public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
1974                 Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
1975                 connectNode();
1976                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1977                 String identifier = extractIdentifier(lines);
1978                 assertThat(lines, matchesFcpMessage(
1979                         "ModifyConfig",
1980                         "Identifier=" + identifier,
1981                         "foo.bar=baz",
1982                         "EndMessage"
1983                 ));
1984                 fcpServer.writeLine(
1985                         "ConfigData",
1986                         "Identifier=" + identifier,
1987                         "current.foo.bar=baz",
1988                         "EndMessage"
1989                 );
1990                 assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
1991         }
1992
1993         public class LoadPlugin {
1994
1995                 @Test
1996                 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1997                         Future<Optional<PluginInfo>> pluginInfo =
1998                                 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1999                         connectNode();
2000                         List<String> lines = fcpServer.collectUntil(is("EndMessage"));
2001                         String identifier = extractIdentifier(lines);
2002                         assertThat(lines, matchesFcpMessage(
2003                                 "LoadPlugin",
2004                                 "Identifier=" + identifier,
2005                                 "PluginURL=superPlugin",
2006                                 "URLType=official",
2007                                 "OfficialSource=freenet",
2008                                 "EndMessage"
2009                         ));
2010                         assertThat(lines, not(contains(startsWith("Store="))));
2011                         replyWithPluginInfo(identifier);
2012                         verifyPluginInfo(pluginInfo);
2013                 }
2014
2015                 @Test
2016                 public void persistentFromFreenet() throws ExecutionException, InterruptedException,
2017                 IOException {
2018                         Future<Optional<PluginInfo>> pluginInfo =
2019                                 fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
2020                         connectNode();
2021                         List<String> lines = fcpServer.collectUntil(is("EndMessage"));
2022                         String identifier = extractIdentifier(lines);
2023                         assertThat(lines, matchesFcpMessage(
2024                                 "LoadPlugin",
2025                                 "Identifier=" + identifier,
2026                                 "PluginURL=superPlugin",
2027                                 "URLType=official",
2028                                 "OfficialSource=freenet",
2029                                 "Store=true",
2030                                 "EndMessage"
2031                         ));
2032                         replyWithPluginInfo(identifier);
2033                         verifyPluginInfo(pluginInfo);
2034                 }
2035
2036                 @Test
2037                 public void fromHttps() throws ExecutionException, InterruptedException, IOException {
2038                         Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().officialFromHttps("superPlugin").execute();
2039                         connectNode();
2040                         List<String> lines = fcpServer.collectUntil(is("EndMessage"));
2041                         String identifier = extractIdentifier(lines);
2042                         assertThat(lines, matchesFcpMessage(
2043                                 "LoadPlugin",
2044                                 "Identifier=" + identifier,
2045                                 "PluginURL=superPlugin",
2046                                 "URLType=official",
2047                                 "OfficialSource=https",
2048                                 "EndMessage"
2049                         ));
2050                         replyWithPluginInfo(identifier);
2051                         verifyPluginInfo(pluginInfo);
2052                 }
2053
2054                 @Test
2055                 public void fromFile() throws ExecutionException, InterruptedException, IOException {
2056                         Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFile("/path/to/plugin.jar").execute();
2057                         connectNode();
2058                         List<String> lines = fcpServer.collectUntil(is("EndMessage"));
2059                         String identifier = extractIdentifier(lines);
2060                         assertThat(lines, matchesFcpMessage(
2061                                 "LoadPlugin",
2062                                 "Identifier=" + identifier,
2063                                 "PluginURL=/path/to/plugin.jar",
2064                                 "URLType=file",
2065                                 "EndMessage"
2066                         ));
2067                         replyWithPluginInfo(identifier);
2068                         verifyPluginInfo(pluginInfo);
2069                 }
2070
2071                 @Test
2072                 public void fromUrl() throws ExecutionException, InterruptedException, IOException {
2073                         Future<Optional<PluginInfo>> pluginInfo =
2074                                 fcpClient.loadPlugin().fromUrl("http://server.com/plugin.jar").execute();
2075                         connectNode();
2076                         List<String> lines = fcpServer.collectUntil(is("EndMessage"));
2077                         String identifier = extractIdentifier(lines);
2078                         assertThat(lines, matchesFcpMessage(
2079                                 "LoadPlugin",
2080                                 "Identifier=" + identifier,
2081                                 "PluginURL=http://server.com/plugin.jar",
2082                                 "URLType=url",
2083                                 "EndMessage"
2084                         ));
2085                         replyWithPluginInfo(identifier);
2086                         verifyPluginInfo(pluginInfo);
2087                 }
2088
2089                 @Test
2090                 public void failedLoad() throws ExecutionException, InterruptedException, IOException {
2091                         Future<Optional<PluginInfo>> pluginInfo =
2092                                 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
2093                         connectNode();
2094                         List<String> lines = fcpServer.collectUntil(is("EndMessage"));
2095                         String identifier = extractIdentifier(lines);
2096                         fcpServer.writeLine(
2097                                 "ProtocolError",
2098                                 "Identifier=" + identifier,
2099                                 "EndMessage"
2100                         );
2101                         assertThat(pluginInfo.get().isPresent(), is(false));
2102                 }
2103
2104                 private void replyWithPluginInfo(String identifier) throws IOException {
2105                         fcpServer.writeLine(
2106                                 "PluginInfo",
2107                                 "Identifier=" + identifier,
2108                                 "PluginName=superPlugin",
2109                                 "IsTalkable=true",
2110                                 "LongVersion=1.2.3",
2111                                 "Version=42",
2112                                 "OriginUri=superPlugin",
2113                                 "Started=true",
2114                                 "EndMessage"
2115                         );
2116                 }
2117
2118                 private void verifyPluginInfo(Future<Optional<PluginInfo>> pluginInfo)
2119                 throws InterruptedException, ExecutionException {
2120                         assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
2121                         assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
2122                         assertThat(pluginInfo.get().get().isTalkable(), is(true));
2123                         assertThat(pluginInfo.get().get().getVersion(), is("42"));
2124                         assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
2125                         assertThat(pluginInfo.get().get().isStarted(), is(true));
2126                 }
2127
2128         }
2129
2130 }