1 package net.pterodactylus.fcp.quelaton;
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.allOf;
5 import static org.hamcrest.Matchers.contains;
6 import static org.hamcrest.Matchers.containsInAnyOrder;
7 import static org.hamcrest.Matchers.hasItem;
8 import static org.hamcrest.Matchers.hasSize;
9 import static org.hamcrest.Matchers.is;
10 import static org.hamcrest.Matchers.not;
11 import static org.hamcrest.Matchers.notNullValue;
12 import static org.hamcrest.Matchers.startsWith;
14 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
18 import java.nio.charset.StandardCharsets;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Optional;
22 import java.util.concurrent.CopyOnWriteArrayList;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.atomic.AtomicBoolean;
30 import java.util.concurrent.atomic.AtomicInteger;
31 import java.util.function.Supplier;
32 import java.util.stream.Collectors;
34 import net.pterodactylus.fcp.ARK;
35 import net.pterodactylus.fcp.ConfigData;
36 import net.pterodactylus.fcp.DSAGroup;
37 import net.pterodactylus.fcp.FcpKeyPair;
38 import net.pterodactylus.fcp.Key;
39 import net.pterodactylus.fcp.NodeData;
40 import net.pterodactylus.fcp.NodeRef;
41 import net.pterodactylus.fcp.Peer;
42 import net.pterodactylus.fcp.PeerNote;
43 import net.pterodactylus.fcp.PluginInfo;
44 import net.pterodactylus.fcp.Priority;
45 import net.pterodactylus.fcp.fake.FakeTcpServer;
46 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
48 import com.google.common.io.ByteStreams;
49 import com.google.common.io.Files;
50 import com.nitorcreations.junit.runners.NestedRunner;
51 import org.hamcrest.Description;
52 import org.hamcrest.Matcher;
53 import org.hamcrest.Matchers;
54 import org.hamcrest.TypeSafeDiagnosingMatcher;
55 import org.junit.After;
56 import org.junit.Assert;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
61 * Unit test for {@link DefaultFcpClient}.
63 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
65 @RunWith(NestedRunner.class)
66 public class DefaultFcpClientTest {
68 private static final String INSERT_URI =
69 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
70 private static final String REQUEST_URI =
71 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
73 private int threadCounter = 0;
74 private final ExecutorService threadPool =
75 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
76 private final FakeTcpServer fcpServer;
77 private final DefaultFcpClient fcpClient;
79 public DefaultFcpClientTest() throws IOException {
80 fcpServer = new FakeTcpServer(threadPool);
81 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
85 public void tearDown() throws IOException {
87 threadPool.shutdown();
90 @Test(expected = ExecutionException.class)
91 public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
92 throws IOException, ExecutionException, InterruptedException {
93 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
94 fcpServer.connect().get();
95 fcpServer.collectUntil(is("EndMessage"));
97 "CloseConnectionDuplicateClientName",
103 @Test(expected = ExecutionException.class)
104 public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
105 throws IOException, ExecutionException, InterruptedException {
106 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
107 fcpServer.connect().get();
108 fcpServer.collectUntil(is("EndMessage"));
113 private void connectNode() throws InterruptedException, ExecutionException, IOException {
114 fcpServer.connect().get();
115 fcpServer.collectUntil(is("EndMessage"));
116 fcpServer.writeLine("NodeHello",
117 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
118 "Revision=build01466",
120 "Version=Fred,0.7,1.0,1466",
122 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
126 "NodeLanguage=ENGLISH",
133 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
134 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
136 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
137 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
138 String identifier = extractIdentifier(lines);
141 "Identifier=" + identifier,
143 "StartupTime=1435610539000",
144 "CompletionTime=1435610540000",
145 "Metadata.ContentType=text/plain;charset=utf-8",
149 Optional<Data> data = dataFuture.get();
150 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
151 assertThat(data.get().size(), is(6L));
152 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
153 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
156 private String extractIdentifier(List<String> lines) {
157 return lines.stream()
158 .filter(s -> s.startsWith("Identifier="))
159 .map(s -> s.substring(s.indexOf('=') + 1))
165 public void clientGetDownloadsDataForCorrectIdentifier()
166 throws InterruptedException, ExecutionException, IOException {
167 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
169 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
170 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
171 String identifier = extractIdentifier(lines);
174 "Identifier=not-test",
176 "StartupTime=1435610539000",
177 "CompletionTime=1435610540000",
178 "Metadata.ContentType=text/plain;charset=latin-9",
184 "Identifier=" + identifier,
186 "StartupTime=1435610539000",
187 "CompletionTime=1435610540000",
188 "Metadata.ContentType=text/plain;charset=utf-8",
192 Optional<Data> data = dataFuture.get();
193 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
194 assertThat(data.get().size(), is(6L));
195 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
196 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
200 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
201 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
203 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
204 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
205 String identifier = extractIdentifier(lines);
208 "Identifier=" + identifier,
212 Optional<Data> data = dataFuture.get();
213 assertThat(data.isPresent(), is(false));
217 public void clientGetRecognizesGetFailedForCorrectIdentifier()
218 throws InterruptedException, ExecutionException, IOException {
219 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
221 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
222 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
223 String identifier = extractIdentifier(lines);
226 "Identifier=not-test",
232 "Identifier=" + identifier,
236 Optional<Data> data = dataFuture.get();
237 assertThat(data.isPresent(), is(false));
240 @Test(expected = ExecutionException.class)
241 public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
242 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
244 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
245 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
251 public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
252 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
254 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
255 String identifier = extractIdentifier(lines);
258 "InsertURI=" + INSERT_URI + "",
259 "RequestURI=" + REQUEST_URI + "",
260 "Identifier=" + identifier,
264 keyPair = fcpClient.generateKeypair().execute();
265 lines = fcpServer.collectUntil(is("EndMessage"));
266 identifier = extractIdentifier(lines);
269 "InsertURI=" + INSERT_URI + "",
270 "RequestURI=" + REQUEST_URI + "",
271 "Identifier=" + identifier,
278 public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
279 throws InterruptedException, ExecutionException, IOException {
280 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
282 fcpServer.collectUntil(is("EndMessage"));
287 } catch (ExecutionException e) {
289 keyPair = fcpClient.generateKeypair().execute();
291 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
292 String identifier = extractIdentifier(lines);
295 "InsertURI=" + INSERT_URI + "",
296 "RequestURI=" + REQUEST_URI + "",
297 "Identifier=" + identifier,
304 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
305 throws InterruptedException, ExecutionException, IOException {
306 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
308 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
309 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
313 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
314 throws InterruptedException, ExecutionException, IOException {
315 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
317 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
318 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
322 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
323 throws InterruptedException, ExecutionException, IOException {
324 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
326 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
327 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
331 public void clientGetWithPrioritySettingSendsCorrectCommands()
332 throws InterruptedException, ExecutionException, IOException {
333 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
335 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
336 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
340 public void clientGetWithRealTimeSettingSendsCorrectCommands()
341 throws InterruptedException, ExecutionException, IOException {
342 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
344 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
345 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
349 public void clientGetWithGlobalSettingSendsCorrectCommands()
350 throws InterruptedException, ExecutionException, IOException {
351 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
353 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
354 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
357 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
358 return new TypeSafeDiagnosingMatcher<List<String>>() {
360 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
361 if (!item.get(0).equals(name)) {
362 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
365 for (String requiredLine : requiredLines) {
366 if (item.indexOf(requiredLine) < 1) {
367 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
375 public void describeTo(Description description) {
376 description.appendText("FCP message named ").appendValue(name);
377 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
383 public void clientPutWithDirectDataSendsCorrectCommand()
384 throws IOException, ExecutionException, InterruptedException {
385 fcpClient.clientPut()
386 .from(new ByteArrayInputStream("Hello\n".getBytes()))
391 List<String> lines = fcpServer.collectUntil(is("Hello"));
392 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
396 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
397 throws InterruptedException, ExecutionException, IOException {
398 Future<Optional<Key>> key = fcpClient.clientPut()
399 .from(new ByteArrayInputStream("Hello\n".getBytes()))
404 List<String> lines = fcpServer.collectUntil(is("Hello"));
405 String identifier = extractIdentifier(lines);
408 "Identifier=not-the-right-one",
414 "Identifier=" + identifier,
417 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
421 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
422 throws InterruptedException, ExecutionException, IOException {
423 Future<Optional<Key>> key = fcpClient.clientPut()
424 .from(new ByteArrayInputStream("Hello\n".getBytes()))
429 List<String> lines = fcpServer.collectUntil(is("Hello"));
430 String identifier = extractIdentifier(lines);
433 "Identifier=not-the-right-one",
439 "Identifier=" + identifier,
442 assertThat(key.get().isPresent(), is(false));
446 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
447 throws InterruptedException, ExecutionException, IOException {
448 fcpClient.clientPut()
449 .named("otherName.txt")
450 .from(new ByteArrayInputStream("Hello\n".getBytes()))
455 List<String> lines = fcpServer.collectUntil(is("Hello"));
456 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
457 "DataLength=6", "URI=KSK@foo.txt"));
461 public void clientPutWithRedirectSendsCorrectCommand()
462 throws IOException, ExecutionException, InterruptedException {
463 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
465 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
467 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
471 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
472 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
474 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
476 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
480 public void clientPutWithFileCanCompleteTestDdaSequence()
481 throws IOException, ExecutionException, InterruptedException {
482 File tempFile = createTempFile();
483 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
485 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
486 String identifier = extractIdentifier(lines);
489 "Identifier=" + identifier,
493 lines = fcpServer.collectUntil(is("EndMessage"));
494 assertThat(lines, matchesFcpMessage(
496 "Directory=" + tempFile.getParent(),
497 "WantReadDirectory=true",
498 "WantWriteDirectory=false",
503 "Directory=" + tempFile.getParent(),
504 "ReadFilename=" + tempFile,
507 lines = fcpServer.collectUntil(is("EndMessage"));
508 assertThat(lines, matchesFcpMessage(
510 "Directory=" + tempFile.getParent(),
511 "ReadContent=test-content",
516 "Directory=" + tempFile.getParent(),
517 "ReadDirectoryAllowed=true",
520 lines = fcpServer.collectUntil(is("EndMessage"));
522 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
523 "Filename=" + new File(tempFile.getParent(), "test.dat")));
526 private File createTempFile() throws IOException {
527 File tempFile = File.createTempFile("test-dda-", ".dat");
528 tempFile.deleteOnExit();
529 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
534 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
535 throws InterruptedException, ExecutionException, IOException {
536 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
538 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
539 String identifier = extractIdentifier(lines);
542 "Identifier=not-the-right-one",
548 "Identifier=" + identifier,
552 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
556 public void clientPutAbortsOnProtocolErrorOtherThan25()
557 throws InterruptedException, ExecutionException, IOException {
558 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
560 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
561 String identifier = extractIdentifier(lines);
564 "Identifier=" + identifier,
568 assertThat(key.get().isPresent(), is(false));
572 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
573 InterruptedException {
574 File tempFile = createTempFile();
575 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
577 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
578 String identifier = extractIdentifier(lines);
581 "Identifier=" + identifier,
585 lines = fcpServer.collectUntil(is("EndMessage"));
586 assertThat(lines, matchesFcpMessage(
588 "Directory=" + tempFile.getParent(),
589 "WantReadDirectory=true",
590 "WantWriteDirectory=false",
595 "Directory=/some-other-directory",
596 "ReadFilename=" + tempFile,
601 "Directory=" + tempFile.getParent(),
602 "ReadFilename=" + tempFile,
605 lines = fcpServer.collectUntil(is("EndMessage"));
606 assertThat(lines, matchesFcpMessage(
608 "Directory=" + tempFile.getParent(),
609 "ReadContent=test-content",
615 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
616 throws IOException, ExecutionException, InterruptedException {
617 File tempFile = createTempFile();
618 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
620 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
621 String identifier = extractIdentifier(lines);
624 "Identifier=" + identifier,
628 lines = fcpServer.collectUntil(is("EndMessage"));
629 assertThat(lines, matchesFcpMessage(
631 "Directory=" + tempFile.getParent(),
632 "WantReadDirectory=true",
633 "WantWriteDirectory=false",
638 "Directory=" + tempFile.getParent(),
639 "ReadFilename=" + tempFile + ".foo",
642 lines = fcpServer.collectUntil(is("EndMessage"));
643 assertThat(lines, matchesFcpMessage(
645 "Directory=" + tempFile.getParent(),
646 "ReadContent=failed-to-read",
652 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
653 throws IOException, ExecutionException, InterruptedException {
654 File tempFile = createTempFile();
655 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
657 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
658 String identifier = extractIdentifier(lines);
661 "Directory=/some-other-directory",
666 "Identifier=" + identifier,
670 lines = fcpServer.collectUntil(is("EndMessage"));
671 assertThat(lines, matchesFcpMessage(
673 "Directory=" + tempFile.getParent(),
674 "WantReadDirectory=true",
675 "WantWriteDirectory=false",
681 public void clientPutSendsNotificationsForGeneratedKeys()
682 throws InterruptedException, ExecutionException, IOException {
683 List<String> generatedKeys = new CopyOnWriteArrayList<>();
684 Future<Optional<Key>> key = fcpClient.clientPut()
685 .onKeyGenerated(generatedKeys::add)
686 .from(new ByteArrayInputStream("Hello\n".getBytes()))
691 List<String> lines = fcpServer.collectUntil(is("Hello"));
692 String identifier = extractIdentifier(lines);
695 "Identifier=" + identifier,
702 "Identifier=" + identifier,
705 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
706 assertThat(generatedKeys, contains("KSK@foo.txt"));
710 public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
711 Future<NodeData> nodeData = fcpClient.getNode().execute();
713 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
714 String identifier = extractIdentifier(lines);
715 assertThat(lines, matchesFcpMessage(
717 "Identifier=" + identifier,
718 "GiveOpennetRef=false",
720 "WithVolatile=false",
725 "Identifier=" + identifier,
726 "ark.pubURI=SSK@3YEf.../ark",
729 "version=Fred,0.7,1.0,1466",
730 "lastGoodVersion=Fred,0.7,1.0,1466",
733 assertThat(nodeData.get(), notNullValue());
737 public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
738 throws InterruptedException, ExecutionException, IOException {
739 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
741 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
742 String identifier = extractIdentifier(lines);
743 assertThat(lines, matchesFcpMessage(
745 "Identifier=" + identifier,
746 "GiveOpennetRef=true",
748 "WithVolatile=false",
753 "Identifier=" + identifier,
755 "ark.pubURI=SSK@3YEf.../ark",
758 "version=Fred,0.7,1.0,1466",
759 "lastGoodVersion=Fred,0.7,1.0,1466",
762 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
766 public void defaultFcpClientCanGetNodeInformationWithPrivateData()
767 throws InterruptedException, ExecutionException, IOException {
768 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
770 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
771 String identifier = extractIdentifier(lines);
772 assertThat(lines, matchesFcpMessage(
774 "Identifier=" + identifier,
775 "GiveOpennetRef=false",
777 "WithVolatile=false",
782 "Identifier=" + identifier,
784 "ark.pubURI=SSK@3YEf.../ark",
787 "version=Fred,0.7,1.0,1466",
788 "lastGoodVersion=Fred,0.7,1.0,1466",
789 "ark.privURI=SSK@XdHMiRl",
792 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
796 public void defaultFcpClientCanGetNodeInformationWithVolatileData()
797 throws InterruptedException, ExecutionException, IOException {
798 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
800 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
801 String identifier = extractIdentifier(lines);
802 assertThat(lines, matchesFcpMessage(
804 "Identifier=" + identifier,
805 "GiveOpennetRef=false",
812 "Identifier=" + identifier,
814 "ark.pubURI=SSK@3YEf.../ark",
817 "version=Fred,0.7,1.0,1466",
818 "lastGoodVersion=Fred,0.7,1.0,1466",
819 "volatile.freeJavaMemory=205706528",
822 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
826 public void defaultFcpClientCanGetConfigWithoutDetails()
827 throws InterruptedException, ExecutionException, IOException {
828 Future<ConfigData> configData = fcpClient.getConfig().execute();
830 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
831 String identifier = extractIdentifier(lines);
832 assertThat(lines, matchesFcpMessage(
834 "Identifier=" + identifier,
839 "Identifier=" + identifier,
842 assertThat(configData.get(), notNullValue());
846 public void defaultFcpClientCanGetConfigWithCurrent()
847 throws InterruptedException, ExecutionException, IOException {
848 Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
850 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
851 String identifier = extractIdentifier(lines);
852 assertThat(lines, matchesFcpMessage(
854 "Identifier=" + identifier,
860 "Identifier=" + identifier,
864 assertThat(configData.get().getCurrent("foo"), is("bar"));
868 public void defaultFcpClientCanGetConfigWithDefaults()
869 throws InterruptedException, ExecutionException, IOException {
870 Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
872 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
873 String identifier = extractIdentifier(lines);
874 assertThat(lines, matchesFcpMessage(
876 "Identifier=" + identifier,
882 "Identifier=" + identifier,
886 assertThat(configData.get().getDefault("foo"), is("bar"));
890 public void defaultFcpClientCanGetConfigWithSortOrder()
891 throws InterruptedException, ExecutionException, IOException {
892 Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
894 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
895 String identifier = extractIdentifier(lines);
896 assertThat(lines, matchesFcpMessage(
898 "Identifier=" + identifier,
899 "WithSortOrder=true",
904 "Identifier=" + identifier,
908 assertThat(configData.get().getSortOrder("foo"), is(17));
912 public void defaultFcpClientCanGetConfigWithExpertFlag()
913 throws InterruptedException, ExecutionException, IOException {
914 Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
916 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
917 String identifier = extractIdentifier(lines);
918 assertThat(lines, matchesFcpMessage(
920 "Identifier=" + identifier,
921 "WithExpertFlag=true",
926 "Identifier=" + identifier,
927 "expertFlag.foo=true",
930 assertThat(configData.get().getExpertFlag("foo"), is(true));
934 public void defaultFcpClientCanGetConfigWithForceWriteFlag()
935 throws InterruptedException, ExecutionException, IOException {
936 Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
938 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
939 String identifier = extractIdentifier(lines);
940 assertThat(lines, matchesFcpMessage(
942 "Identifier=" + identifier,
943 "WithForceWriteFlag=true",
948 "Identifier=" + identifier,
949 "forceWriteFlag.foo=true",
952 assertThat(configData.get().getForceWriteFlag("foo"), is(true));
956 public void defaultFcpClientCanGetConfigWithShortDescription()
957 throws InterruptedException, ExecutionException, IOException {
958 Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
960 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
961 String identifier = extractIdentifier(lines);
962 assertThat(lines, matchesFcpMessage(
964 "Identifier=" + identifier,
965 "WithShortDescription=true",
970 "Identifier=" + identifier,
971 "shortDescription.foo=bar",
974 assertThat(configData.get().getShortDescription("foo"), is("bar"));
978 public void defaultFcpClientCanGetConfigWithLongDescription()
979 throws InterruptedException, ExecutionException, IOException {
980 Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
982 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
983 String identifier = extractIdentifier(lines);
984 assertThat(lines, matchesFcpMessage(
986 "Identifier=" + identifier,
987 "WithLongDescription=true",
992 "Identifier=" + identifier,
993 "longDescription.foo=bar",
996 assertThat(configData.get().getLongDescription("foo"), is("bar"));
1000 public void defaultFcpClientCanGetConfigWithDataTypes()
1001 throws InterruptedException, ExecutionException, IOException {
1002 Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
1004 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1005 String identifier = extractIdentifier(lines);
1006 assertThat(lines, matchesFcpMessage(
1008 "Identifier=" + identifier,
1009 "WithDataTypes=true",
1012 fcpServer.writeLine(
1014 "Identifier=" + identifier,
1015 "dataType.foo=number",
1018 assertThat(configData.get().getDataType("foo"), is("number"));
1022 public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
1023 Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
1025 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1026 String identifier = extractIdentifier(lines);
1027 assertThat(lines, matchesFcpMessage(
1029 "Identifier=" + identifier,
1033 fcpServer.writeLine(
1035 "Identifier=" + identifier,
1036 "current.foo.bar=baz",
1039 assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
1042 private List<String> lines;
1043 private String identifier;
1045 private void connectAndAssert(Supplier<Matcher<List<String>>> requestMatcher)
1046 throws InterruptedException, ExecutionException, IOException {
1048 readMessage(requestMatcher);
1051 private void readMessage(Supplier<Matcher<List<String>>> requestMatcher) throws IOException {
1052 lines = fcpServer.collectUntil(is("EndMessage"));
1053 identifier = extractIdentifier(lines);
1054 assertThat(lines, requestMatcher.get());
1057 public class GenerateKeyPair {
1060 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
1061 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
1062 connectAndAssert(() ->matchesFcpMessage("GenerateSSK", "EndMessage"));
1064 FcpKeyPair keyPair = keyPairFuture.get();
1065 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
1066 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
1069 private void replyWithKeyPair() throws IOException {
1070 fcpServer.writeLine("SSKKeypair",
1071 "InsertURI=" + INSERT_URI + "",
1072 "RequestURI=" + REQUEST_URI + "",
1073 "Identifier=" + identifier,
1079 public class Peers {
1081 public class PeerCommands {
1083 public class ListPeer {
1086 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
1087 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
1088 connectAndAssert(() -> matchesListPeer("id1"));
1089 replyWithPeer("id1");
1090 assertThat(peer.get().get().getIdentity(), is("id1"));
1094 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1095 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
1096 connectAndAssert(() -> matchesListPeer("host.free.net:12345"));
1097 replyWithPeer("id1");
1098 assertThat(peer.get().get().getIdentity(), is("id1"));
1102 public void byName() throws InterruptedException, ExecutionException, IOException {
1103 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
1104 connectAndAssert(() -> matchesListPeer("FriendNode"));
1105 replyWithPeer("id1");
1106 assertThat(peer.get().get().getIdentity(), is("id1"));
1110 public void unknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1111 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
1112 connectAndAssert(() -> matchesListPeer("id2"));
1113 replyWithUnknownNodeIdentifier();
1114 assertThat(peer.get().isPresent(), is(false));
1117 private Matcher<List<String>> matchesListPeer(String nodeId) {
1118 return matchesFcpMessage(
1120 "Identifier=" + identifier,
1121 "NodeIdentifier=" + nodeId,
1128 public class ListPeers {
1131 public void withoutMetadataOrVolatile() throws IOException, ExecutionException, InterruptedException {
1132 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
1133 connectAndAssert(() -> matchesListPeers(false, false));
1134 replyWithPeer("id1");
1135 replyWithPeer("id2");
1136 sendEndOfPeerList();
1137 assertThat(peers.get(), hasSize(2));
1138 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
1139 containsInAnyOrder("id1", "id2"));
1143 public void withMetadata() throws IOException, ExecutionException, InterruptedException {
1144 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
1145 connectAndAssert(() -> matchesListPeers(false, true));
1146 replyWithPeer("id1", "metadata.foo=bar1");
1147 replyWithPeer("id2", "metadata.foo=bar2");
1148 sendEndOfPeerList();
1149 assertThat(peers.get(), hasSize(2));
1150 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
1151 containsInAnyOrder("bar1", "bar2"));
1155 public void withVolatile() throws IOException, ExecutionException, InterruptedException {
1156 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
1157 connectAndAssert(() -> matchesListPeers(true, false));
1158 replyWithPeer("id1", "volatile.foo=bar1");
1159 replyWithPeer("id2", "volatile.foo=bar2");
1160 sendEndOfPeerList();
1161 assertThat(peers.get(), hasSize(2));
1162 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
1163 containsInAnyOrder("bar1", "bar2"));
1166 private Matcher<List<String>> matchesListPeers(boolean withVolatile, boolean withMetadata) {
1167 return matchesFcpMessage(
1169 "WithVolatile=" + withVolatile,
1170 "WithMetadata=" + withMetadata,
1175 private void sendEndOfPeerList() throws IOException {
1176 fcpServer.writeLine(
1178 "Identifier=" + identifier,
1185 public class AddPeer {
1188 public void fromFile() throws InterruptedException, ExecutionException, IOException {
1189 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1190 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("File=/tmp/ref.txt")));
1191 replyWithPeer("id1");
1192 assertThat(peer.get().get().getIdentity(), is("id1"));
1196 public void fromUrl() throws InterruptedException, ExecutionException, IOException {
1197 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1198 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("URL=http://node.ref/")));
1199 replyWithPeer("id1");
1200 assertThat(peer.get().get().getIdentity(), is("id1"));
1204 public void fromNodeRef() throws InterruptedException, ExecutionException, IOException {
1205 NodeRef nodeRef = createNodeRef();
1206 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1207 connectAndAssert(() -> allOf(matchesAddPeer(), Matchers.<String>hasItems(
1209 "ark.pubURI=public",
1213 "dsaGroup.q=subprime",
1214 "dsaPubKey.y=dsa-public",
1215 "physical.udp=1.2.3.4:5678",
1216 "auth.negTypes=3;5",
1219 replyWithPeer("id1");
1220 assertThat(peer.get().get().getIdentity(), is("id1"));
1223 private NodeRef createNodeRef() {
1224 NodeRef nodeRef = new NodeRef();
1225 nodeRef.setIdentity("id1");
1226 nodeRef.setName("name");
1227 nodeRef.setARK(new ARK("public", "1"));
1228 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1229 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1230 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1231 nodeRef.setDSAPublicKey("dsa-public");
1232 nodeRef.setSignature("sig");
1236 private Matcher<List<String>> matchesAddPeer() {
1237 return matchesFcpMessage(
1239 "Identifier=" + identifier,
1246 public class ModifyPeer {
1249 public void defaultFcpClientCanEnablePeerByName()
1250 throws InterruptedException, ExecutionException, IOException {
1251 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("id1").execute();
1252 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1253 replyWithPeer("id1");
1254 assertThat(peer.get().get().getIdentity(), is("id1"));
1258 public void defaultFcpClientCanDisablePeerByName()
1259 throws InterruptedException, ExecutionException, IOException {
1260 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("id1").execute();
1261 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", true));
1262 replyWithPeer("id1");
1263 assertThat(peer.get().get().getIdentity(), is("id1"));
1267 public void defaultFcpClientCanEnablePeerByIdentity()
1268 throws InterruptedException, ExecutionException, IOException {
1269 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1270 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1271 replyWithPeer("id1");
1272 assertThat(peer.get().get().getIdentity(), is("id1"));
1276 public void defaultFcpClientCanEnablePeerByHostAndPort()
1277 throws InterruptedException, ExecutionException, IOException {
1278 Future<Optional<Peer>> peer =
1279 fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
1280 connectAndAssert(() -> matchesModifyPeer("1.2.3.4:5678", "IsDisabled", false));
1281 replyWithPeer("id1");
1282 assertThat(peer.get().get().getIdentity(), is("id1"));
1286 public void allowLocalAddressesOfPeer() throws InterruptedException, ExecutionException, IOException {
1287 Future<Optional<Peer>> peer =
1288 fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
1289 connectAndAssert(() -> allOf(
1290 matchesModifyPeer("id1", "AllowLocalAddresses", true),
1291 not(contains(startsWith("IsDisabled=")))
1293 replyWithPeer("id1");
1294 assertThat(peer.get().get().getIdentity(), is("id1"));
1298 public void disallowLocalAddressesOfPeer()
1299 throws InterruptedException, ExecutionException, IOException {
1300 Future<Optional<Peer>> peer =
1301 fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
1302 connectAndAssert(() -> allOf(
1303 matchesModifyPeer("id1", "AllowLocalAddresses", false),
1304 not(contains(startsWith("IsDisabled=")))
1306 replyWithPeer("id1");
1307 assertThat(peer.get().get().getIdentity(), is("id1"));
1311 public void setBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1312 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
1313 connectAndAssert(() -> allOf(
1314 matchesModifyPeer("id1", "IsBurstOnly", true),
1315 not(contains(startsWith("AllowLocalAddresses="))),
1316 not(contains(startsWith("IsDisabled=")))
1318 replyWithPeer("id1");
1319 assertThat(peer.get().get().getIdentity(), is("id1"));
1323 public void clearBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1324 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
1325 connectAndAssert(() -> allOf(
1326 matchesModifyPeer("id1", "IsBurstOnly", false),
1327 not(contains(startsWith("AllowLocalAddresses="))),
1328 not(contains(startsWith("IsDisabled=")))
1330 replyWithPeer("id1");
1331 assertThat(peer.get().get().getIdentity(), is("id1"));
1335 public void defaultFcpClientCanSetListenOnlyForPeer()
1336 throws InterruptedException, ExecutionException, IOException {
1337 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
1338 connectAndAssert(() -> allOf(
1339 matchesModifyPeer("id1", "IsListenOnly", true),
1340 not(contains(startsWith("AllowLocalAddresses="))),
1341 not(contains(startsWith("IsDisabled="))),
1342 not(contains(startsWith("IsBurstOnly=")))
1344 replyWithPeer("id1");
1345 assertThat(peer.get().get().getIdentity(), is("id1"));
1349 public void clearListenOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1350 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
1351 connectAndAssert(() -> allOf(
1352 matchesModifyPeer("id1", "IsListenOnly", false),
1353 not(contains(startsWith("AllowLocalAddresses="))),
1354 not(contains(startsWith("IsDisabled="))),
1355 not(contains(startsWith("IsBurstOnly=")))
1357 replyWithPeer("id1");
1358 assertThat(peer.get().get().getIdentity(), is("id1"));
1362 public void ignoreSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1363 Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
1364 connectAndAssert(() -> allOf(
1365 matchesModifyPeer("id1", "IgnoreSourcePort", true),
1366 not(contains(startsWith("AllowLocalAddresses="))),
1367 not(contains(startsWith("IsDisabled="))),
1368 not(contains(startsWith("IsBurstOnly="))),
1369 not(contains(startsWith("IsListenOnly=")))
1371 replyWithPeer("id1");
1372 assertThat(peer.get().get().getIdentity(), is("id1"));
1376 public void useSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1377 Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
1378 connectAndAssert(() -> allOf(
1379 matchesModifyPeer("id1", "IgnoreSourcePort", false),
1380 not(contains(startsWith("AllowLocalAddresses="))),
1381 not(contains(startsWith("IsDisabled="))),
1382 not(contains(startsWith("IsBurstOnly="))),
1383 not(contains(startsWith("IsListenOnly=")))
1385 replyWithPeer("id1");
1386 assertThat(peer.get().get().getIdentity(), is("id1"));
1390 public void unknownNode() throws InterruptedException, ExecutionException, IOException {
1391 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1392 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1393 replyWithUnknownNodeIdentifier();
1394 assertThat(peer.get().isPresent(), is(false));
1397 private Matcher<List<String>> matchesModifyPeer(String nodeIdentifier, String setting, boolean value) {
1398 return matchesFcpMessage(
1400 "Identifier=" + identifier,
1401 "NodeIdentifier=" + nodeIdentifier,
1402 setting + "=" + value,
1409 public class RemovePeer {
1412 public void byName() throws InterruptedException, ExecutionException, IOException {
1413 Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
1414 connectAndAssert(() -> matchesRemovePeer("Friend1"));
1415 replyWithPeerRemoved("Friend1");
1416 assertThat(peer.get(), is(true));
1420 public void invalidName() throws InterruptedException, ExecutionException, IOException {
1421 Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
1422 connectAndAssert(() -> matchesRemovePeer("NotFriend1"));
1423 replyWithUnknownNodeIdentifier();
1424 assertThat(peer.get(), is(false));
1428 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
1429 Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
1430 connectAndAssert(() -> matchesRemovePeer("id1"));
1431 replyWithPeerRemoved("id1");
1432 assertThat(peer.get(), is(true));
1436 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1437 Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
1438 connectAndAssert(() -> matchesRemovePeer("1.2.3.4:5678"));
1439 replyWithPeerRemoved("Friend1");
1440 assertThat(peer.get(), is(true));
1443 private Matcher<List<String>> matchesRemovePeer(String nodeIdentifier) {
1444 return matchesFcpMessage(
1446 "Identifier=" + identifier,
1447 "NodeIdentifier=" + nodeIdentifier,
1452 private void replyWithPeerRemoved(String nodeIdentifier) throws IOException {
1453 fcpServer.writeLine(
1455 "Identifier=" + identifier,
1456 "NodeIdentifier=" + nodeIdentifier,
1463 private void replyWithPeer(String peerId, String... additionalLines) throws IOException {
1464 fcpServer.writeLine(
1466 "Identifier=" + identifier,
1467 "identity=" + peerId,
1469 "ark.pubURI=SSK@3YEf.../ark",
1472 "version=Fred,0.7,1.0,1466",
1473 "lastGoodVersion=Fred,0.7,1.0,1466"
1475 fcpServer.writeLine(additionalLines);
1476 fcpServer.writeLine("EndMessage");
1481 public class PeerNoteCommands {
1483 public class ListPeerNotes {
1486 public void onUnknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1487 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1488 connectAndAssert(() -> matchesListPeerNotes("Friend1"));
1489 replyWithUnknownNodeIdentifier();
1490 assertThat(peerNote.get().isPresent(), is(false));
1494 public void byNodeName() throws InterruptedException, ExecutionException, IOException {
1495 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1496 connectAndAssert(() -> matchesListPeerNotes("Friend1"));
1497 replyWithPeerNote();
1498 replyWithEndListPeerNotes();
1499 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1500 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1504 public void byNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1505 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
1506 connectAndAssert(() -> matchesListPeerNotes("id1"));
1507 replyWithPeerNote();
1508 replyWithEndListPeerNotes();
1509 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1510 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1514 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1515 Future<Optional<PeerNote>> peerNote =
1516 fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
1517 connectAndAssert(() -> matchesListPeerNotes("1.2.3.4:5678"));
1518 replyWithPeerNote();
1519 replyWithEndListPeerNotes();
1520 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1521 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1524 private Matcher<List<String>> matchesListPeerNotes(String nodeIdentifier) {
1525 return matchesFcpMessage(
1527 "NodeIdentifier=" + nodeIdentifier,
1532 private void replyWithEndListPeerNotes() throws IOException {
1533 fcpServer.writeLine(
1535 "Identifier=" + identifier,
1540 private void replyWithPeerNote() throws IOException {
1541 fcpServer.writeLine(
1543 "Identifier=" + identifier,
1544 "NodeIdentifier=Friend1",
1545 "NoteText=RXhhbXBsZSBUZXh0Lg==",
1553 public class ModifyPeerNotes {
1556 public void byName() throws InterruptedException, ExecutionException, IOException {
1557 Future<Boolean> noteUpdated =
1558 fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1559 connectAndAssert(() -> matchesModifyPeerNote("Friend1"));
1560 replyWithPeerNote();
1561 assertThat(noteUpdated.get(), is(true));
1565 public void onUnknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1566 Future<Boolean> noteUpdated =
1567 fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1568 connectAndAssert(() -> matchesModifyPeerNote("Friend1"));
1569 replyWithUnknownNodeIdentifier();
1570 assertThat(noteUpdated.get(), is(false));
1574 public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
1575 throws InterruptedException, ExecutionException, IOException {
1576 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
1577 assertThat(noteUpdated.get(), is(false));
1581 public void byIdentifier() throws InterruptedException, ExecutionException, IOException {
1582 Future<Boolean> noteUpdated =
1583 fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
1584 connectAndAssert(() -> matchesModifyPeerNote("id1"));
1585 replyWithPeerNote();
1586 assertThat(noteUpdated.get(), is(true));
1590 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1591 Future<Boolean> noteUpdated =
1592 fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
1593 connectAndAssert(() -> matchesModifyPeerNote("1.2.3.4:5678"));
1594 replyWithPeerNote();
1595 assertThat(noteUpdated.get(), is(true));
1598 private Matcher<List<String>> matchesModifyPeerNote(String nodeIdentifier) {
1599 return matchesFcpMessage(
1601 "Identifier=" + identifier,
1602 "NodeIdentifier=" + nodeIdentifier,
1609 private void replyWithPeerNote() throws IOException {
1610 fcpServer.writeLine(
1612 "Identifier=" + identifier,
1613 "NodeIdentifier=Friend1",
1624 private void replyWithUnknownNodeIdentifier() throws IOException {
1625 fcpServer.writeLine(
1626 "UnknownNodeIdentifier",
1627 "Identifier=" + identifier,
1628 "NodeIdentifier=id2",
1635 public class PluginCommands {
1637 private static final String CLASS_NAME = "foo.plugin.Plugin";
1639 private void replyWithPluginInfo() throws IOException {
1640 fcpServer.writeLine(
1642 "Identifier=" + identifier,
1643 "PluginName=superPlugin",
1645 "LongVersion=1.2.3",
1647 "OriginUri=superPlugin",
1653 private void verifyPluginInfo(Future<Optional<PluginInfo>> pluginInfo)
1654 throws InterruptedException, ExecutionException {
1655 assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
1656 assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
1657 assertThat(pluginInfo.get().get().isTalkable(), is(true));
1658 assertThat(pluginInfo.get().get().getVersion(), is("42"));
1659 assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
1660 assertThat(pluginInfo.get().get().isStarted(), is(true));
1663 public class LoadPlugin {
1665 public class OfficialPlugins {
1668 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1669 Future<Optional<PluginInfo>> pluginInfo =
1670 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1671 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1672 assertThat(lines, not(contains(startsWith("Store="))));
1673 replyWithPluginInfo();
1674 verifyPluginInfo(pluginInfo);
1678 public void persistentFromFreenet() throws ExecutionException, InterruptedException, IOException {
1679 Future<Optional<PluginInfo>> pluginInfo =
1680 fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
1681 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1682 assertThat(lines, hasItem("Store=true"));
1683 replyWithPluginInfo();
1684 verifyPluginInfo(pluginInfo);
1688 public void fromHttps() throws ExecutionException, InterruptedException, IOException {
1689 Future<Optional<PluginInfo>> pluginInfo =
1690 fcpClient.loadPlugin().officialFromHttps("superPlugin").execute();
1691 connectAndAssert(() -> createMatcherForOfficialSource("https"));
1692 replyWithPluginInfo();
1693 verifyPluginInfo(pluginInfo);
1696 private Matcher<List<String>> createMatcherForOfficialSource(String officialSource) {
1697 return matchesFcpMessage(
1699 "Identifier=" + identifier,
1700 "PluginURL=superPlugin",
1702 "OfficialSource=" + officialSource,
1709 public class FromOtherSources {
1711 private static final String FILE_PATH = "/path/to/plugin.jar";
1712 private static final String URL = "http://server.com/plugin.jar";
1713 private static final String KEY = "KSK@plugin.jar";
1716 public void fromFile() throws ExecutionException, InterruptedException, IOException {
1717 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFile(FILE_PATH).execute();
1718 connectAndAssert(() -> createMatcher("file", FILE_PATH));
1719 replyWithPluginInfo();
1720 verifyPluginInfo(pluginInfo);
1724 public void fromUrl() throws ExecutionException, InterruptedException, IOException {
1725 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromUrl(URL).execute();
1726 connectAndAssert(() -> createMatcher("url", URL));
1727 replyWithPluginInfo();
1728 verifyPluginInfo(pluginInfo);
1732 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1733 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFreenet(KEY).execute();
1734 connectAndAssert(() -> createMatcher("freenet", KEY));
1735 replyWithPluginInfo();
1736 verifyPluginInfo(pluginInfo);
1739 private Matcher<List<String>> createMatcher(String urlType, String url) {
1740 return matchesFcpMessage(
1742 "Identifier=" + identifier,
1744 "URLType=" + urlType,
1751 public class Failed {
1754 public void failedLoad() throws ExecutionException, InterruptedException, IOException {
1755 Future<Optional<PluginInfo>> pluginInfo =
1756 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1757 connectAndAssert(() -> matchesFcpMessage("LoadPlugin", "EndMessage"));
1758 replyWithProtocolError();
1759 assertThat(pluginInfo.get().isPresent(), is(false));
1766 private void replyWithProtocolError() throws IOException {
1767 fcpServer.writeLine(
1769 "Identifier=" + identifier,
1774 public class ReloadPlugin {
1777 public void reloadingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1778 Future<Optional<PluginInfo>> pluginInfo = fcpClient.reloadPlugin().plugin(CLASS_NAME).execute();
1779 connectAndAssert(() -> matchReloadPluginMessage());
1780 replyWithPluginInfo();
1781 verifyPluginInfo(pluginInfo);
1785 public void reloadingPluginWithMaxWaitTimeWorks()
1786 throws InterruptedException, ExecutionException, IOException {
1787 Future<Optional<PluginInfo>> pluginInfo =
1788 fcpClient.reloadPlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1789 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("MaxWaitTime=1234")));
1790 replyWithPluginInfo();
1791 verifyPluginInfo(pluginInfo);
1795 public void reloadingPluginWithPurgeWorks()
1796 throws InterruptedException, ExecutionException, IOException {
1797 Future<Optional<PluginInfo>> pluginInfo =
1798 fcpClient.reloadPlugin().purge().plugin(CLASS_NAME).execute();
1799 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Purge=true")));
1800 replyWithPluginInfo();
1801 verifyPluginInfo(pluginInfo);
1805 public void reloadingPluginWithStoreWorks()
1806 throws InterruptedException, ExecutionException, IOException {
1807 Future<Optional<PluginInfo>> pluginInfo =
1808 fcpClient.reloadPlugin().addToConfig().plugin(CLASS_NAME).execute();
1809 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Store=true")));
1810 replyWithPluginInfo();
1811 verifyPluginInfo(pluginInfo);
1814 private Matcher<List<String>> matchReloadPluginMessage() {
1815 return matchesFcpMessage(
1817 "Identifier=" + identifier,
1818 "PluginName=" + CLASS_NAME,
1825 public class RemovePlugin {
1828 public void removingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1829 Future<Boolean> pluginRemoved = fcpClient.removePlugin().plugin(CLASS_NAME).execute();
1830 connectAndAssert(() -> matchPluginRemovedMessage());
1831 replyWithPluginRemoved();
1832 assertThat(pluginRemoved.get(), is(true));
1836 public void removingPluginWithMaxWaitTimeWorks()
1837 throws InterruptedException, ExecutionException, IOException {
1838 Future<Boolean> pluginRemoved = fcpClient.removePlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1839 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("MaxWaitTime=1234")));
1840 replyWithPluginRemoved();
1841 assertThat(pluginRemoved.get(), is(true));
1845 public void removingPluginWithPurgeWorks()
1846 throws InterruptedException, ExecutionException, IOException {
1847 Future<Boolean> pluginRemoved = fcpClient.removePlugin().purge().plugin(CLASS_NAME).execute();
1848 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("Purge=true")));
1849 replyWithPluginRemoved();
1850 assertThat(pluginRemoved.get(), is(true));
1853 private void replyWithPluginRemoved() throws IOException {
1854 fcpServer.writeLine(
1856 "Identifier=" + identifier,
1857 "PluginName=" + CLASS_NAME,
1862 private Matcher<List<String>> matchPluginRemovedMessage() {
1863 return matchesFcpMessage(
1865 "Identifier=" + identifier,
1866 "PluginName=" + CLASS_NAME,
1873 public class GetPluginInfo {
1876 public void gettingPluginInfoWorks() throws InterruptedException, ExecutionException, IOException {
1877 Future<Optional<PluginInfo>> pluginInfo = fcpClient.getPluginInfo().plugin(CLASS_NAME).execute();
1878 connectAndAssert(() -> matchGetPluginInfoMessage());
1879 replyWithPluginInfo();
1880 verifyPluginInfo(pluginInfo);
1884 public void gettingPluginInfoWithDetailsWorks()
1885 throws InterruptedException, ExecutionException, IOException {
1886 Future<Optional<PluginInfo>> pluginInfo =
1887 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1888 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1889 replyWithPluginInfo();
1890 verifyPluginInfo(pluginInfo);
1894 public void protocolErrorIsRecognizedAsFailure()
1895 throws InterruptedException, ExecutionException, IOException {
1896 Future<Optional<PluginInfo>> pluginInfo =
1897 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1898 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1899 replyWithProtocolError();
1900 assertThat(pluginInfo.get(), is(Optional.empty()));
1903 private Matcher<List<String>> matchGetPluginInfoMessage() {
1904 return matchesFcpMessage(
1906 "Identifier=" + identifier,
1907 "PluginName=" + CLASS_NAME,
1916 public class UskSubscriptionCommands {
1918 private static final String URI = "USK@some,uri/file.txt";
1921 public void subscriptionWorks() throws InterruptedException, ExecutionException, IOException {
1922 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1923 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
1924 replyWithSubscribed();
1925 assertThat(uskSubscription.get().get().getUri(), is(URI));
1926 AtomicInteger edition = new AtomicInteger();
1927 CountDownLatch updated = new CountDownLatch(2);
1928 uskSubscription.get().get().onUpdate(e -> {
1930 updated.countDown();
1932 sendUpdateNotification(23);
1933 sendUpdateNotification(24);
1934 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1935 assertThat(edition.get(), is(24));
1939 public void subscriptionUpdatesMultipleTimes() throws InterruptedException, ExecutionException, IOException {
1940 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1941 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
1942 replyWithSubscribed();
1943 assertThat(uskSubscription.get().get().getUri(), is(URI));
1944 AtomicInteger edition = new AtomicInteger();
1945 CountDownLatch updated = new CountDownLatch(2);
1946 uskSubscription.get().get().onUpdate(e -> {
1948 updated.countDown();
1950 uskSubscription.get().get().onUpdate(e -> updated.countDown());
1951 sendUpdateNotification(23);
1952 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1953 assertThat(edition.get(), is(23));
1957 public void subscriptionCanBeCancelled() throws InterruptedException, ExecutionException, IOException {
1958 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1959 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
1960 replyWithSubscribed();
1961 assertThat(uskSubscription.get().get().getUri(), is(URI));
1962 AtomicBoolean updated = new AtomicBoolean();
1963 uskSubscription.get().get().onUpdate(e -> updated.set(true));
1964 uskSubscription.get().get().cancel();
1965 readMessage(() -> matchesFcpMessage("UnsubscribeUSK", "Identifier=" + identifier, "EndMessage"));
1966 sendUpdateNotification(23);
1967 assertThat(updated.get(), is(false));
1970 private void replyWithSubscribed() throws IOException {
1971 fcpServer.writeLine(
1973 "Identifier=" + identifier,
1980 private void sendUpdateNotification(int edition, String... additionalLines) throws IOException {
1981 fcpServer.writeLine(
1982 "SubscribedUSKUpdate",
1983 "Identifier=" + identifier,
1985 "Edition=" + edition
1987 fcpServer.writeLine(additionalLines);
1988 fcpServer.writeLine("EndMessage");