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