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",
111 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
112 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
114 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
115 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
116 String identifier = extractIdentifier(lines);
119 "Identifier=" + identifier,
121 "StartupTime=1435610539000",
122 "CompletionTime=1435610540000",
123 "Metadata.ContentType=text/plain;charset=utf-8",
127 Optional<Data> data = dataFuture.get();
128 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
129 assertThat(data.get().size(), is(6L));
130 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
131 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
134 private String extractIdentifier(List<String> lines) {
135 return lines.stream()
136 .filter(s -> s.startsWith("Identifier="))
137 .map(s -> s.substring(s.indexOf('=') + 1))
143 public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
144 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
146 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
147 String identifier = extractIdentifier(lines);
150 "InsertURI=" + INSERT_URI + "",
151 "RequestURI=" + REQUEST_URI + "",
152 "Identifier=" + identifier,
156 keyPair = fcpClient.generateKeypair().execute();
157 lines = fcpServer.collectUntil(is("EndMessage"));
158 identifier = extractIdentifier(lines);
161 "InsertURI=" + INSERT_URI + "",
162 "RequestURI=" + REQUEST_URI + "",
163 "Identifier=" + identifier,
170 public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
171 throws InterruptedException, ExecutionException, IOException {
172 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
174 fcpServer.collectUntil(is("EndMessage"));
179 } catch (ExecutionException e) {
181 keyPair = fcpClient.generateKeypair().execute();
183 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
184 String identifier = extractIdentifier(lines);
187 "InsertURI=" + INSERT_URI + "",
188 "RequestURI=" + REQUEST_URI + "",
189 "Identifier=" + identifier,
195 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
196 return matchesFcpMessageWithTerminator(name, "EndMessage", requiredLines);
199 private Matcher<List<String>> matchesDataMessage(String name, String... requiredLines) {
200 return matchesFcpMessageWithTerminator(name, "Data", requiredLines);
203 private Matcher<Iterable<String>> hasHead(String firstElement) {
204 return new TypeSafeDiagnosingMatcher<Iterable<String>>() {
206 protected boolean matchesSafely(Iterable<String> iterable, Description mismatchDescription) {
207 if (!iterable.iterator().hasNext()) {
208 mismatchDescription.appendText("is empty");
211 String element = iterable.iterator().next();
212 if (!element.equals(firstElement)) {
213 mismatchDescription.appendText("starts with ").appendValue(element);
220 public void describeTo(Description description) {
221 description.appendText("starts with ").appendValue(firstElement);
226 private Matcher<List<String>> matchesFcpMessageWithTerminator(
227 String name, String terminator, String... requiredLines) {
228 return allOf(hasHead(name), hasParameters(1, 1, requiredLines), hasTail(terminator));
231 private Matcher<List<String>> hasParameters(int ignoreStart, int ignoreEnd, String... lines) {
232 return new TypeSafeDiagnosingMatcher<List<String>>() {
234 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
235 if (item.size() < (ignoreStart + ignoreEnd)) {
236 mismatchDescription.appendText("has only ").appendValue(item.size()).appendText(" elements");
239 for (String line : lines) {
240 if ((item.indexOf(line) < ignoreStart) || (item.indexOf(line) >= (item.size() - ignoreEnd))) {
241 mismatchDescription.appendText("does not contains ").appendValue(line);
249 public void describeTo(Description description) {
250 description.appendText("contains ").appendValueList("(", ", ", ")", lines);
251 description.appendText(", ignoring the first ").appendValue(ignoreStart);
252 description.appendText(" and the last ").appendValue(ignoreEnd);
257 private Matcher<List<String>> hasTail(String... lastElements) {
258 return new TypeSafeDiagnosingMatcher<List<String>>() {
260 protected boolean matchesSafely(List<String> list, Description mismatchDescription) {
261 if (list.size() < lastElements.length) {
262 mismatchDescription.appendText("is too small");
265 List<String> tail = list.subList(list.size() - lastElements.length, list.size());
266 if (!tail.equals(Arrays.asList(lastElements))) {
267 mismatchDescription.appendText("ends with ").appendValueList("(", ", ", ")", tail);
274 public void describeTo(Description description) {
275 description.appendText("ends with ").appendValueList("(", ", ", ")", lastElements);
281 public void clientPutWithDirectDataSendsCorrectCommand()
282 throws IOException, ExecutionException, InterruptedException {
283 fcpClient.clientPut()
284 .from(new ByteArrayInputStream("Hello\n".getBytes()))
289 List<String> lines = fcpServer.collectUntil(is("Hello"));
290 assertThat(lines, allOf(
291 hasHead("ClientPut"),
292 hasParameters(1, 2, "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"),
293 hasTail("EndMessage", "Hello")
298 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
299 throws InterruptedException, ExecutionException, IOException {
300 Future<Optional<Key>> key = fcpClient.clientPut()
301 .from(new ByteArrayInputStream("Hello\n".getBytes()))
306 List<String> lines = fcpServer.collectUntil(is("Hello"));
307 String identifier = extractIdentifier(lines);
310 "Identifier=not-the-right-one",
316 "Identifier=" + identifier,
319 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
323 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
324 throws InterruptedException, ExecutionException, IOException {
325 Future<Optional<Key>> key = fcpClient.clientPut()
326 .from(new ByteArrayInputStream("Hello\n".getBytes()))
331 List<String> lines = fcpServer.collectUntil(is("Hello"));
332 String identifier = extractIdentifier(lines);
335 "Identifier=not-the-right-one",
341 "Identifier=" + identifier,
344 assertThat(key.get().isPresent(), is(false));
348 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
349 throws InterruptedException, ExecutionException, IOException {
350 fcpClient.clientPut()
351 .named("otherName.txt")
352 .from(new ByteArrayInputStream("Hello\n".getBytes()))
357 List<String> lines = fcpServer.collectUntil(is("Hello"));
358 assertThat(lines, allOf(
359 hasHead("ClientPut"),
360 hasParameters(1, 2, "TargetFilename=otherName.txt", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"),
361 hasTail("EndMessage", "Hello")
366 public void clientPutWithRedirectSendsCorrectCommand()
367 throws IOException, ExecutionException, InterruptedException {
368 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
370 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
372 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
376 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
377 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
379 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
381 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
385 public void clientPutWithFileCanCompleteTestDdaSequence()
386 throws IOException, ExecutionException, InterruptedException {
387 File tempFile = createTempFile();
388 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
390 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
391 String identifier = extractIdentifier(lines);
394 "Identifier=" + identifier,
398 lines = fcpServer.collectUntil(is("EndMessage"));
399 assertThat(lines, matchesFcpMessage(
401 "Directory=" + tempFile.getParent(),
402 "WantReadDirectory=true",
403 "WantWriteDirectory=false"
407 "Directory=" + tempFile.getParent(),
408 "ReadFilename=" + tempFile,
411 lines = fcpServer.collectUntil(is("EndMessage"));
412 assertThat(lines, matchesFcpMessage(
414 "Directory=" + tempFile.getParent(),
415 "ReadContent=test-content"
419 "Directory=" + tempFile.getParent(),
420 "ReadDirectoryAllowed=true",
423 lines = fcpServer.collectUntil(is("EndMessage"));
425 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
426 "Filename=" + new File(tempFile.getParent(), "test.dat")));
429 private File createTempFile() throws IOException {
430 File tempFile = File.createTempFile("test-dda-", ".dat");
431 tempFile.deleteOnExit();
432 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
437 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
438 throws InterruptedException, ExecutionException, IOException {
439 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
441 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
442 String identifier = extractIdentifier(lines);
445 "Identifier=not-the-right-one",
451 "Identifier=" + identifier,
455 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
459 public void clientPutAbortsOnProtocolErrorOtherThan25()
460 throws InterruptedException, ExecutionException, IOException {
461 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
463 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
464 String identifier = extractIdentifier(lines);
467 "Identifier=" + identifier,
471 assertThat(key.get().isPresent(), is(false));
475 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
476 InterruptedException {
477 File tempFile = createTempFile();
478 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
480 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
481 String identifier = extractIdentifier(lines);
484 "Identifier=" + identifier,
488 lines = fcpServer.collectUntil(is("EndMessage"));
489 assertThat(lines, matchesFcpMessage(
491 "Directory=" + tempFile.getParent(),
492 "WantReadDirectory=true",
493 "WantWriteDirectory=false"
497 "Directory=/some-other-directory",
498 "ReadFilename=" + tempFile,
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 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
517 throws IOException, ExecutionException, InterruptedException {
518 File tempFile = createTempFile();
519 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
521 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
522 String identifier = extractIdentifier(lines);
525 "Identifier=" + identifier,
529 lines = fcpServer.collectUntil(is("EndMessage"));
530 assertThat(lines, matchesFcpMessage(
532 "Directory=" + tempFile.getParent(),
533 "WantReadDirectory=true",
534 "WantWriteDirectory=false"
538 "Directory=" + tempFile.getParent(),
539 "ReadFilename=" + tempFile + ".foo",
542 lines = fcpServer.collectUntil(is("EndMessage"));
543 assertThat(lines, matchesFcpMessage(
545 "Directory=" + tempFile.getParent(),
546 "ReadContent=failed-to-read"
551 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
552 throws IOException, ExecutionException, InterruptedException {
553 File tempFile = createTempFile();
554 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
556 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
557 String identifier = extractIdentifier(lines);
560 "Directory=/some-other-directory",
565 "Identifier=" + identifier,
569 lines = fcpServer.collectUntil(is("EndMessage"));
570 assertThat(lines, matchesFcpMessage(
572 "Directory=" + tempFile.getParent(),
573 "WantReadDirectory=true",
574 "WantWriteDirectory=false"
579 public void clientPutSendsNotificationsForGeneratedKeys()
580 throws InterruptedException, ExecutionException, IOException {
581 List<String> generatedKeys = new CopyOnWriteArrayList<>();
582 Future<Optional<Key>> key = fcpClient.clientPut()
583 .onKeyGenerated(generatedKeys::add)
584 .from(new ByteArrayInputStream("Hello\n".getBytes()))
589 List<String> lines = fcpServer.collectUntil(is("Hello"));
590 String identifier = extractIdentifier(lines);
593 "Identifier=" + identifier,
600 "Identifier=" + identifier,
603 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
604 assertThat(generatedKeys, contains("KSK@foo.txt"));
608 public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
609 Future<NodeData> nodeData = fcpClient.getNode().execute();
611 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
612 String identifier = extractIdentifier(lines);
613 assertThat(lines, matchesFcpMessage(
615 "Identifier=" + identifier,
616 "GiveOpennetRef=false",
622 "Identifier=" + identifier,
623 "ark.pubURI=SSK@3YEf.../ark",
626 "version=Fred,0.7,1.0,1466",
627 "lastGoodVersion=Fred,0.7,1.0,1466",
630 assertThat(nodeData.get(), notNullValue());
634 public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
635 throws InterruptedException, ExecutionException, IOException {
636 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
638 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
639 String identifier = extractIdentifier(lines);
640 assertThat(lines, matchesFcpMessage(
642 "Identifier=" + identifier,
643 "GiveOpennetRef=true",
649 "Identifier=" + identifier,
651 "ark.pubURI=SSK@3YEf.../ark",
654 "version=Fred,0.7,1.0,1466",
655 "lastGoodVersion=Fred,0.7,1.0,1466",
658 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
662 public void defaultFcpClientCanGetNodeInformationWithPrivateData()
663 throws InterruptedException, ExecutionException, IOException {
664 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
666 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
667 String identifier = extractIdentifier(lines);
668 assertThat(lines, matchesFcpMessage(
670 "Identifier=" + identifier,
671 "GiveOpennetRef=false",
677 "Identifier=" + identifier,
679 "ark.pubURI=SSK@3YEf.../ark",
682 "version=Fred,0.7,1.0,1466",
683 "lastGoodVersion=Fred,0.7,1.0,1466",
684 "ark.privURI=SSK@XdHMiRl",
687 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
691 public void defaultFcpClientCanGetNodeInformationWithVolatileData()
692 throws InterruptedException, ExecutionException, IOException {
693 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
695 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
696 String identifier = extractIdentifier(lines);
697 assertThat(lines, matchesFcpMessage(
699 "Identifier=" + identifier,
700 "GiveOpennetRef=false",
706 "Identifier=" + identifier,
708 "ark.pubURI=SSK@3YEf.../ark",
711 "version=Fred,0.7,1.0,1466",
712 "lastGoodVersion=Fred,0.7,1.0,1466",
713 "volatile.freeJavaMemory=205706528",
716 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
720 public void defaultFcpClientCanGetConfigWithoutDetails()
721 throws InterruptedException, ExecutionException, IOException {
722 Future<ConfigData> configData = fcpClient.getConfig().execute();
724 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
725 String identifier = extractIdentifier(lines);
726 assertThat(lines, matchesFcpMessage(
728 "Identifier=" + identifier
732 "Identifier=" + identifier,
735 assertThat(configData.get(), notNullValue());
739 public void defaultFcpClientCanGetConfigWithCurrent()
740 throws InterruptedException, ExecutionException, IOException {
741 Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
743 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
744 String identifier = extractIdentifier(lines);
745 assertThat(lines, matchesFcpMessage(
747 "Identifier=" + identifier,
752 "Identifier=" + identifier,
756 assertThat(configData.get().getCurrent("foo"), is("bar"));
760 public void defaultFcpClientCanGetConfigWithDefaults()
761 throws InterruptedException, ExecutionException, IOException {
762 Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
764 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
765 String identifier = extractIdentifier(lines);
766 assertThat(lines, matchesFcpMessage(
768 "Identifier=" + identifier,
773 "Identifier=" + identifier,
777 assertThat(configData.get().getDefault("foo"), is("bar"));
781 public void defaultFcpClientCanGetConfigWithSortOrder()
782 throws InterruptedException, ExecutionException, IOException {
783 Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
785 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
786 String identifier = extractIdentifier(lines);
787 assertThat(lines, matchesFcpMessage(
789 "Identifier=" + identifier,
794 "Identifier=" + identifier,
798 assertThat(configData.get().getSortOrder("foo"), is(17));
802 public void defaultFcpClientCanGetConfigWithExpertFlag()
803 throws InterruptedException, ExecutionException, IOException {
804 Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
806 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
807 String identifier = extractIdentifier(lines);
808 assertThat(lines, matchesFcpMessage(
810 "Identifier=" + identifier,
811 "WithExpertFlag=true"
815 "Identifier=" + identifier,
816 "expertFlag.foo=true",
819 assertThat(configData.get().getExpertFlag("foo"), is(true));
823 public void defaultFcpClientCanGetConfigWithForceWriteFlag()
824 throws InterruptedException, ExecutionException, IOException {
825 Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
827 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
828 String identifier = extractIdentifier(lines);
829 assertThat(lines, matchesFcpMessage(
831 "Identifier=" + identifier,
832 "WithForceWriteFlag=true"
836 "Identifier=" + identifier,
837 "forceWriteFlag.foo=true",
840 assertThat(configData.get().getForceWriteFlag("foo"), is(true));
844 public void defaultFcpClientCanGetConfigWithShortDescription()
845 throws InterruptedException, ExecutionException, IOException {
846 Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
848 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
849 String identifier = extractIdentifier(lines);
850 assertThat(lines, matchesFcpMessage(
852 "Identifier=" + identifier,
853 "WithShortDescription=true"
857 "Identifier=" + identifier,
858 "shortDescription.foo=bar",
861 assertThat(configData.get().getShortDescription("foo"), is("bar"));
865 public void defaultFcpClientCanGetConfigWithLongDescription()
866 throws InterruptedException, ExecutionException, IOException {
867 Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
869 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
870 String identifier = extractIdentifier(lines);
871 assertThat(lines, matchesFcpMessage(
873 "Identifier=" + identifier,
874 "WithLongDescription=true"
878 "Identifier=" + identifier,
879 "longDescription.foo=bar",
882 assertThat(configData.get().getLongDescription("foo"), is("bar"));
886 public void defaultFcpClientCanGetConfigWithDataTypes()
887 throws InterruptedException, ExecutionException, IOException {
888 Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
890 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
891 String identifier = extractIdentifier(lines);
892 assertThat(lines, matchesFcpMessage(
894 "Identifier=" + identifier,
899 "Identifier=" + identifier,
900 "dataType.foo=number",
903 assertThat(configData.get().getDataType("foo"), is("number"));
907 public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
908 Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
910 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
911 String identifier = extractIdentifier(lines);
912 assertThat(lines, matchesFcpMessage(
914 "Identifier=" + identifier,
919 "Identifier=" + identifier,
920 "current.foo.bar=baz",
923 assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
926 private List<String> lines;
927 private String identifier;
929 private void connectAndAssert(Supplier<Matcher<List<String>>> requestMatcher)
930 throws InterruptedException, ExecutionException, IOException {
932 readMessage(requestMatcher);
935 private void readMessage(Supplier<Matcher<List<String>>> requestMatcher) throws IOException {
936 lines = fcpServer.collectUntil(is("EndMessage"));
937 identifier = extractIdentifier(lines);
938 assertThat(lines, requestMatcher.get());
941 public class Connections {
943 @Test(expected = ExecutionException.class)
944 public void throwsExceptionOnFailure() throws IOException, ExecutionException, InterruptedException {
945 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
946 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
948 "CloseConnectionDuplicateClientName",
954 @Test(expected = ExecutionException.class)
955 public void throwsExceptionIfConnectionIsClosed() throws IOException, ExecutionException, InterruptedException {
956 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
957 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
964 public class GenerateKeyPair {
967 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
968 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
969 connectAndAssert(() -> matchesFcpMessage("GenerateSSK"));
971 FcpKeyPair keyPair = keyPairFuture.get();
972 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
973 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
976 private void replyWithKeyPair() throws IOException {
977 fcpServer.writeLine("SSKKeypair",
978 "InsertURI=" + INSERT_URI + "",
979 "RequestURI=" + REQUEST_URI + "",
980 "Identifier=" + identifier,
988 public class PeerCommands {
990 public class ListPeer {
993 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
994 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
995 connectAndAssert(() -> matchesListPeer("id1"));
996 replyWithPeer("id1");
997 assertThat(peer.get().get().getIdentity(), is("id1"));
1001 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1002 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
1003 connectAndAssert(() -> matchesListPeer("host.free.net:12345"));
1004 replyWithPeer("id1");
1005 assertThat(peer.get().get().getIdentity(), is("id1"));
1009 public void byName() throws InterruptedException, ExecutionException, IOException {
1010 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
1011 connectAndAssert(() -> matchesListPeer("FriendNode"));
1012 replyWithPeer("id1");
1013 assertThat(peer.get().get().getIdentity(), is("id1"));
1017 public void unknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1018 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
1019 connectAndAssert(() -> matchesListPeer("id2"));
1020 replyWithUnknownNodeIdentifier();
1021 assertThat(peer.get().isPresent(), is(false));
1024 private Matcher<List<String>> matchesListPeer(String nodeId) {
1025 return matchesFcpMessage(
1027 "Identifier=" + identifier,
1028 "NodeIdentifier=" + nodeId
1034 public class ListPeers {
1037 public void withoutMetadataOrVolatile() throws IOException, ExecutionException, InterruptedException {
1038 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
1039 connectAndAssert(() -> matchesListPeers(false, false));
1040 replyWithPeer("id1");
1041 replyWithPeer("id2");
1042 sendEndOfPeerList();
1043 assertThat(peers.get(), hasSize(2));
1044 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
1045 containsInAnyOrder("id1", "id2"));
1049 public void withMetadata() throws IOException, ExecutionException, InterruptedException {
1050 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
1051 connectAndAssert(() -> matchesListPeers(false, true));
1052 replyWithPeer("id1", "metadata.foo=bar1");
1053 replyWithPeer("id2", "metadata.foo=bar2");
1054 sendEndOfPeerList();
1055 assertThat(peers.get(), hasSize(2));
1056 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
1057 containsInAnyOrder("bar1", "bar2"));
1061 public void withVolatile() throws IOException, ExecutionException, InterruptedException {
1062 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
1063 connectAndAssert(() -> matchesListPeers(true, false));
1064 replyWithPeer("id1", "volatile.foo=bar1");
1065 replyWithPeer("id2", "volatile.foo=bar2");
1066 sendEndOfPeerList();
1067 assertThat(peers.get(), hasSize(2));
1068 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
1069 containsInAnyOrder("bar1", "bar2"));
1072 private Matcher<List<String>> matchesListPeers(boolean withVolatile, boolean withMetadata) {
1073 return matchesFcpMessage(
1075 "WithVolatile=" + withVolatile,
1076 "WithMetadata=" + withMetadata
1080 private void sendEndOfPeerList() throws IOException {
1081 fcpServer.writeLine(
1083 "Identifier=" + identifier,
1090 public class AddPeer {
1093 public void fromFile() throws InterruptedException, ExecutionException, IOException {
1094 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1095 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("File=/tmp/ref.txt")));
1096 replyWithPeer("id1");
1097 assertThat(peer.get().get().getIdentity(), is("id1"));
1101 public void fromUrl() throws InterruptedException, ExecutionException, IOException {
1102 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1103 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("URL=http://node.ref/")));
1104 replyWithPeer("id1");
1105 assertThat(peer.get().get().getIdentity(), is("id1"));
1109 public void fromNodeRef() throws InterruptedException, ExecutionException, IOException {
1110 NodeRef nodeRef = createNodeRef();
1111 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1112 connectAndAssert(() -> allOf(matchesAddPeer(), Matchers.<String>hasItems(
1114 "ark.pubURI=public",
1118 "dsaGroup.q=subprime",
1119 "dsaPubKey.y=dsa-public",
1120 "physical.udp=1.2.3.4:5678",
1121 "auth.negTypes=3;5",
1124 replyWithPeer("id1");
1125 assertThat(peer.get().get().getIdentity(), is("id1"));
1128 private NodeRef createNodeRef() {
1129 NodeRef nodeRef = new NodeRef();
1130 nodeRef.setIdentity("id1");
1131 nodeRef.setName("name");
1132 nodeRef.setARK(new ARK("public", "1"));
1133 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1134 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1135 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1136 nodeRef.setDSAPublicKey("dsa-public");
1137 nodeRef.setSignature("sig");
1141 private Matcher<List<String>> matchesAddPeer() {
1142 return matchesFcpMessage(
1144 "Identifier=" + identifier
1150 public class ModifyPeer {
1153 public void defaultFcpClientCanEnablePeerByName()
1154 throws InterruptedException, ExecutionException, IOException {
1155 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("id1").execute();
1156 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1157 replyWithPeer("id1");
1158 assertThat(peer.get().get().getIdentity(), is("id1"));
1162 public void defaultFcpClientCanDisablePeerByName()
1163 throws InterruptedException, ExecutionException, IOException {
1164 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("id1").execute();
1165 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", true));
1166 replyWithPeer("id1");
1167 assertThat(peer.get().get().getIdentity(), is("id1"));
1171 public void defaultFcpClientCanEnablePeerByIdentity()
1172 throws InterruptedException, ExecutionException, IOException {
1173 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1174 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1175 replyWithPeer("id1");
1176 assertThat(peer.get().get().getIdentity(), is("id1"));
1180 public void defaultFcpClientCanEnablePeerByHostAndPort()
1181 throws InterruptedException, ExecutionException, IOException {
1182 Future<Optional<Peer>> peer =
1183 fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
1184 connectAndAssert(() -> matchesModifyPeer("1.2.3.4:5678", "IsDisabled", false));
1185 replyWithPeer("id1");
1186 assertThat(peer.get().get().getIdentity(), is("id1"));
1190 public void allowLocalAddressesOfPeer() throws InterruptedException, ExecutionException, IOException {
1191 Future<Optional<Peer>> peer =
1192 fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
1193 connectAndAssert(() -> allOf(
1194 matchesModifyPeer("id1", "AllowLocalAddresses", true),
1195 not(contains(startsWith("IsDisabled=")))
1197 replyWithPeer("id1");
1198 assertThat(peer.get().get().getIdentity(), is("id1"));
1202 public void disallowLocalAddressesOfPeer()
1203 throws InterruptedException, ExecutionException, IOException {
1204 Future<Optional<Peer>> peer =
1205 fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
1206 connectAndAssert(() -> allOf(
1207 matchesModifyPeer("id1", "AllowLocalAddresses", false),
1208 not(contains(startsWith("IsDisabled=")))
1210 replyWithPeer("id1");
1211 assertThat(peer.get().get().getIdentity(), is("id1"));
1215 public void setBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1216 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
1217 connectAndAssert(() -> allOf(
1218 matchesModifyPeer("id1", "IsBurstOnly", true),
1219 not(contains(startsWith("AllowLocalAddresses="))),
1220 not(contains(startsWith("IsDisabled=")))
1222 replyWithPeer("id1");
1223 assertThat(peer.get().get().getIdentity(), is("id1"));
1227 public void clearBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1228 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
1229 connectAndAssert(() -> allOf(
1230 matchesModifyPeer("id1", "IsBurstOnly", false),
1231 not(contains(startsWith("AllowLocalAddresses="))),
1232 not(contains(startsWith("IsDisabled=")))
1234 replyWithPeer("id1");
1235 assertThat(peer.get().get().getIdentity(), is("id1"));
1239 public void defaultFcpClientCanSetListenOnlyForPeer()
1240 throws InterruptedException, ExecutionException, IOException {
1241 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
1242 connectAndAssert(() -> allOf(
1243 matchesModifyPeer("id1", "IsListenOnly", true),
1244 not(contains(startsWith("AllowLocalAddresses="))),
1245 not(contains(startsWith("IsDisabled="))),
1246 not(contains(startsWith("IsBurstOnly=")))
1248 replyWithPeer("id1");
1249 assertThat(peer.get().get().getIdentity(), is("id1"));
1253 public void clearListenOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1254 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
1255 connectAndAssert(() -> allOf(
1256 matchesModifyPeer("id1", "IsListenOnly", false),
1257 not(contains(startsWith("AllowLocalAddresses="))),
1258 not(contains(startsWith("IsDisabled="))),
1259 not(contains(startsWith("IsBurstOnly=")))
1261 replyWithPeer("id1");
1262 assertThat(peer.get().get().getIdentity(), is("id1"));
1266 public void ignoreSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1267 Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
1268 connectAndAssert(() -> allOf(
1269 matchesModifyPeer("id1", "IgnoreSourcePort", true),
1270 not(contains(startsWith("AllowLocalAddresses="))),
1271 not(contains(startsWith("IsDisabled="))),
1272 not(contains(startsWith("IsBurstOnly="))),
1273 not(contains(startsWith("IsListenOnly=")))
1275 replyWithPeer("id1");
1276 assertThat(peer.get().get().getIdentity(), is("id1"));
1280 public void useSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1281 Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
1282 connectAndAssert(() -> allOf(
1283 matchesModifyPeer("id1", "IgnoreSourcePort", false),
1284 not(contains(startsWith("AllowLocalAddresses="))),
1285 not(contains(startsWith("IsDisabled="))),
1286 not(contains(startsWith("IsBurstOnly="))),
1287 not(contains(startsWith("IsListenOnly=")))
1289 replyWithPeer("id1");
1290 assertThat(peer.get().get().getIdentity(), is("id1"));
1294 public void unknownNode() throws InterruptedException, ExecutionException, IOException {
1295 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1296 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1297 replyWithUnknownNodeIdentifier();
1298 assertThat(peer.get().isPresent(), is(false));
1301 private Matcher<List<String>> matchesModifyPeer(String nodeIdentifier, String setting, boolean value) {
1302 return matchesFcpMessage(
1304 "Identifier=" + identifier,
1305 "NodeIdentifier=" + nodeIdentifier,
1306 setting + "=" + value
1312 public class RemovePeer {
1315 public void byName() throws InterruptedException, ExecutionException, IOException {
1316 Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
1317 connectAndAssert(() -> matchesRemovePeer("Friend1"));
1318 replyWithPeerRemoved("Friend1");
1319 assertThat(peer.get(), is(true));
1323 public void invalidName() throws InterruptedException, ExecutionException, IOException {
1324 Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
1325 connectAndAssert(() -> matchesRemovePeer("NotFriend1"));
1326 replyWithUnknownNodeIdentifier();
1327 assertThat(peer.get(), is(false));
1331 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
1332 Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
1333 connectAndAssert(() -> matchesRemovePeer("id1"));
1334 replyWithPeerRemoved("id1");
1335 assertThat(peer.get(), is(true));
1339 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1340 Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
1341 connectAndAssert(() -> matchesRemovePeer("1.2.3.4:5678"));
1342 replyWithPeerRemoved("Friend1");
1343 assertThat(peer.get(), is(true));
1346 private Matcher<List<String>> matchesRemovePeer(String nodeIdentifier) {
1347 return matchesFcpMessage(
1349 "Identifier=" + identifier,
1350 "NodeIdentifier=" + nodeIdentifier
1354 private void replyWithPeerRemoved(String nodeIdentifier) throws IOException {
1355 fcpServer.writeLine(
1357 "Identifier=" + identifier,
1358 "NodeIdentifier=" + nodeIdentifier,
1365 private void replyWithPeer(String peerId, String... additionalLines) throws IOException {
1366 fcpServer.writeLine(
1368 "Identifier=" + identifier,
1369 "identity=" + peerId,
1371 "ark.pubURI=SSK@3YEf.../ark",
1374 "version=Fred,0.7,1.0,1466",
1375 "lastGoodVersion=Fred,0.7,1.0,1466"
1377 fcpServer.writeLine(additionalLines);
1378 fcpServer.writeLine("EndMessage");
1383 public class PeerNoteCommands {
1385 public class ListPeerNotes {
1388 public void onUnknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1389 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1390 connectAndAssert(() -> matchesListPeerNotes("Friend1"));
1391 replyWithUnknownNodeIdentifier();
1392 assertThat(peerNote.get().isPresent(), is(false));
1396 public void byNodeName() throws InterruptedException, ExecutionException, IOException {
1397 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1398 connectAndAssert(() -> matchesListPeerNotes("Friend1"));
1399 replyWithPeerNote();
1400 replyWithEndListPeerNotes();
1401 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1402 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1406 public void byNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1407 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
1408 connectAndAssert(() -> matchesListPeerNotes("id1"));
1409 replyWithPeerNote();
1410 replyWithEndListPeerNotes();
1411 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1412 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1416 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1417 Future<Optional<PeerNote>> peerNote =
1418 fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
1419 connectAndAssert(() -> matchesListPeerNotes("1.2.3.4:5678"));
1420 replyWithPeerNote();
1421 replyWithEndListPeerNotes();
1422 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1423 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1426 private Matcher<List<String>> matchesListPeerNotes(String nodeIdentifier) {
1427 return matchesFcpMessage(
1429 "NodeIdentifier=" + nodeIdentifier
1433 private void replyWithEndListPeerNotes() throws IOException {
1434 fcpServer.writeLine(
1436 "Identifier=" + identifier,
1441 private void replyWithPeerNote() throws IOException {
1442 fcpServer.writeLine(
1444 "Identifier=" + identifier,
1445 "NodeIdentifier=Friend1",
1446 "NoteText=RXhhbXBsZSBUZXh0Lg==",
1454 public class ModifyPeerNotes {
1457 public void byName() throws InterruptedException, ExecutionException, IOException {
1458 Future<Boolean> noteUpdated =
1459 fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1460 connectAndAssert(() -> matchesModifyPeerNote("Friend1"));
1461 replyWithPeerNote();
1462 assertThat(noteUpdated.get(), is(true));
1466 public void onUnknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1467 Future<Boolean> noteUpdated =
1468 fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1469 connectAndAssert(() -> matchesModifyPeerNote("Friend1"));
1470 replyWithUnknownNodeIdentifier();
1471 assertThat(noteUpdated.get(), is(false));
1475 public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
1476 throws InterruptedException, ExecutionException, IOException {
1477 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
1478 assertThat(noteUpdated.get(), is(false));
1482 public void byIdentifier() throws InterruptedException, ExecutionException, IOException {
1483 Future<Boolean> noteUpdated =
1484 fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
1485 connectAndAssert(() -> matchesModifyPeerNote("id1"));
1486 replyWithPeerNote();
1487 assertThat(noteUpdated.get(), is(true));
1491 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1492 Future<Boolean> noteUpdated =
1493 fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
1494 connectAndAssert(() -> matchesModifyPeerNote("1.2.3.4:5678"));
1495 replyWithPeerNote();
1496 assertThat(noteUpdated.get(), is(true));
1499 private Matcher<List<String>> matchesModifyPeerNote(String nodeIdentifier) {
1500 return matchesFcpMessage(
1502 "Identifier=" + identifier,
1503 "NodeIdentifier=" + nodeIdentifier,
1509 private void replyWithPeerNote() throws IOException {
1510 fcpServer.writeLine(
1512 "Identifier=" + identifier,
1513 "NodeIdentifier=Friend1",
1524 private void replyWithUnknownNodeIdentifier() throws IOException {
1525 fcpServer.writeLine(
1526 "UnknownNodeIdentifier",
1527 "Identifier=" + identifier,
1528 "NodeIdentifier=id2",
1535 public class PluginCommands {
1537 private static final String CLASS_NAME = "foo.plugin.Plugin";
1539 private void replyWithPluginInfo() throws IOException {
1540 fcpServer.writeLine(
1542 "Identifier=" + identifier,
1543 "PluginName=superPlugin",
1545 "LongVersion=1.2.3",
1547 "OriginUri=superPlugin",
1553 private void verifyPluginInfo(Future<Optional<PluginInfo>> pluginInfo)
1554 throws InterruptedException, ExecutionException {
1555 assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
1556 assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
1557 assertThat(pluginInfo.get().get().isTalkable(), is(true));
1558 assertThat(pluginInfo.get().get().getVersion(), is("42"));
1559 assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
1560 assertThat(pluginInfo.get().get().isStarted(), is(true));
1563 public class LoadPlugin {
1565 public class OfficialPlugins {
1568 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1569 Future<Optional<PluginInfo>> pluginInfo =
1570 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1571 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1572 assertThat(lines, not(contains(startsWith("Store="))));
1573 replyWithPluginInfo();
1574 verifyPluginInfo(pluginInfo);
1578 public void persistentFromFreenet() throws ExecutionException, InterruptedException, IOException {
1579 Future<Optional<PluginInfo>> pluginInfo =
1580 fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
1581 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1582 assertThat(lines, hasItem("Store=true"));
1583 replyWithPluginInfo();
1584 verifyPluginInfo(pluginInfo);
1588 public void fromHttps() throws ExecutionException, InterruptedException, IOException {
1589 Future<Optional<PluginInfo>> pluginInfo =
1590 fcpClient.loadPlugin().officialFromHttps("superPlugin").execute();
1591 connectAndAssert(() -> createMatcherForOfficialSource("https"));
1592 replyWithPluginInfo();
1593 verifyPluginInfo(pluginInfo);
1596 private Matcher<List<String>> createMatcherForOfficialSource(String officialSource) {
1597 return matchesFcpMessage(
1599 "Identifier=" + identifier,
1600 "PluginURL=superPlugin",
1602 "OfficialSource=" + officialSource
1608 public class FromOtherSources {
1610 private static final String FILE_PATH = "/path/to/plugin.jar";
1611 private static final String URL = "http://server.com/plugin.jar";
1612 private static final String KEY = "KSK@plugin.jar";
1615 public void fromFile() throws ExecutionException, InterruptedException, IOException {
1616 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFile(FILE_PATH).execute();
1617 connectAndAssert(() -> createMatcher("file", FILE_PATH));
1618 replyWithPluginInfo();
1619 verifyPluginInfo(pluginInfo);
1623 public void fromUrl() throws ExecutionException, InterruptedException, IOException {
1624 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromUrl(URL).execute();
1625 connectAndAssert(() -> createMatcher("url", URL));
1626 replyWithPluginInfo();
1627 verifyPluginInfo(pluginInfo);
1631 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1632 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFreenet(KEY).execute();
1633 connectAndAssert(() -> createMatcher("freenet", KEY));
1634 replyWithPluginInfo();
1635 verifyPluginInfo(pluginInfo);
1638 private Matcher<List<String>> createMatcher(String urlType, String url) {
1639 return matchesFcpMessage(
1641 "Identifier=" + identifier,
1643 "URLType=" + urlType
1649 public class Failed {
1652 public void failedLoad() throws ExecutionException, InterruptedException, IOException {
1653 Future<Optional<PluginInfo>> pluginInfo =
1654 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1655 connectAndAssert(() -> matchesFcpMessage("LoadPlugin"));
1656 replyWithProtocolError();
1657 assertThat(pluginInfo.get().isPresent(), is(false));
1664 private void replyWithProtocolError() throws IOException {
1665 fcpServer.writeLine(
1667 "Identifier=" + identifier,
1672 public class ReloadPlugin {
1675 public void reloadingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1676 Future<Optional<PluginInfo>> pluginInfo = fcpClient.reloadPlugin().plugin(CLASS_NAME).execute();
1677 connectAndAssert(() -> matchReloadPluginMessage());
1678 replyWithPluginInfo();
1679 verifyPluginInfo(pluginInfo);
1683 public void reloadingPluginWithMaxWaitTimeWorks()
1684 throws InterruptedException, ExecutionException, IOException {
1685 Future<Optional<PluginInfo>> pluginInfo =
1686 fcpClient.reloadPlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1687 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("MaxWaitTime=1234")));
1688 replyWithPluginInfo();
1689 verifyPluginInfo(pluginInfo);
1693 public void reloadingPluginWithPurgeWorks()
1694 throws InterruptedException, ExecutionException, IOException {
1695 Future<Optional<PluginInfo>> pluginInfo =
1696 fcpClient.reloadPlugin().purge().plugin(CLASS_NAME).execute();
1697 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Purge=true")));
1698 replyWithPluginInfo();
1699 verifyPluginInfo(pluginInfo);
1703 public void reloadingPluginWithStoreWorks()
1704 throws InterruptedException, ExecutionException, IOException {
1705 Future<Optional<PluginInfo>> pluginInfo =
1706 fcpClient.reloadPlugin().addToConfig().plugin(CLASS_NAME).execute();
1707 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Store=true")));
1708 replyWithPluginInfo();
1709 verifyPluginInfo(pluginInfo);
1712 private Matcher<List<String>> matchReloadPluginMessage() {
1713 return matchesFcpMessage(
1715 "Identifier=" + identifier,
1716 "PluginName=" + CLASS_NAME
1722 public class RemovePlugin {
1725 public void removingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1726 Future<Boolean> pluginRemoved = fcpClient.removePlugin().plugin(CLASS_NAME).execute();
1727 connectAndAssert(() -> matchPluginRemovedMessage());
1728 replyWithPluginRemoved();
1729 assertThat(pluginRemoved.get(), is(true));
1733 public void removingPluginWithMaxWaitTimeWorks()
1734 throws InterruptedException, ExecutionException, IOException {
1735 Future<Boolean> pluginRemoved = fcpClient.removePlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1736 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("MaxWaitTime=1234")));
1737 replyWithPluginRemoved();
1738 assertThat(pluginRemoved.get(), is(true));
1742 public void removingPluginWithPurgeWorks()
1743 throws InterruptedException, ExecutionException, IOException {
1744 Future<Boolean> pluginRemoved = fcpClient.removePlugin().purge().plugin(CLASS_NAME).execute();
1745 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("Purge=true")));
1746 replyWithPluginRemoved();
1747 assertThat(pluginRemoved.get(), is(true));
1750 private void replyWithPluginRemoved() throws IOException {
1751 fcpServer.writeLine(
1753 "Identifier=" + identifier,
1754 "PluginName=" + CLASS_NAME,
1759 private Matcher<List<String>> matchPluginRemovedMessage() {
1760 return matchesFcpMessage(
1762 "Identifier=" + identifier,
1763 "PluginName=" + CLASS_NAME
1769 public class GetPluginInfo {
1772 public void gettingPluginInfoWorks() throws InterruptedException, ExecutionException, IOException {
1773 Future<Optional<PluginInfo>> pluginInfo = fcpClient.getPluginInfo().plugin(CLASS_NAME).execute();
1774 connectAndAssert(() -> matchGetPluginInfoMessage());
1775 replyWithPluginInfo();
1776 verifyPluginInfo(pluginInfo);
1780 public void gettingPluginInfoWithDetailsWorks()
1781 throws InterruptedException, ExecutionException, IOException {
1782 Future<Optional<PluginInfo>> pluginInfo =
1783 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1784 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1785 replyWithPluginInfo();
1786 verifyPluginInfo(pluginInfo);
1790 public void protocolErrorIsRecognizedAsFailure()
1791 throws InterruptedException, ExecutionException, IOException {
1792 Future<Optional<PluginInfo>> pluginInfo =
1793 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1794 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1795 replyWithProtocolError();
1796 assertThat(pluginInfo.get(), is(Optional.empty()));
1799 private Matcher<List<String>> matchGetPluginInfoMessage() {
1800 return matchesFcpMessage(
1802 "Identifier=" + identifier,
1803 "PluginName=" + CLASS_NAME
1811 public class UskSubscriptionCommands {
1813 private static final String URI = "USK@some,uri/file.txt";
1816 public void subscriptionWorks() throws InterruptedException, ExecutionException, IOException {
1817 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1818 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI));
1819 replyWithSubscribed();
1820 assertThat(uskSubscription.get().get().getUri(), is(URI));
1821 AtomicInteger edition = new AtomicInteger();
1822 CountDownLatch updated = new CountDownLatch(2);
1823 uskSubscription.get().get().onUpdate(e -> {
1825 updated.countDown();
1827 sendUpdateNotification(23);
1828 sendUpdateNotification(24);
1829 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1830 assertThat(edition.get(), is(24));
1834 public void subscriptionUpdatesMultipleTimes() throws InterruptedException, ExecutionException, IOException {
1835 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1836 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI));
1837 replyWithSubscribed();
1838 assertThat(uskSubscription.get().get().getUri(), is(URI));
1839 AtomicInteger edition = new AtomicInteger();
1840 CountDownLatch updated = new CountDownLatch(2);
1841 uskSubscription.get().get().onUpdate(e -> {
1843 updated.countDown();
1845 uskSubscription.get().get().onUpdate(e -> updated.countDown());
1846 sendUpdateNotification(23);
1847 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1848 assertThat(edition.get(), is(23));
1852 public void subscriptionCanBeCancelled() throws InterruptedException, ExecutionException, IOException {
1853 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1854 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI));
1855 replyWithSubscribed();
1856 assertThat(uskSubscription.get().get().getUri(), is(URI));
1857 AtomicBoolean updated = new AtomicBoolean();
1858 uskSubscription.get().get().onUpdate(e -> updated.set(true));
1859 uskSubscription.get().get().cancel();
1860 readMessage(() -> matchesFcpMessage("UnsubscribeUSK", "Identifier=" + identifier));
1861 sendUpdateNotification(23);
1862 assertThat(updated.get(), is(false));
1865 private void replyWithSubscribed() throws IOException {
1866 fcpServer.writeLine(
1868 "Identifier=" + identifier,
1875 private void sendUpdateNotification(int edition, String... additionalLines) throws IOException {
1876 fcpServer.writeLine(
1877 "SubscribedUSKUpdate",
1878 "Identifier=" + identifier,
1880 "Edition=" + edition
1882 fcpServer.writeLine(additionalLines);
1883 fcpServer.writeLine("EndMessage");
1888 public class ClientGet {
1891 public void works() throws InterruptedException, ExecutionException, IOException {
1892 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1893 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1894 replyWithAllData("not-test", "Hello World", "text/plain;charset=latin-9");
1895 replyWithAllData(identifier, "Hello", "text/plain;charset=utf-8");
1896 Optional<Data> data = dataFuture.get();
1901 public void getFailedIsRecognized() throws InterruptedException, ExecutionException, IOException {
1902 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1903 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1904 replyWithGetFailed(identifier);
1905 Optional<Data> data = dataFuture.get();
1906 assertThat(data.isPresent(), is(false));
1910 public void getFailedForDifferentIdentifierIsIgnored()
1911 throws InterruptedException, ExecutionException, IOException {
1912 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1913 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1914 replyWithGetFailed("not-test");
1915 replyWithAllData(identifier, "Hello", "text/plain;charset=utf-8");
1916 Optional<Data> data = dataFuture.get();
1920 @Test(expected = ExecutionException.class)
1921 public void connectionClosedIsRecognized() throws InterruptedException, ExecutionException, IOException {
1922 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
1923 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
1929 public void withIgnoreData() throws InterruptedException, ExecutionException, IOException {
1930 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
1931 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
1935 public void withDataStoreOnly() throws InterruptedException, ExecutionException, IOException {
1936 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
1937 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
1941 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
1942 throws InterruptedException, ExecutionException, IOException {
1943 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
1944 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
1948 public void clientGetWithPrioritySettingSendsCorrectCommands()
1949 throws InterruptedException, ExecutionException, IOException {
1950 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
1951 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
1955 public void clientGetWithRealTimeSettingSendsCorrectCommands()
1956 throws InterruptedException, ExecutionException, IOException {
1957 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
1958 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
1962 public void clientGetWithGlobalSettingSendsCorrectCommands()
1963 throws InterruptedException, ExecutionException, IOException {
1964 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
1965 connectAndAssert(() -> matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
1968 private void replyWithGetFailed(String identifier) throws IOException {
1969 fcpServer.writeLine(
1971 "Identifier=" + identifier,
1977 private void replyWithAllData(String identifier, String text, String contentType) throws IOException {
1978 fcpServer.writeLine(
1980 "Identifier=" + identifier,
1981 "DataLength=" + (text.length() + 1),
1982 "StartupTime=1435610539000",
1983 "CompletionTime=1435610540000",
1984 "Metadata.ContentType=" + contentType,
1990 private void verifyData(Optional<Data> data) throws IOException {
1991 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
1992 assertThat(data.get().size(), is(6L));
1993 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
1994 is("Hello\n".getBytes(StandardCharsets.UTF_8)));