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