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