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