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