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.Arrays;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.Future;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.function.Supplier;
33 import java.util.stream.Collectors;
35 import net.pterodactylus.fcp.ARK;
36 import net.pterodactylus.fcp.ConfigData;
37 import net.pterodactylus.fcp.DSAGroup;
38 import net.pterodactylus.fcp.FcpKeyPair;
39 import net.pterodactylus.fcp.Key;
40 import net.pterodactylus.fcp.NodeData;
41 import net.pterodactylus.fcp.NodeRef;
42 import net.pterodactylus.fcp.Peer;
43 import net.pterodactylus.fcp.PeerNote;
44 import net.pterodactylus.fcp.PluginInfo;
45 import net.pterodactylus.fcp.Priority;
46 import net.pterodactylus.fcp.fake.FakeTcpServer;
47 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
49 import com.google.common.io.ByteStreams;
50 import com.google.common.io.Files;
51 import com.nitorcreations.junit.runners.NestedRunner;
52 import org.hamcrest.Description;
53 import org.hamcrest.Matcher;
54 import org.hamcrest.Matchers;
55 import org.hamcrest.TypeSafeDiagnosingMatcher;
56 import org.junit.After;
57 import org.junit.Assert;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
62 * Unit test for {@link DefaultFcpClient}.
64 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
66 @RunWith(NestedRunner.class)
67 public class DefaultFcpClientTest {
69 private static final String INSERT_URI =
70 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
71 private static final String REQUEST_URI =
72 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
74 private int threadCounter = 0;
75 private final ExecutorService threadPool =
76 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
77 private final FakeTcpServer fcpServer;
78 private final DefaultFcpClient fcpClient;
80 public DefaultFcpClientTest() throws IOException {
81 fcpServer = new FakeTcpServer(threadPool);
82 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
86 public void tearDown() throws IOException {
88 threadPool.shutdown();
91 private void connectNode() throws InterruptedException, ExecutionException, IOException {
92 fcpServer.connect().get();
93 fcpServer.collectUntil(is("EndMessage"));
94 fcpServer.writeLine("NodeHello",
95 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
96 "Revision=build01466",
98 "Version=Fred,0.7,1.0,1466",
100 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
104 "NodeLanguage=ENGLISH",
110 private String extractIdentifier(List<String> lines) {
111 return lines.stream()
112 .filter(s -> s.startsWith("Identifier="))
113 .map(s -> s.substring(s.indexOf('=') + 1))
118 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
119 return matchesFcpMessageWithTerminator(name, "EndMessage", requiredLines);
122 private Matcher<Iterable<String>> hasHead(String firstElement) {
123 return new TypeSafeDiagnosingMatcher<Iterable<String>>() {
125 protected boolean matchesSafely(Iterable<String> iterable, Description mismatchDescription) {
126 if (!iterable.iterator().hasNext()) {
127 mismatchDescription.appendText("is empty");
130 String element = iterable.iterator().next();
131 if (!element.equals(firstElement)) {
132 mismatchDescription.appendText("starts with ").appendValue(element);
139 public void describeTo(Description description) {
140 description.appendText("starts with ").appendValue(firstElement);
145 private Matcher<List<String>> matchesFcpMessageWithTerminator(
146 String name, String terminator, String... requiredLines) {
147 return allOf(hasHead(name), hasParameters(1, 1, requiredLines), hasTail(terminator));
150 private Matcher<List<String>> hasParameters(int ignoreStart, int ignoreEnd, String... lines) {
151 return new TypeSafeDiagnosingMatcher<List<String>>() {
153 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
154 if (item.size() < (ignoreStart + ignoreEnd)) {
155 mismatchDescription.appendText("has only ").appendValue(item.size()).appendText(" elements");
158 for (String line : lines) {
159 if ((item.indexOf(line) < ignoreStart) || (item.indexOf(line) >= (item.size() - ignoreEnd))) {
160 mismatchDescription.appendText("does not contains ").appendValue(line);
168 public void describeTo(Description description) {
169 description.appendText("contains ").appendValueList("(", ", ", ")", lines);
170 description.appendText(", ignoring the first ").appendValue(ignoreStart);
171 description.appendText(" and the last ").appendValue(ignoreEnd);
176 private Matcher<List<String>> hasTail(String... lastElements) {
177 return new TypeSafeDiagnosingMatcher<List<String>>() {
179 protected boolean matchesSafely(List<String> list, Description mismatchDescription) {
180 if (list.size() < lastElements.length) {
181 mismatchDescription.appendText("is too small");
184 List<String> tail = list.subList(list.size() - lastElements.length, list.size());
185 if (!tail.equals(Arrays.asList(lastElements))) {
186 mismatchDescription.appendText("ends with ").appendValueList("(", ", ", ")", tail);
193 public void describeTo(Description description) {
194 description.appendText("ends with ").appendValueList("(", ", ", ")", lastElements);
200 public void clientPutWithDirectDataSendsCorrectCommand()
201 throws IOException, ExecutionException, InterruptedException {
202 fcpClient.clientPut()
203 .from(new ByteArrayInputStream("Hello\n".getBytes()))
208 List<String> lines = fcpServer.collectUntil(is("Hello"));
209 assertThat(lines, allOf(
210 hasHead("ClientPut"),
211 hasParameters(1, 2, "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"),
212 hasTail("EndMessage", "Hello")
217 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
218 throws InterruptedException, ExecutionException, IOException {
219 Future<Optional<Key>> key = fcpClient.clientPut()
220 .from(new ByteArrayInputStream("Hello\n".getBytes()))
225 List<String> lines = fcpServer.collectUntil(is("Hello"));
226 String identifier = extractIdentifier(lines);
229 "Identifier=not-the-right-one",
235 "Identifier=" + identifier,
238 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
242 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
243 throws InterruptedException, ExecutionException, IOException {
244 Future<Optional<Key>> key = fcpClient.clientPut()
245 .from(new ByteArrayInputStream("Hello\n".getBytes()))
250 List<String> lines = fcpServer.collectUntil(is("Hello"));
251 String identifier = extractIdentifier(lines);
254 "Identifier=not-the-right-one",
260 "Identifier=" + identifier,
263 assertThat(key.get().isPresent(), is(false));
267 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
268 throws InterruptedException, ExecutionException, IOException {
269 fcpClient.clientPut()
270 .named("otherName.txt")
271 .from(new ByteArrayInputStream("Hello\n".getBytes()))
276 List<String> lines = fcpServer.collectUntil(is("Hello"));
277 assertThat(lines, allOf(
278 hasHead("ClientPut"),
279 hasParameters(1, 2, "TargetFilename=otherName.txt", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"),
280 hasTail("EndMessage", "Hello")
285 public void clientPutWithRedirectSendsCorrectCommand()
286 throws IOException, ExecutionException, InterruptedException {
287 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
289 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
291 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
295 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
296 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
298 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
300 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
304 public void clientPutWithFileCanCompleteTestDdaSequence()
305 throws IOException, ExecutionException, InterruptedException {
306 File tempFile = createTempFile();
307 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
309 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
310 String identifier = extractIdentifier(lines);
313 "Identifier=" + identifier,
317 lines = fcpServer.collectUntil(is("EndMessage"));
318 assertThat(lines, matchesFcpMessage(
320 "Directory=" + tempFile.getParent(),
321 "WantReadDirectory=true",
322 "WantWriteDirectory=false"
326 "Directory=" + tempFile.getParent(),
327 "ReadFilename=" + tempFile,
330 lines = fcpServer.collectUntil(is("EndMessage"));
331 assertThat(lines, matchesFcpMessage(
333 "Directory=" + tempFile.getParent(),
334 "ReadContent=test-content"
338 "Directory=" + tempFile.getParent(),
339 "ReadDirectoryAllowed=true",
342 lines = fcpServer.collectUntil(is("EndMessage"));
344 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
345 "Filename=" + new File(tempFile.getParent(), "test.dat")));
348 private File createTempFile() throws IOException {
349 File tempFile = File.createTempFile("test-dda-", ".dat");
350 tempFile.deleteOnExit();
351 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
356 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
357 throws InterruptedException, ExecutionException, IOException {
358 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
360 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
361 String identifier = extractIdentifier(lines);
364 "Identifier=not-the-right-one",
370 "Identifier=" + identifier,
374 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
378 public void clientPutAbortsOnProtocolErrorOtherThan25()
379 throws InterruptedException, ExecutionException, IOException {
380 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
382 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
383 String identifier = extractIdentifier(lines);
386 "Identifier=" + identifier,
390 assertThat(key.get().isPresent(), is(false));
394 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
395 InterruptedException {
396 File tempFile = createTempFile();
397 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
399 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
400 String identifier = extractIdentifier(lines);
403 "Identifier=" + identifier,
407 lines = fcpServer.collectUntil(is("EndMessage"));
408 assertThat(lines, matchesFcpMessage(
410 "Directory=" + tempFile.getParent(),
411 "WantReadDirectory=true",
412 "WantWriteDirectory=false"
416 "Directory=/some-other-directory",
417 "ReadFilename=" + tempFile,
422 "Directory=" + tempFile.getParent(),
423 "ReadFilename=" + tempFile,
426 lines = fcpServer.collectUntil(is("EndMessage"));
427 assertThat(lines, matchesFcpMessage(
429 "Directory=" + tempFile.getParent(),
430 "ReadContent=test-content"
435 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
436 throws IOException, ExecutionException, InterruptedException {
437 File tempFile = createTempFile();
438 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
440 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
441 String identifier = extractIdentifier(lines);
444 "Identifier=" + identifier,
448 lines = fcpServer.collectUntil(is("EndMessage"));
449 assertThat(lines, matchesFcpMessage(
451 "Directory=" + tempFile.getParent(),
452 "WantReadDirectory=true",
453 "WantWriteDirectory=false"
457 "Directory=" + tempFile.getParent(),
458 "ReadFilename=" + tempFile + ".foo",
461 lines = fcpServer.collectUntil(is("EndMessage"));
462 assertThat(lines, matchesFcpMessage(
464 "Directory=" + tempFile.getParent(),
465 "ReadContent=failed-to-read"
470 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
471 throws IOException, ExecutionException, InterruptedException {
472 File tempFile = createTempFile();
473 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
475 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
476 String identifier = extractIdentifier(lines);
479 "Directory=/some-other-directory",
484 "Identifier=" + identifier,
488 lines = fcpServer.collectUntil(is("EndMessage"));
489 assertThat(lines, matchesFcpMessage(
491 "Directory=" + tempFile.getParent(),
492 "WantReadDirectory=true",
493 "WantWriteDirectory=false"
498 public void clientPutSendsNotificationsForGeneratedKeys()
499 throws InterruptedException, ExecutionException, IOException {
500 List<String> generatedKeys = new CopyOnWriteArrayList<>();
501 Future<Optional<Key>> key = fcpClient.clientPut()
502 .onKeyGenerated(generatedKeys::add)
503 .from(new ByteArrayInputStream("Hello\n".getBytes()))
508 List<String> lines = fcpServer.collectUntil(is("Hello"));
509 String identifier = extractIdentifier(lines);
512 "Identifier=" + identifier,
519 "Identifier=" + identifier,
522 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
523 assertThat(generatedKeys, contains("KSK@foo.txt"));
527 public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
528 Future<NodeData> nodeData = fcpClient.getNode().execute();
530 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
531 String identifier = extractIdentifier(lines);
532 assertThat(lines, matchesFcpMessage(
534 "Identifier=" + identifier,
535 "GiveOpennetRef=false",
541 "Identifier=" + identifier,
542 "ark.pubURI=SSK@3YEf.../ark",
545 "version=Fred,0.7,1.0,1466",
546 "lastGoodVersion=Fred,0.7,1.0,1466",
549 assertThat(nodeData.get(), notNullValue());
553 public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
554 throws InterruptedException, ExecutionException, IOException {
555 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
557 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
558 String identifier = extractIdentifier(lines);
559 assertThat(lines, matchesFcpMessage(
561 "Identifier=" + identifier,
562 "GiveOpennetRef=true",
568 "Identifier=" + identifier,
570 "ark.pubURI=SSK@3YEf.../ark",
573 "version=Fred,0.7,1.0,1466",
574 "lastGoodVersion=Fred,0.7,1.0,1466",
577 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
581 public void defaultFcpClientCanGetNodeInformationWithPrivateData()
582 throws InterruptedException, ExecutionException, IOException {
583 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
585 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
586 String identifier = extractIdentifier(lines);
587 assertThat(lines, matchesFcpMessage(
589 "Identifier=" + identifier,
590 "GiveOpennetRef=false",
596 "Identifier=" + identifier,
598 "ark.pubURI=SSK@3YEf.../ark",
601 "version=Fred,0.7,1.0,1466",
602 "lastGoodVersion=Fred,0.7,1.0,1466",
603 "ark.privURI=SSK@XdHMiRl",
606 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
610 public void defaultFcpClientCanGetNodeInformationWithVolatileData()
611 throws InterruptedException, ExecutionException, IOException {
612 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
614 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
615 String identifier = extractIdentifier(lines);
616 assertThat(lines, matchesFcpMessage(
618 "Identifier=" + identifier,
619 "GiveOpennetRef=false",
625 "Identifier=" + identifier,
627 "ark.pubURI=SSK@3YEf.../ark",
630 "version=Fred,0.7,1.0,1466",
631 "lastGoodVersion=Fred,0.7,1.0,1466",
632 "volatile.freeJavaMemory=205706528",
635 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
639 public void defaultFcpClientCanGetConfigWithoutDetails()
640 throws InterruptedException, ExecutionException, IOException {
641 Future<ConfigData> configData = fcpClient.getConfig().execute();
643 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
644 String identifier = extractIdentifier(lines);
645 assertThat(lines, matchesFcpMessage(
647 "Identifier=" + identifier
651 "Identifier=" + identifier,
654 assertThat(configData.get(), notNullValue());
658 public void defaultFcpClientCanGetConfigWithCurrent()
659 throws InterruptedException, ExecutionException, IOException {
660 Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
662 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
663 String identifier = extractIdentifier(lines);
664 assertThat(lines, matchesFcpMessage(
666 "Identifier=" + identifier,
671 "Identifier=" + identifier,
675 assertThat(configData.get().getCurrent("foo"), is("bar"));
679 public void defaultFcpClientCanGetConfigWithDefaults()
680 throws InterruptedException, ExecutionException, IOException {
681 Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
683 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
684 String identifier = extractIdentifier(lines);
685 assertThat(lines, matchesFcpMessage(
687 "Identifier=" + identifier,
692 "Identifier=" + identifier,
696 assertThat(configData.get().getDefault("foo"), is("bar"));
700 public void defaultFcpClientCanGetConfigWithSortOrder()
701 throws InterruptedException, ExecutionException, IOException {
702 Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
704 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
705 String identifier = extractIdentifier(lines);
706 assertThat(lines, matchesFcpMessage(
708 "Identifier=" + identifier,
713 "Identifier=" + identifier,
717 assertThat(configData.get().getSortOrder("foo"), is(17));
721 public void defaultFcpClientCanGetConfigWithExpertFlag()
722 throws InterruptedException, ExecutionException, IOException {
723 Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
725 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
726 String identifier = extractIdentifier(lines);
727 assertThat(lines, matchesFcpMessage(
729 "Identifier=" + identifier,
730 "WithExpertFlag=true"
734 "Identifier=" + identifier,
735 "expertFlag.foo=true",
738 assertThat(configData.get().getExpertFlag("foo"), is(true));
742 public void defaultFcpClientCanGetConfigWithForceWriteFlag()
743 throws InterruptedException, ExecutionException, IOException {
744 Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
746 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
747 String identifier = extractIdentifier(lines);
748 assertThat(lines, matchesFcpMessage(
750 "Identifier=" + identifier,
751 "WithForceWriteFlag=true"
755 "Identifier=" + identifier,
756 "forceWriteFlag.foo=true",
759 assertThat(configData.get().getForceWriteFlag("foo"), is(true));
763 public void defaultFcpClientCanGetConfigWithShortDescription()
764 throws InterruptedException, ExecutionException, IOException {
765 Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
767 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
768 String identifier = extractIdentifier(lines);
769 assertThat(lines, matchesFcpMessage(
771 "Identifier=" + identifier,
772 "WithShortDescription=true"
776 "Identifier=" + identifier,
777 "shortDescription.foo=bar",
780 assertThat(configData.get().getShortDescription("foo"), is("bar"));
784 public void defaultFcpClientCanGetConfigWithLongDescription()
785 throws InterruptedException, ExecutionException, IOException {
786 Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
788 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
789 String identifier = extractIdentifier(lines);
790 assertThat(lines, matchesFcpMessage(
792 "Identifier=" + identifier,
793 "WithLongDescription=true"
797 "Identifier=" + identifier,
798 "longDescription.foo=bar",
801 assertThat(configData.get().getLongDescription("foo"), is("bar"));
805 public void defaultFcpClientCanGetConfigWithDataTypes()
806 throws InterruptedException, ExecutionException, IOException {
807 Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
809 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
810 String identifier = extractIdentifier(lines);
811 assertThat(lines, matchesFcpMessage(
813 "Identifier=" + identifier,
818 "Identifier=" + identifier,
819 "dataType.foo=number",
822 assertThat(configData.get().getDataType("foo"), is("number"));
826 public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
827 Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
829 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
830 String identifier = extractIdentifier(lines);
831 assertThat(lines, matchesFcpMessage(
833 "Identifier=" + identifier,
838 "Identifier=" + identifier,
839 "current.foo.bar=baz",
842 assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
845 private List<String> lines;
846 private String identifier;
848 private void connectAndAssert(Supplier<Matcher<List<String>>> requestMatcher)
849 throws InterruptedException, ExecutionException, IOException {
851 readMessage(requestMatcher);
854 private void readMessage(Supplier<Matcher<List<String>>> requestMatcher) throws IOException {
855 lines = fcpServer.collectUntil(is("EndMessage"));
856 identifier = extractIdentifier(lines);
857 assertThat(lines, requestMatcher.get());
860 public class ConnectionsAndKeyPairs {
862 public class Connections {
864 @Test(expected = ExecutionException.class)
865 public void throwsExceptionOnFailure() throws IOException, ExecutionException, InterruptedException {
866 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
867 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
869 "CloseConnectionDuplicateClientName",
875 @Test(expected = ExecutionException.class)
876 public void throwsExceptionIfConnectionIsClosed() throws IOException, ExecutionException, InterruptedException {
877 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
878 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
884 public void connectionIsReused() throws InterruptedException, ExecutionException, IOException {
885 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
886 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
889 keyPair = fcpClient.generateKeypair().execute();
890 readMessage(() -> matchesFcpMessage("GenerateSSK"));
891 identifier = extractIdentifier(lines);
897 public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
898 throws InterruptedException, ExecutionException, IOException {
899 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
900 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
905 } catch (ExecutionException e) {
907 keyPair = fcpClient.generateKeypair().execute();
908 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
915 public class GenerateKeyPair {
918 public void defaultFcpClientCanGenerateKeypair()
919 throws ExecutionException, InterruptedException, IOException {
920 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
921 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
923 FcpKeyPair keyPair = keyPairFuture.get();
924 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
925 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
930 private void replyWithKeyPair() throws IOException {
931 fcpServer.writeLine("SSKKeypair",
932 "InsertURI=" + INSERT_URI + "",
933 "RequestURI=" + REQUEST_URI + "",
934 "Identifier=" + identifier,
942 public class PeerCommands {
944 public class ListPeer {
947 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
948 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
949 connectAndAssert(() -> matchesListPeer("id1"));
950 replyWithPeer("id1");
951 assertThat(peer.get().get().getIdentity(), is("id1"));
955 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
956 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
957 connectAndAssert(() -> matchesListPeer("host.free.net:12345"));
958 replyWithPeer("id1");
959 assertThat(peer.get().get().getIdentity(), is("id1"));
963 public void byName() throws InterruptedException, ExecutionException, IOException {
964 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
965 connectAndAssert(() -> matchesListPeer("FriendNode"));
966 replyWithPeer("id1");
967 assertThat(peer.get().get().getIdentity(), is("id1"));
971 public void unknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
972 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
973 connectAndAssert(() -> matchesListPeer("id2"));
974 replyWithUnknownNodeIdentifier();
975 assertThat(peer.get().isPresent(), is(false));
978 private Matcher<List<String>> matchesListPeer(String nodeId) {
979 return matchesFcpMessage(
981 "Identifier=" + identifier,
982 "NodeIdentifier=" + nodeId
988 public class ListPeers {
991 public void withoutMetadataOrVolatile() throws IOException, ExecutionException, InterruptedException {
992 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
993 connectAndAssert(() -> matchesListPeers(false, false));
994 replyWithPeer("id1");
995 replyWithPeer("id2");
997 assertThat(peers.get(), hasSize(2));
998 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
999 containsInAnyOrder("id1", "id2"));
1003 public void withMetadata() throws IOException, ExecutionException, InterruptedException {
1004 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
1005 connectAndAssert(() -> matchesListPeers(false, true));
1006 replyWithPeer("id1", "metadata.foo=bar1");
1007 replyWithPeer("id2", "metadata.foo=bar2");
1008 sendEndOfPeerList();
1009 assertThat(peers.get(), hasSize(2));
1010 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
1011 containsInAnyOrder("bar1", "bar2"));
1015 public void withVolatile() throws IOException, ExecutionException, InterruptedException {
1016 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
1017 connectAndAssert(() -> matchesListPeers(true, false));
1018 replyWithPeer("id1", "volatile.foo=bar1");
1019 replyWithPeer("id2", "volatile.foo=bar2");
1020 sendEndOfPeerList();
1021 assertThat(peers.get(), hasSize(2));
1022 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
1023 containsInAnyOrder("bar1", "bar2"));
1026 private Matcher<List<String>> matchesListPeers(boolean withVolatile, boolean withMetadata) {
1027 return matchesFcpMessage(
1029 "WithVolatile=" + withVolatile,
1030 "WithMetadata=" + withMetadata
1034 private void sendEndOfPeerList() throws IOException {
1035 fcpServer.writeLine(
1037 "Identifier=" + identifier,
1044 public class AddPeer {
1047 public void fromFile() throws InterruptedException, ExecutionException, IOException {
1048 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1049 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("File=/tmp/ref.txt")));
1050 replyWithPeer("id1");
1051 assertThat(peer.get().get().getIdentity(), is("id1"));
1055 public void fromUrl() throws InterruptedException, ExecutionException, IOException {
1056 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1057 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("URL=http://node.ref/")));
1058 replyWithPeer("id1");
1059 assertThat(peer.get().get().getIdentity(), is("id1"));
1063 public void fromNodeRef() throws InterruptedException, ExecutionException, IOException {
1064 NodeRef nodeRef = createNodeRef();
1065 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1066 connectAndAssert(() -> allOf(matchesAddPeer(), Matchers.<String>hasItems(
1068 "ark.pubURI=public",
1072 "dsaGroup.q=subprime",
1073 "dsaPubKey.y=dsa-public",
1074 "physical.udp=1.2.3.4:5678",
1075 "auth.negTypes=3;5",
1078 replyWithPeer("id1");
1079 assertThat(peer.get().get().getIdentity(), is("id1"));
1082 private NodeRef createNodeRef() {
1083 NodeRef nodeRef = new NodeRef();
1084 nodeRef.setIdentity("id1");
1085 nodeRef.setName("name");
1086 nodeRef.setARK(new ARK("public", "1"));
1087 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1088 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1089 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1090 nodeRef.setDSAPublicKey("dsa-public");
1091 nodeRef.setSignature("sig");
1095 private Matcher<List<String>> matchesAddPeer() {
1096 return matchesFcpMessage(
1098 "Identifier=" + identifier
1104 public class ModifyPeer {
1107 public void defaultFcpClientCanEnablePeerByName()
1108 throws InterruptedException, ExecutionException, IOException {
1109 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("id1").execute();
1110 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1111 replyWithPeer("id1");
1112 assertThat(peer.get().get().getIdentity(), is("id1"));
1116 public void defaultFcpClientCanDisablePeerByName()
1117 throws InterruptedException, ExecutionException, IOException {
1118 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("id1").execute();
1119 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", true));
1120 replyWithPeer("id1");
1121 assertThat(peer.get().get().getIdentity(), is("id1"));
1125 public void defaultFcpClientCanEnablePeerByIdentity()
1126 throws InterruptedException, ExecutionException, IOException {
1127 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1128 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1129 replyWithPeer("id1");
1130 assertThat(peer.get().get().getIdentity(), is("id1"));
1134 public void defaultFcpClientCanEnablePeerByHostAndPort()
1135 throws InterruptedException, ExecutionException, IOException {
1136 Future<Optional<Peer>> peer =
1137 fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
1138 connectAndAssert(() -> matchesModifyPeer("1.2.3.4:5678", "IsDisabled", false));
1139 replyWithPeer("id1");
1140 assertThat(peer.get().get().getIdentity(), is("id1"));
1144 public void allowLocalAddressesOfPeer() throws InterruptedException, ExecutionException, IOException {
1145 Future<Optional<Peer>> peer =
1146 fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
1147 connectAndAssert(() -> allOf(
1148 matchesModifyPeer("id1", "AllowLocalAddresses", true),
1149 not(contains(startsWith("IsDisabled=")))
1151 replyWithPeer("id1");
1152 assertThat(peer.get().get().getIdentity(), is("id1"));
1156 public void disallowLocalAddressesOfPeer()
1157 throws InterruptedException, ExecutionException, IOException {
1158 Future<Optional<Peer>> peer =
1159 fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
1160 connectAndAssert(() -> allOf(
1161 matchesModifyPeer("id1", "AllowLocalAddresses", false),
1162 not(contains(startsWith("IsDisabled=")))
1164 replyWithPeer("id1");
1165 assertThat(peer.get().get().getIdentity(), is("id1"));
1169 public void setBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1170 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
1171 connectAndAssert(() -> allOf(
1172 matchesModifyPeer("id1", "IsBurstOnly", true),
1173 not(contains(startsWith("AllowLocalAddresses="))),
1174 not(contains(startsWith("IsDisabled=")))
1176 replyWithPeer("id1");
1177 assertThat(peer.get().get().getIdentity(), is("id1"));
1181 public void clearBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1182 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
1183 connectAndAssert(() -> allOf(
1184 matchesModifyPeer("id1", "IsBurstOnly", false),
1185 not(contains(startsWith("AllowLocalAddresses="))),
1186 not(contains(startsWith("IsDisabled=")))
1188 replyWithPeer("id1");
1189 assertThat(peer.get().get().getIdentity(), is("id1"));
1193 public void defaultFcpClientCanSetListenOnlyForPeer()
1194 throws InterruptedException, ExecutionException, IOException {
1195 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
1196 connectAndAssert(() -> allOf(
1197 matchesModifyPeer("id1", "IsListenOnly", true),
1198 not(contains(startsWith("AllowLocalAddresses="))),
1199 not(contains(startsWith("IsDisabled="))),
1200 not(contains(startsWith("IsBurstOnly=")))
1202 replyWithPeer("id1");
1203 assertThat(peer.get().get().getIdentity(), is("id1"));
1207 public void clearListenOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1208 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
1209 connectAndAssert(() -> allOf(
1210 matchesModifyPeer("id1", "IsListenOnly", false),
1211 not(contains(startsWith("AllowLocalAddresses="))),
1212 not(contains(startsWith("IsDisabled="))),
1213 not(contains(startsWith("IsBurstOnly=")))
1215 replyWithPeer("id1");
1216 assertThat(peer.get().get().getIdentity(), is("id1"));
1220 public void ignoreSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1221 Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
1222 connectAndAssert(() -> allOf(
1223 matchesModifyPeer("id1", "IgnoreSourcePort", true),
1224 not(contains(startsWith("AllowLocalAddresses="))),
1225 not(contains(startsWith("IsDisabled="))),
1226 not(contains(startsWith("IsBurstOnly="))),
1227 not(contains(startsWith("IsListenOnly=")))
1229 replyWithPeer("id1");
1230 assertThat(peer.get().get().getIdentity(), is("id1"));
1234 public void useSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1235 Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
1236 connectAndAssert(() -> allOf(
1237 matchesModifyPeer("id1", "IgnoreSourcePort", false),
1238 not(contains(startsWith("AllowLocalAddresses="))),
1239 not(contains(startsWith("IsDisabled="))),
1240 not(contains(startsWith("IsBurstOnly="))),
1241 not(contains(startsWith("IsListenOnly=")))
1243 replyWithPeer("id1");
1244 assertThat(peer.get().get().getIdentity(), is("id1"));
1248 public void unknownNode() throws InterruptedException, ExecutionException, IOException {
1249 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1250 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1251 replyWithUnknownNodeIdentifier();
1252 assertThat(peer.get().isPresent(), is(false));
1255 private Matcher<List<String>> matchesModifyPeer(String nodeIdentifier, String setting, boolean value) {
1256 return matchesFcpMessage(
1258 "Identifier=" + identifier,
1259 "NodeIdentifier=" + nodeIdentifier,
1260 setting + "=" + value
1266 public class RemovePeer {
1269 public void byName() throws InterruptedException, ExecutionException, IOException {
1270 Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
1271 connectAndAssert(() -> matchesRemovePeer("Friend1"));
1272 replyWithPeerRemoved("Friend1");
1273 assertThat(peer.get(), is(true));
1277 public void invalidName() throws InterruptedException, ExecutionException, IOException {
1278 Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
1279 connectAndAssert(() -> matchesRemovePeer("NotFriend1"));
1280 replyWithUnknownNodeIdentifier();
1281 assertThat(peer.get(), is(false));
1285 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
1286 Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
1287 connectAndAssert(() -> matchesRemovePeer("id1"));
1288 replyWithPeerRemoved("id1");
1289 assertThat(peer.get(), is(true));
1293 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1294 Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
1295 connectAndAssert(() -> matchesRemovePeer("1.2.3.4:5678"));
1296 replyWithPeerRemoved("Friend1");
1297 assertThat(peer.get(), is(true));
1300 private Matcher<List<String>> matchesRemovePeer(String nodeIdentifier) {
1301 return matchesFcpMessage(
1303 "Identifier=" + identifier,
1304 "NodeIdentifier=" + nodeIdentifier
1308 private void replyWithPeerRemoved(String nodeIdentifier) throws IOException {
1309 fcpServer.writeLine(
1311 "Identifier=" + identifier,
1312 "NodeIdentifier=" + nodeIdentifier,
1319 private void replyWithPeer(String peerId, String... additionalLines) throws IOException {
1320 fcpServer.writeLine(
1322 "Identifier=" + identifier,
1323 "identity=" + peerId,
1325 "ark.pubURI=SSK@3YEf.../ark",
1328 "version=Fred,0.7,1.0,1466",
1329 "lastGoodVersion=Fred,0.7,1.0,1466"
1331 fcpServer.writeLine(additionalLines);
1332 fcpServer.writeLine("EndMessage");
1337 public class PeerNoteCommands {
1339 public class ListPeerNotes {
1342 public void onUnknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1343 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1344 connectAndAssert(() -> matchesListPeerNotes("Friend1"));
1345 replyWithUnknownNodeIdentifier();
1346 assertThat(peerNote.get().isPresent(), is(false));
1350 public void byNodeName() throws InterruptedException, ExecutionException, IOException {
1351 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1352 connectAndAssert(() -> matchesListPeerNotes("Friend1"));
1353 replyWithPeerNote();
1354 replyWithEndListPeerNotes();
1355 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1356 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1360 public void byNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1361 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
1362 connectAndAssert(() -> matchesListPeerNotes("id1"));
1363 replyWithPeerNote();
1364 replyWithEndListPeerNotes();
1365 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1366 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1370 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1371 Future<Optional<PeerNote>> peerNote =
1372 fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
1373 connectAndAssert(() -> matchesListPeerNotes("1.2.3.4:5678"));
1374 replyWithPeerNote();
1375 replyWithEndListPeerNotes();
1376 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1377 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1380 private Matcher<List<String>> matchesListPeerNotes(String nodeIdentifier) {
1381 return matchesFcpMessage(
1383 "NodeIdentifier=" + nodeIdentifier
1387 private void replyWithEndListPeerNotes() throws IOException {
1388 fcpServer.writeLine(
1390 "Identifier=" + identifier,
1395 private void replyWithPeerNote() throws IOException {
1396 fcpServer.writeLine(
1398 "Identifier=" + identifier,
1399 "NodeIdentifier=Friend1",
1400 "NoteText=RXhhbXBsZSBUZXh0Lg==",
1408 public class ModifyPeerNotes {
1411 public void byName() throws InterruptedException, ExecutionException, IOException {
1412 Future<Boolean> noteUpdated =
1413 fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1414 connectAndAssert(() -> matchesModifyPeerNote("Friend1"));
1415 replyWithPeerNote();
1416 assertThat(noteUpdated.get(), is(true));
1420 public void onUnknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1421 Future<Boolean> noteUpdated =
1422 fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1423 connectAndAssert(() -> matchesModifyPeerNote("Friend1"));
1424 replyWithUnknownNodeIdentifier();
1425 assertThat(noteUpdated.get(), is(false));
1429 public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
1430 throws InterruptedException, ExecutionException, IOException {
1431 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
1432 assertThat(noteUpdated.get(), is(false));
1436 public void byIdentifier() throws InterruptedException, ExecutionException, IOException {
1437 Future<Boolean> noteUpdated =
1438 fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
1439 connectAndAssert(() -> matchesModifyPeerNote("id1"));
1440 replyWithPeerNote();
1441 assertThat(noteUpdated.get(), is(true));
1445 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1446 Future<Boolean> noteUpdated =
1447 fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
1448 connectAndAssert(() -> matchesModifyPeerNote("1.2.3.4:5678"));
1449 replyWithPeerNote();
1450 assertThat(noteUpdated.get(), is(true));
1453 private Matcher<List<String>> matchesModifyPeerNote(String nodeIdentifier) {
1454 return matchesFcpMessage(
1456 "Identifier=" + identifier,
1457 "NodeIdentifier=" + nodeIdentifier,
1463 private void replyWithPeerNote() throws IOException {
1464 fcpServer.writeLine(
1466 "Identifier=" + identifier,
1467 "NodeIdentifier=Friend1",
1478 private void replyWithUnknownNodeIdentifier() throws IOException {
1479 fcpServer.writeLine(
1480 "UnknownNodeIdentifier",
1481 "Identifier=" + identifier,
1482 "NodeIdentifier=id2",
1489 public class PluginCommands {
1491 private static final String CLASS_NAME = "foo.plugin.Plugin";
1493 private void replyWithPluginInfo() throws IOException {
1494 fcpServer.writeLine(
1496 "Identifier=" + identifier,
1497 "PluginName=superPlugin",
1499 "LongVersion=1.2.3",
1501 "OriginUri=superPlugin",
1507 private void verifyPluginInfo(Future<Optional<PluginInfo>> pluginInfo)
1508 throws InterruptedException, ExecutionException {
1509 assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
1510 assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
1511 assertThat(pluginInfo.get().get().isTalkable(), is(true));
1512 assertThat(pluginInfo.get().get().getVersion(), is("42"));
1513 assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
1514 assertThat(pluginInfo.get().get().isStarted(), is(true));
1517 public class LoadPlugin {
1519 public class OfficialPlugins {
1522 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1523 Future<Optional<PluginInfo>> pluginInfo =
1524 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1525 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1526 assertThat(lines, not(contains(startsWith("Store="))));
1527 replyWithPluginInfo();
1528 verifyPluginInfo(pluginInfo);
1532 public void persistentFromFreenet() throws ExecutionException, InterruptedException, IOException {
1533 Future<Optional<PluginInfo>> pluginInfo =
1534 fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
1535 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1536 assertThat(lines, hasItem("Store=true"));
1537 replyWithPluginInfo();
1538 verifyPluginInfo(pluginInfo);
1542 public void fromHttps() throws ExecutionException, InterruptedException, IOException {
1543 Future<Optional<PluginInfo>> pluginInfo =
1544 fcpClient.loadPlugin().officialFromHttps("superPlugin").execute();
1545 connectAndAssert(() -> createMatcherForOfficialSource("https"));
1546 replyWithPluginInfo();
1547 verifyPluginInfo(pluginInfo);
1550 private Matcher<List<String>> createMatcherForOfficialSource(String officialSource) {
1551 return matchesFcpMessage(
1553 "Identifier=" + identifier,
1554 "PluginURL=superPlugin",
1556 "OfficialSource=" + officialSource
1562 public class FromOtherSources {
1564 private static final String FILE_PATH = "/path/to/plugin.jar";
1565 private static final String URL = "http://server.com/plugin.jar";
1566 private static final String KEY = "KSK@plugin.jar";
1569 public void fromFile() throws ExecutionException, InterruptedException, IOException {
1570 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFile(FILE_PATH).execute();
1571 connectAndAssert(() -> createMatcher("file", FILE_PATH));
1572 replyWithPluginInfo();
1573 verifyPluginInfo(pluginInfo);
1577 public void fromUrl() throws ExecutionException, InterruptedException, IOException {
1578 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromUrl(URL).execute();
1579 connectAndAssert(() -> createMatcher("url", URL));
1580 replyWithPluginInfo();
1581 verifyPluginInfo(pluginInfo);
1585 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1586 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFreenet(KEY).execute();
1587 connectAndAssert(() -> createMatcher("freenet", KEY));
1588 replyWithPluginInfo();
1589 verifyPluginInfo(pluginInfo);
1592 private Matcher<List<String>> createMatcher(String urlType, String url) {
1593 return matchesFcpMessage(
1595 "Identifier=" + identifier,
1597 "URLType=" + urlType
1603 public class Failed {
1606 public void failedLoad() throws ExecutionException, InterruptedException, IOException {
1607 Future<Optional<PluginInfo>> pluginInfo =
1608 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1609 connectAndAssert(() -> matchesFcpMessage("LoadPlugin"));
1610 replyWithProtocolError();
1611 assertThat(pluginInfo.get().isPresent(), is(false));
1618 private void replyWithProtocolError() throws IOException {
1619 fcpServer.writeLine(
1621 "Identifier=" + identifier,
1626 public class ReloadPlugin {
1629 public void reloadingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1630 Future<Optional<PluginInfo>> pluginInfo = fcpClient.reloadPlugin().plugin(CLASS_NAME).execute();
1631 connectAndAssert(() -> matchReloadPluginMessage());
1632 replyWithPluginInfo();
1633 verifyPluginInfo(pluginInfo);
1637 public void reloadingPluginWithMaxWaitTimeWorks()
1638 throws InterruptedException, ExecutionException, IOException {
1639 Future<Optional<PluginInfo>> pluginInfo =
1640 fcpClient.reloadPlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1641 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("MaxWaitTime=1234")));
1642 replyWithPluginInfo();
1643 verifyPluginInfo(pluginInfo);
1647 public void reloadingPluginWithPurgeWorks()
1648 throws InterruptedException, ExecutionException, IOException {
1649 Future<Optional<PluginInfo>> pluginInfo =
1650 fcpClient.reloadPlugin().purge().plugin(CLASS_NAME).execute();
1651 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Purge=true")));
1652 replyWithPluginInfo();
1653 verifyPluginInfo(pluginInfo);
1657 public void reloadingPluginWithStoreWorks()
1658 throws InterruptedException, ExecutionException, IOException {
1659 Future<Optional<PluginInfo>> pluginInfo =
1660 fcpClient.reloadPlugin().addToConfig().plugin(CLASS_NAME).execute();
1661 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Store=true")));
1662 replyWithPluginInfo();
1663 verifyPluginInfo(pluginInfo);
1666 private Matcher<List<String>> matchReloadPluginMessage() {
1667 return matchesFcpMessage(
1669 "Identifier=" + identifier,
1670 "PluginName=" + CLASS_NAME
1676 public class RemovePlugin {
1679 public void removingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1680 Future<Boolean> pluginRemoved = fcpClient.removePlugin().plugin(CLASS_NAME).execute();
1681 connectAndAssert(() -> matchPluginRemovedMessage());
1682 replyWithPluginRemoved();
1683 assertThat(pluginRemoved.get(), is(true));
1687 public void removingPluginWithMaxWaitTimeWorks()
1688 throws InterruptedException, ExecutionException, IOException {
1689 Future<Boolean> pluginRemoved = fcpClient.removePlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1690 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("MaxWaitTime=1234")));
1691 replyWithPluginRemoved();
1692 assertThat(pluginRemoved.get(), is(true));
1696 public void removingPluginWithPurgeWorks()
1697 throws InterruptedException, ExecutionException, IOException {
1698 Future<Boolean> pluginRemoved = fcpClient.removePlugin().purge().plugin(CLASS_NAME).execute();
1699 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("Purge=true")));
1700 replyWithPluginRemoved();
1701 assertThat(pluginRemoved.get(), is(true));
1704 private void replyWithPluginRemoved() throws IOException {
1705 fcpServer.writeLine(
1707 "Identifier=" + identifier,
1708 "PluginName=" + CLASS_NAME,
1713 private Matcher<List<String>> matchPluginRemovedMessage() {
1714 return matchesFcpMessage(
1716 "Identifier=" + identifier,
1717 "PluginName=" + CLASS_NAME
1723 public class GetPluginInfo {
1726 public void gettingPluginInfoWorks() throws InterruptedException, ExecutionException, IOException {
1727 Future<Optional<PluginInfo>> pluginInfo = fcpClient.getPluginInfo().plugin(CLASS_NAME).execute();
1728 connectAndAssert(() -> matchGetPluginInfoMessage());
1729 replyWithPluginInfo();
1730 verifyPluginInfo(pluginInfo);
1734 public void gettingPluginInfoWithDetailsWorks()
1735 throws InterruptedException, ExecutionException, IOException {
1736 Future<Optional<PluginInfo>> pluginInfo =
1737 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1738 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1739 replyWithPluginInfo();
1740 verifyPluginInfo(pluginInfo);
1744 public void protocolErrorIsRecognizedAsFailure()
1745 throws InterruptedException, ExecutionException, IOException {
1746 Future<Optional<PluginInfo>> pluginInfo =
1747 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1748 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1749 replyWithProtocolError();
1750 assertThat(pluginInfo.get(), is(Optional.empty()));
1753 private Matcher<List<String>> matchGetPluginInfoMessage() {
1754 return matchesFcpMessage(
1756 "Identifier=" + identifier,
1757 "PluginName=" + CLASS_NAME
1765 public class UskSubscriptionCommands {
1767 private static final String URI = "USK@some,uri/file.txt";
1770 public void subscriptionWorks() throws InterruptedException, ExecutionException, IOException {
1771 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1772 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI));
1773 replyWithSubscribed();
1774 assertThat(uskSubscription.get().get().getUri(), is(URI));
1775 AtomicInteger edition = new AtomicInteger();
1776 CountDownLatch updated = new CountDownLatch(2);
1777 uskSubscription.get().get().onUpdate(e -> {
1779 updated.countDown();
1781 sendUpdateNotification(23);
1782 sendUpdateNotification(24);
1783 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1784 assertThat(edition.get(), is(24));
1788 public void subscriptionUpdatesMultipleTimes() throws InterruptedException, ExecutionException, IOException {
1789 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1790 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI));
1791 replyWithSubscribed();
1792 assertThat(uskSubscription.get().get().getUri(), is(URI));
1793 AtomicInteger edition = new AtomicInteger();
1794 CountDownLatch updated = new CountDownLatch(2);
1795 uskSubscription.get().get().onUpdate(e -> {
1797 updated.countDown();
1799 uskSubscription.get().get().onUpdate(e -> updated.countDown());
1800 sendUpdateNotification(23);
1801 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1802 assertThat(edition.get(), is(23));
1806 public void subscriptionCanBeCancelled() throws InterruptedException, ExecutionException, IOException {
1807 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1808 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI));
1809 replyWithSubscribed();
1810 assertThat(uskSubscription.get().get().getUri(), is(URI));
1811 AtomicBoolean updated = new AtomicBoolean();
1812 uskSubscription.get().get().onUpdate(e -> updated.set(true));
1813 uskSubscription.get().get().cancel();
1814 readMessage(() -> matchesFcpMessage("UnsubscribeUSK", "Identifier=" + identifier));
1815 sendUpdateNotification(23);
1816 assertThat(updated.get(), is(false));
1819 private void replyWithSubscribed() throws IOException {
1820 fcpServer.writeLine(
1822 "Identifier=" + identifier,
1829 private void sendUpdateNotification(int edition, String... additionalLines) throws IOException {
1830 fcpServer.writeLine(
1831 "SubscribedUSKUpdate",
1832 "Identifier=" + identifier,
1834 "Edition=" + edition
1836 fcpServer.writeLine(additionalLines);
1837 fcpServer.writeLine("EndMessage");
1842 public class ClientGet {
1845 public void works() throws InterruptedException, ExecutionException, IOException {
1846 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1847 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "ReturnType=direct"));
1848 replyWithAllData("not-test", "Hello World", "text/plain;charset=latin-9");
1849 replyWithAllData(identifier, "Hello", "text/plain;charset=utf-8");
1850 Optional<Data> data = dataFuture.get();
1855 public void getFailedIsRecognized() throws InterruptedException, ExecutionException, IOException {
1856 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1857 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1858 replyWithGetFailed("not-test");
1859 replyWithGetFailed(identifier);
1860 Optional<Data> data = dataFuture.get();
1861 assertThat(data.isPresent(), is(false));
1865 public void getFailedForDifferentIdentifierIsIgnored()
1866 throws InterruptedException, ExecutionException, IOException {
1867 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1868 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1869 replyWithGetFailed("not-test");
1870 replyWithAllData(identifier, "Hello", "text/plain;charset=utf-8");
1871 Optional<Data> data = dataFuture.get();
1875 @Test(expected = ExecutionException.class)
1876 public void connectionClosedIsRecognized() throws InterruptedException, ExecutionException, IOException {
1877 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1878 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1884 public void withIgnoreData() throws InterruptedException, ExecutionException, IOException {
1885 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
1886 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
1890 public void withDataStoreOnly() throws InterruptedException, ExecutionException, IOException {
1891 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
1892 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
1896 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
1897 throws InterruptedException, ExecutionException, IOException {
1898 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
1899 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
1903 public void clientGetWithPrioritySettingSendsCorrectCommands()
1904 throws InterruptedException, ExecutionException, IOException {
1905 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
1906 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
1910 public void clientGetWithRealTimeSettingSendsCorrectCommands()
1911 throws InterruptedException, ExecutionException, IOException {
1912 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
1913 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
1917 public void clientGetWithGlobalSettingSendsCorrectCommands()
1918 throws InterruptedException, ExecutionException, IOException {
1919 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
1920 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
1923 private void replyWithGetFailed(String identifier) throws IOException {
1924 fcpServer.writeLine(
1926 "Identifier=" + identifier,
1932 private void replyWithAllData(String identifier, String text, String contentType) throws IOException {
1933 fcpServer.writeLine(
1935 "Identifier=" + identifier,
1936 "DataLength=" + (text.length() + 1),
1937 "StartupTime=1435610539000",
1938 "CompletionTime=1435610540000",
1939 "Metadata.ContentType=" + contentType,
1945 private void verifyData(Optional<Data> data) throws IOException {
1946 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
1947 assertThat(data.get().size(), is(6L));
1948 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
1949 is("Hello\n".getBytes(StandardCharsets.UTF_8)));