1 package net.pterodactylus.fcp.quelaton;
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.allOf;
5 import static org.hamcrest.Matchers.contains;
6 import static org.hamcrest.Matchers.containsInAnyOrder;
7 import static org.hamcrest.Matchers.hasItem;
8 import static org.hamcrest.Matchers.hasSize;
9 import static org.hamcrest.Matchers.is;
10 import static org.hamcrest.Matchers.not;
11 import static org.hamcrest.Matchers.notNullValue;
12 import static org.hamcrest.Matchers.startsWith;
14 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
18 import java.nio.charset.StandardCharsets;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Optional;
22 import java.util.concurrent.CopyOnWriteArrayList;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.atomic.AtomicBoolean;
30 import java.util.concurrent.atomic.AtomicInteger;
31 import java.util.function.Supplier;
32 import java.util.stream.Collectors;
34 import net.pterodactylus.fcp.ARK;
35 import net.pterodactylus.fcp.ConfigData;
36 import net.pterodactylus.fcp.DSAGroup;
37 import net.pterodactylus.fcp.FcpKeyPair;
38 import net.pterodactylus.fcp.Key;
39 import net.pterodactylus.fcp.NodeData;
40 import net.pterodactylus.fcp.NodeRef;
41 import net.pterodactylus.fcp.Peer;
42 import net.pterodactylus.fcp.PeerNote;
43 import net.pterodactylus.fcp.PluginInfo;
44 import net.pterodactylus.fcp.Priority;
45 import net.pterodactylus.fcp.fake.FakeTcpServer;
46 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
48 import com.google.common.io.ByteStreams;
49 import com.google.common.io.Files;
50 import com.nitorcreations.junit.runners.NestedRunner;
51 import org.hamcrest.Description;
52 import org.hamcrest.Matcher;
53 import org.hamcrest.Matchers;
54 import org.hamcrest.TypeSafeDiagnosingMatcher;
55 import org.junit.After;
56 import org.junit.Assert;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
61 * Unit test for {@link DefaultFcpClient}.
63 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
65 @RunWith(NestedRunner.class)
66 public class DefaultFcpClientTest {
68 private static final String INSERT_URI =
69 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
70 private static final String REQUEST_URI =
71 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
73 private int threadCounter = 0;
74 private final ExecutorService threadPool =
75 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
76 private final FakeTcpServer fcpServer;
77 private final DefaultFcpClient fcpClient;
79 public DefaultFcpClientTest() throws IOException {
80 fcpServer = new FakeTcpServer(threadPool);
81 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
85 public void tearDown() throws IOException {
87 threadPool.shutdown();
90 @Test(expected = ExecutionException.class)
91 public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
92 throws IOException, ExecutionException, InterruptedException {
93 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
94 fcpServer.connect().get();
95 fcpServer.collectUntil(is("EndMessage"));
97 "CloseConnectionDuplicateClientName",
103 @Test(expected = ExecutionException.class)
104 public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
105 throws IOException, ExecutionException, InterruptedException {
106 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
107 fcpServer.connect().get();
108 fcpServer.collectUntil(is("EndMessage"));
114 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
115 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
117 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
118 String identifier = extractIdentifier(lines);
119 fcpServer.writeLine("SSKKeypair",
120 "InsertURI=" + INSERT_URI + "",
121 "RequestURI=" + REQUEST_URI + "",
122 "Identifier=" + identifier,
124 FcpKeyPair keyPair = keyPairFuture.get();
125 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
126 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
129 private void connectNode() throws InterruptedException, ExecutionException, IOException {
130 fcpServer.connect().get();
131 fcpServer.collectUntil(is("EndMessage"));
132 fcpServer.writeLine("NodeHello",
133 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
134 "Revision=build01466",
136 "Version=Fred,0.7,1.0,1466",
138 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
142 "NodeLanguage=ENGLISH",
149 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
150 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
152 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
153 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
154 String identifier = extractIdentifier(lines);
157 "Identifier=" + identifier,
159 "StartupTime=1435610539000",
160 "CompletionTime=1435610540000",
161 "Metadata.ContentType=text/plain;charset=utf-8",
165 Optional<Data> data = dataFuture.get();
166 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
167 assertThat(data.get().size(), is(6L));
168 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
169 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
172 private String extractIdentifier(List<String> lines) {
173 return lines.stream()
174 .filter(s -> s.startsWith("Identifier="))
175 .map(s -> s.substring(s.indexOf('=') + 1))
181 public void clientGetDownloadsDataForCorrectIdentifier()
182 throws InterruptedException, ExecutionException, IOException {
183 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
185 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
186 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
187 String identifier = extractIdentifier(lines);
190 "Identifier=not-test",
192 "StartupTime=1435610539000",
193 "CompletionTime=1435610540000",
194 "Metadata.ContentType=text/plain;charset=latin-9",
200 "Identifier=" + identifier,
202 "StartupTime=1435610539000",
203 "CompletionTime=1435610540000",
204 "Metadata.ContentType=text/plain;charset=utf-8",
208 Optional<Data> data = dataFuture.get();
209 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
210 assertThat(data.get().size(), is(6L));
211 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
212 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
216 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
217 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
219 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
220 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
221 String identifier = extractIdentifier(lines);
224 "Identifier=" + identifier,
228 Optional<Data> data = dataFuture.get();
229 assertThat(data.isPresent(), is(false));
233 public void clientGetRecognizesGetFailedForCorrectIdentifier()
234 throws InterruptedException, ExecutionException, IOException {
235 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
237 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
238 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
239 String identifier = extractIdentifier(lines);
242 "Identifier=not-test",
248 "Identifier=" + identifier,
252 Optional<Data> data = dataFuture.get();
253 assertThat(data.isPresent(), is(false));
256 @Test(expected = ExecutionException.class)
257 public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
258 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
260 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
261 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
267 public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
268 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
270 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
271 String identifier = extractIdentifier(lines);
274 "InsertURI=" + INSERT_URI + "",
275 "RequestURI=" + REQUEST_URI + "",
276 "Identifier=" + identifier,
280 keyPair = fcpClient.generateKeypair().execute();
281 lines = fcpServer.collectUntil(is("EndMessage"));
282 identifier = extractIdentifier(lines);
285 "InsertURI=" + INSERT_URI + "",
286 "RequestURI=" + REQUEST_URI + "",
287 "Identifier=" + identifier,
294 public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
295 throws InterruptedException, ExecutionException, IOException {
296 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
298 fcpServer.collectUntil(is("EndMessage"));
303 } catch (ExecutionException e) {
305 keyPair = fcpClient.generateKeypair().execute();
307 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
308 String identifier = extractIdentifier(lines);
311 "InsertURI=" + INSERT_URI + "",
312 "RequestURI=" + REQUEST_URI + "",
313 "Identifier=" + identifier,
320 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
321 throws InterruptedException, ExecutionException, IOException {
322 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
324 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
325 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
329 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
330 throws InterruptedException, ExecutionException, IOException {
331 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
333 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
334 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
338 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
339 throws InterruptedException, ExecutionException, IOException {
340 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
342 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
343 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
347 public void clientGetWithPrioritySettingSendsCorrectCommands()
348 throws InterruptedException, ExecutionException, IOException {
349 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
351 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
352 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
356 public void clientGetWithRealTimeSettingSendsCorrectCommands()
357 throws InterruptedException, ExecutionException, IOException {
358 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
360 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
361 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
365 public void clientGetWithGlobalSettingSendsCorrectCommands()
366 throws InterruptedException, ExecutionException, IOException {
367 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
369 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
370 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
373 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
374 return new TypeSafeDiagnosingMatcher<List<String>>() {
376 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
377 if (!item.get(0).equals(name)) {
378 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
381 for (String requiredLine : requiredLines) {
382 if (item.indexOf(requiredLine) < 1) {
383 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
391 public void describeTo(Description description) {
392 description.appendText("FCP message named ").appendValue(name);
393 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
399 public void clientPutWithDirectDataSendsCorrectCommand()
400 throws IOException, ExecutionException, InterruptedException {
401 fcpClient.clientPut()
402 .from(new ByteArrayInputStream("Hello\n".getBytes()))
407 List<String> lines = fcpServer.collectUntil(is("Hello"));
408 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
412 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
413 throws InterruptedException, ExecutionException, IOException {
414 Future<Optional<Key>> key = fcpClient.clientPut()
415 .from(new ByteArrayInputStream("Hello\n".getBytes()))
420 List<String> lines = fcpServer.collectUntil(is("Hello"));
421 String identifier = extractIdentifier(lines);
424 "Identifier=not-the-right-one",
430 "Identifier=" + identifier,
433 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
437 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
438 throws InterruptedException, ExecutionException, IOException {
439 Future<Optional<Key>> key = fcpClient.clientPut()
440 .from(new ByteArrayInputStream("Hello\n".getBytes()))
445 List<String> lines = fcpServer.collectUntil(is("Hello"));
446 String identifier = extractIdentifier(lines);
449 "Identifier=not-the-right-one",
455 "Identifier=" + identifier,
458 assertThat(key.get().isPresent(), is(false));
462 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
463 throws InterruptedException, ExecutionException, IOException {
464 fcpClient.clientPut()
465 .named("otherName.txt")
466 .from(new ByteArrayInputStream("Hello\n".getBytes()))
471 List<String> lines = fcpServer.collectUntil(is("Hello"));
472 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
473 "DataLength=6", "URI=KSK@foo.txt"));
477 public void clientPutWithRedirectSendsCorrectCommand()
478 throws IOException, ExecutionException, InterruptedException {
479 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
481 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
483 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
487 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
488 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
490 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
492 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
496 public void clientPutWithFileCanCompleteTestDdaSequence()
497 throws IOException, ExecutionException, InterruptedException {
498 File tempFile = createTempFile();
499 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
501 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
502 String identifier = extractIdentifier(lines);
505 "Identifier=" + identifier,
509 lines = fcpServer.collectUntil(is("EndMessage"));
510 assertThat(lines, matchesFcpMessage(
512 "Directory=" + tempFile.getParent(),
513 "WantReadDirectory=true",
514 "WantWriteDirectory=false",
519 "Directory=" + tempFile.getParent(),
520 "ReadFilename=" + tempFile,
523 lines = fcpServer.collectUntil(is("EndMessage"));
524 assertThat(lines, matchesFcpMessage(
526 "Directory=" + tempFile.getParent(),
527 "ReadContent=test-content",
532 "Directory=" + tempFile.getParent(),
533 "ReadDirectoryAllowed=true",
536 lines = fcpServer.collectUntil(is("EndMessage"));
538 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
539 "Filename=" + new File(tempFile.getParent(), "test.dat")));
542 private File createTempFile() throws IOException {
543 File tempFile = File.createTempFile("test-dda-", ".dat");
544 tempFile.deleteOnExit();
545 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
550 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
551 throws InterruptedException, ExecutionException, IOException {
552 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
554 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
555 String identifier = extractIdentifier(lines);
558 "Identifier=not-the-right-one",
564 "Identifier=" + identifier,
568 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
572 public void clientPutAbortsOnProtocolErrorOtherThan25()
573 throws InterruptedException, ExecutionException, IOException {
574 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
576 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
577 String identifier = extractIdentifier(lines);
580 "Identifier=" + identifier,
584 assertThat(key.get().isPresent(), is(false));
588 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
589 InterruptedException {
590 File tempFile = createTempFile();
591 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
593 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
594 String identifier = extractIdentifier(lines);
597 "Identifier=" + identifier,
601 lines = fcpServer.collectUntil(is("EndMessage"));
602 assertThat(lines, matchesFcpMessage(
604 "Directory=" + tempFile.getParent(),
605 "WantReadDirectory=true",
606 "WantWriteDirectory=false",
611 "Directory=/some-other-directory",
612 "ReadFilename=" + tempFile,
617 "Directory=" + tempFile.getParent(),
618 "ReadFilename=" + tempFile,
621 lines = fcpServer.collectUntil(is("EndMessage"));
622 assertThat(lines, matchesFcpMessage(
624 "Directory=" + tempFile.getParent(),
625 "ReadContent=test-content",
631 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
632 throws IOException, ExecutionException, InterruptedException {
633 File tempFile = createTempFile();
634 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
636 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
637 String identifier = extractIdentifier(lines);
640 "Identifier=" + identifier,
644 lines = fcpServer.collectUntil(is("EndMessage"));
645 assertThat(lines, matchesFcpMessage(
647 "Directory=" + tempFile.getParent(),
648 "WantReadDirectory=true",
649 "WantWriteDirectory=false",
654 "Directory=" + tempFile.getParent(),
655 "ReadFilename=" + tempFile + ".foo",
658 lines = fcpServer.collectUntil(is("EndMessage"));
659 assertThat(lines, matchesFcpMessage(
661 "Directory=" + tempFile.getParent(),
662 "ReadContent=failed-to-read",
668 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
669 throws IOException, ExecutionException, InterruptedException {
670 File tempFile = createTempFile();
671 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
673 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
674 String identifier = extractIdentifier(lines);
677 "Directory=/some-other-directory",
682 "Identifier=" + identifier,
686 lines = fcpServer.collectUntil(is("EndMessage"));
687 assertThat(lines, matchesFcpMessage(
689 "Directory=" + tempFile.getParent(),
690 "WantReadDirectory=true",
691 "WantWriteDirectory=false",
697 public void clientPutSendsNotificationsForGeneratedKeys()
698 throws InterruptedException, ExecutionException, IOException {
699 List<String> generatedKeys = new CopyOnWriteArrayList<>();
700 Future<Optional<Key>> key = fcpClient.clientPut()
701 .onKeyGenerated(generatedKeys::add)
702 .from(new ByteArrayInputStream("Hello\n".getBytes()))
707 List<String> lines = fcpServer.collectUntil(is("Hello"));
708 String identifier = extractIdentifier(lines);
711 "Identifier=" + identifier,
718 "Identifier=" + identifier,
721 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
722 assertThat(generatedKeys, contains("KSK@foo.txt"));
726 public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
727 Future<NodeData> nodeData = fcpClient.getNode().execute();
729 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
730 String identifier = extractIdentifier(lines);
731 assertThat(lines, matchesFcpMessage(
733 "Identifier=" + identifier,
734 "GiveOpennetRef=false",
736 "WithVolatile=false",
741 "Identifier=" + identifier,
742 "ark.pubURI=SSK@3YEf.../ark",
745 "version=Fred,0.7,1.0,1466",
746 "lastGoodVersion=Fred,0.7,1.0,1466",
749 assertThat(nodeData.get(), notNullValue());
753 public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
754 throws InterruptedException, ExecutionException, IOException {
755 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
757 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
758 String identifier = extractIdentifier(lines);
759 assertThat(lines, matchesFcpMessage(
761 "Identifier=" + identifier,
762 "GiveOpennetRef=true",
764 "WithVolatile=false",
769 "Identifier=" + identifier,
771 "ark.pubURI=SSK@3YEf.../ark",
774 "version=Fred,0.7,1.0,1466",
775 "lastGoodVersion=Fred,0.7,1.0,1466",
778 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
782 public void defaultFcpClientCanGetNodeInformationWithPrivateData()
783 throws InterruptedException, ExecutionException, IOException {
784 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
786 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
787 String identifier = extractIdentifier(lines);
788 assertThat(lines, matchesFcpMessage(
790 "Identifier=" + identifier,
791 "GiveOpennetRef=false",
793 "WithVolatile=false",
798 "Identifier=" + identifier,
800 "ark.pubURI=SSK@3YEf.../ark",
803 "version=Fred,0.7,1.0,1466",
804 "lastGoodVersion=Fred,0.7,1.0,1466",
805 "ark.privURI=SSK@XdHMiRl",
808 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
812 public void defaultFcpClientCanGetNodeInformationWithVolatileData()
813 throws InterruptedException, ExecutionException, IOException {
814 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
816 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
817 String identifier = extractIdentifier(lines);
818 assertThat(lines, matchesFcpMessage(
820 "Identifier=" + identifier,
821 "GiveOpennetRef=false",
828 "Identifier=" + identifier,
830 "ark.pubURI=SSK@3YEf.../ark",
833 "version=Fred,0.7,1.0,1466",
834 "lastGoodVersion=Fred,0.7,1.0,1466",
835 "volatile.freeJavaMemory=205706528",
838 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
842 public void listPeerNotesCanGetPeerNotesByNodeName() throws InterruptedException, ExecutionException, IOException {
843 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
845 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
846 String identifier = extractIdentifier(lines);
847 assertThat(lines, matchesFcpMessage(
849 "NodeIdentifier=Friend1",
854 "Identifier=" + identifier,
855 "NodeIdentifier=Friend1",
856 "NoteText=RXhhbXBsZSBUZXh0Lg==",
862 "Identifier=" + identifier,
865 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
866 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
870 public void listPeerNotesReturnsEmptyOptionalWhenNodeIdenfierUnknown()
871 throws InterruptedException, ExecutionException,
873 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
875 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
876 String identifier = extractIdentifier(lines);
877 assertThat(lines, matchesFcpMessage(
879 "NodeIdentifier=Friend1",
883 "UnknownNodeIdentifier",
884 "Identifier=" + identifier,
885 "NodeIdentifier=Friend1",
888 assertThat(peerNote.get().isPresent(), is(false));
892 public void listPeerNotesCanGetPeerNotesByNodeIdentifier()
893 throws InterruptedException, ExecutionException, IOException {
894 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
896 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
897 String identifier = extractIdentifier(lines);
898 assertThat(lines, matchesFcpMessage(
900 "NodeIdentifier=id1",
905 "Identifier=" + identifier,
906 "NodeIdentifier=id1",
907 "NoteText=RXhhbXBsZSBUZXh0Lg==",
913 "Identifier=" + identifier,
916 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
917 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
921 public void listPeerNotesCanGetPeerNotesByHostNameAndPortNumber()
922 throws InterruptedException, ExecutionException, IOException {
923 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
925 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
926 String identifier = extractIdentifier(lines);
927 assertThat(lines, matchesFcpMessage(
929 "NodeIdentifier=1.2.3.4:5678",
934 "Identifier=" + identifier,
935 "NodeIdentifier=id1",
936 "NoteText=RXhhbXBsZSBUZXh0Lg==",
942 "Identifier=" + identifier,
945 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
946 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
950 public void defaultFcpClientCanModifyPeerNoteByName()
951 throws InterruptedException, ExecutionException, IOException {
952 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
954 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
955 String identifier = extractIdentifier(lines);
956 assertThat(lines, matchesFcpMessage(
958 "Identifier=" + identifier,
959 "NodeIdentifier=Friend1",
966 "Identifier=" + identifier,
967 "NodeIdentifier=Friend1",
972 assertThat(noteUpdated.get(), is(true));
976 public void defaultFcpClientKnowsPeerNoteWasNotModifiedOnUnknownNodeIdentifier()
977 throws InterruptedException, ExecutionException, IOException {
978 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
980 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
981 String identifier = extractIdentifier(lines);
982 assertThat(lines, matchesFcpMessage(
984 "Identifier=" + identifier,
985 "NodeIdentifier=Friend1",
991 "UnknownNodeIdentifier",
992 "Identifier=" + identifier,
993 "NodeIdentifier=Friend1",
996 assertThat(noteUpdated.get(), is(false));
1000 public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
1001 throws InterruptedException, ExecutionException, IOException {
1002 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
1003 assertThat(noteUpdated.get(), is(false));
1007 public void defaultFcpClientCanModifyPeerNoteByIdentifier()
1008 throws InterruptedException, ExecutionException, IOException {
1009 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
1011 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1012 String identifier = extractIdentifier(lines);
1013 assertThat(lines, matchesFcpMessage(
1015 "Identifier=" + identifier,
1016 "NodeIdentifier=id1",
1021 fcpServer.writeLine(
1023 "Identifier=" + identifier,
1024 "NodeIdentifier=id1",
1029 assertThat(noteUpdated.get(), is(true));
1033 public void defaultFcpClientCanModifyPeerNoteByHostAndPort()
1034 throws InterruptedException, ExecutionException, IOException {
1035 Future<Boolean> noteUpdated =
1036 fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
1038 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1039 String identifier = extractIdentifier(lines);
1040 assertThat(lines, matchesFcpMessage(
1042 "Identifier=" + identifier,
1043 "NodeIdentifier=1.2.3.4:5678",
1048 fcpServer.writeLine(
1050 "Identifier=" + identifier,
1051 "NodeIdentifier=1.2.3.4:5678",
1056 assertThat(noteUpdated.get(), is(true));
1060 public void defaultFcpClientCanGetConfigWithoutDetails()
1061 throws InterruptedException, ExecutionException, IOException {
1062 Future<ConfigData> configData = fcpClient.getConfig().execute();
1064 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1065 String identifier = extractIdentifier(lines);
1066 assertThat(lines, matchesFcpMessage(
1068 "Identifier=" + identifier,
1071 fcpServer.writeLine(
1073 "Identifier=" + identifier,
1076 assertThat(configData.get(), notNullValue());
1080 public void defaultFcpClientCanGetConfigWithCurrent()
1081 throws InterruptedException, ExecutionException, IOException {
1082 Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
1084 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1085 String identifier = extractIdentifier(lines);
1086 assertThat(lines, matchesFcpMessage(
1088 "Identifier=" + identifier,
1092 fcpServer.writeLine(
1094 "Identifier=" + identifier,
1098 assertThat(configData.get().getCurrent("foo"), is("bar"));
1102 public void defaultFcpClientCanGetConfigWithDefaults()
1103 throws InterruptedException, ExecutionException, IOException {
1104 Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
1106 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1107 String identifier = extractIdentifier(lines);
1108 assertThat(lines, matchesFcpMessage(
1110 "Identifier=" + identifier,
1111 "WithDefaults=true",
1114 fcpServer.writeLine(
1116 "Identifier=" + identifier,
1120 assertThat(configData.get().getDefault("foo"), is("bar"));
1124 public void defaultFcpClientCanGetConfigWithSortOrder()
1125 throws InterruptedException, ExecutionException, IOException {
1126 Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
1128 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1129 String identifier = extractIdentifier(lines);
1130 assertThat(lines, matchesFcpMessage(
1132 "Identifier=" + identifier,
1133 "WithSortOrder=true",
1136 fcpServer.writeLine(
1138 "Identifier=" + identifier,
1142 assertThat(configData.get().getSortOrder("foo"), is(17));
1146 public void defaultFcpClientCanGetConfigWithExpertFlag()
1147 throws InterruptedException, ExecutionException, IOException {
1148 Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
1150 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1151 String identifier = extractIdentifier(lines);
1152 assertThat(lines, matchesFcpMessage(
1154 "Identifier=" + identifier,
1155 "WithExpertFlag=true",
1158 fcpServer.writeLine(
1160 "Identifier=" + identifier,
1161 "expertFlag.foo=true",
1164 assertThat(configData.get().getExpertFlag("foo"), is(true));
1168 public void defaultFcpClientCanGetConfigWithForceWriteFlag()
1169 throws InterruptedException, ExecutionException, IOException {
1170 Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
1172 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1173 String identifier = extractIdentifier(lines);
1174 assertThat(lines, matchesFcpMessage(
1176 "Identifier=" + identifier,
1177 "WithForceWriteFlag=true",
1180 fcpServer.writeLine(
1182 "Identifier=" + identifier,
1183 "forceWriteFlag.foo=true",
1186 assertThat(configData.get().getForceWriteFlag("foo"), is(true));
1190 public void defaultFcpClientCanGetConfigWithShortDescription()
1191 throws InterruptedException, ExecutionException, IOException {
1192 Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
1194 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1195 String identifier = extractIdentifier(lines);
1196 assertThat(lines, matchesFcpMessage(
1198 "Identifier=" + identifier,
1199 "WithShortDescription=true",
1202 fcpServer.writeLine(
1204 "Identifier=" + identifier,
1205 "shortDescription.foo=bar",
1208 assertThat(configData.get().getShortDescription("foo"), is("bar"));
1212 public void defaultFcpClientCanGetConfigWithLongDescription()
1213 throws InterruptedException, ExecutionException, IOException {
1214 Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
1216 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1217 String identifier = extractIdentifier(lines);
1218 assertThat(lines, matchesFcpMessage(
1220 "Identifier=" + identifier,
1221 "WithLongDescription=true",
1224 fcpServer.writeLine(
1226 "Identifier=" + identifier,
1227 "longDescription.foo=bar",
1230 assertThat(configData.get().getLongDescription("foo"), is("bar"));
1234 public void defaultFcpClientCanGetConfigWithDataTypes()
1235 throws InterruptedException, ExecutionException, IOException {
1236 Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
1238 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1239 String identifier = extractIdentifier(lines);
1240 assertThat(lines, matchesFcpMessage(
1242 "Identifier=" + identifier,
1243 "WithDataTypes=true",
1246 fcpServer.writeLine(
1248 "Identifier=" + identifier,
1249 "dataType.foo=number",
1252 assertThat(configData.get().getDataType("foo"), is("number"));
1256 public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
1257 Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
1259 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1260 String identifier = extractIdentifier(lines);
1261 assertThat(lines, matchesFcpMessage(
1263 "Identifier=" + identifier,
1267 fcpServer.writeLine(
1269 "Identifier=" + identifier,
1270 "current.foo.bar=baz",
1273 assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
1276 private List<String> lines;
1277 private String identifier;
1279 private void connectAndAssert(Supplier<Matcher<List<String>>> requestMatcher)
1280 throws InterruptedException, ExecutionException, IOException {
1282 readMessage(requestMatcher);
1285 private void readMessage(Supplier<Matcher<List<String>>> requestMatcher) throws IOException {
1286 lines = fcpServer.collectUntil(is("EndMessage"));
1287 identifier = extractIdentifier(lines);
1288 assertThat(lines, requestMatcher.get());
1291 public class PeerCommands {
1293 public class ListPeer {
1296 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
1297 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
1298 connectAndAssert(() -> matchesListPeer("id1"));
1299 replyWithPeer("id1");
1300 assertThat(peer.get().get().getIdentity(), is("id1"));
1304 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1305 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
1306 connectAndAssert(() -> matchesListPeer("host.free.net:12345"));
1307 replyWithPeer("id1");
1308 assertThat(peer.get().get().getIdentity(), is("id1"));
1312 public void byName() throws InterruptedException, ExecutionException, IOException {
1313 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
1314 connectAndAssert(() -> matchesListPeer("FriendNode"));
1315 replyWithPeer("id1");
1316 assertThat(peer.get().get().getIdentity(), is("id1"));
1320 public void unknownNodeIdentifier() throws InterruptedException, ExecutionException, IOException {
1321 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
1322 connectAndAssert(() -> matchesListPeer("id2"));
1323 replyWithUnknownNodeIdentifier();
1324 assertThat(peer.get().isPresent(), is(false));
1327 private Matcher<List<String>> matchesListPeer(String nodeId) {
1328 return matchesFcpMessage(
1330 "Identifier=" + identifier,
1331 "NodeIdentifier=" + nodeId,
1338 public class ListPeers {
1341 public void withoutMetadataOrVolatile() throws IOException, ExecutionException, InterruptedException {
1342 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
1343 connectAndAssert(() -> matchesListPeers(false, false));
1344 replyWithPeer("id1");
1345 replyWithPeer("id2");
1346 sendEndOfPeerList();
1347 assertThat(peers.get(), hasSize(2));
1348 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
1349 containsInAnyOrder("id1", "id2"));
1353 public void withMetadata() throws IOException, ExecutionException, InterruptedException {
1354 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
1355 connectAndAssert(() -> matchesListPeers(false, true));
1356 replyWithPeer("id1", "metadata.foo=bar1");
1357 replyWithPeer("id2", "metadata.foo=bar2");
1358 sendEndOfPeerList();
1359 assertThat(peers.get(), hasSize(2));
1360 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
1361 containsInAnyOrder("bar1", "bar2"));
1365 public void withVolatile() throws IOException, ExecutionException, InterruptedException {
1366 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
1367 connectAndAssert(() -> matchesListPeers(true, false));
1368 replyWithPeer("id1", "volatile.foo=bar1");
1369 replyWithPeer("id2", "volatile.foo=bar2");
1370 sendEndOfPeerList();
1371 assertThat(peers.get(), hasSize(2));
1372 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
1373 containsInAnyOrder("bar1", "bar2"));
1376 private Matcher<List<String>> matchesListPeers(boolean withVolatile, boolean withMetadata) {
1377 return matchesFcpMessage(
1379 "WithVolatile=" + withVolatile,
1380 "WithMetadata=" + withMetadata,
1385 private void sendEndOfPeerList() throws IOException {
1386 fcpServer.writeLine(
1388 "Identifier=" + identifier,
1395 public class AddPeer {
1398 public void fromFile() throws InterruptedException, ExecutionException, IOException {
1399 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1400 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("File=/tmp/ref.txt")));
1401 replyWithPeer("id1");
1402 assertThat(peer.get().get().getIdentity(), is("id1"));
1406 public void fromUrl() throws InterruptedException, ExecutionException, IOException {
1407 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1408 connectAndAssert(() -> allOf(matchesAddPeer(), hasItem("URL=http://node.ref/")));
1409 replyWithPeer("id1");
1410 assertThat(peer.get().get().getIdentity(), is("id1"));
1414 public void fromNodeRef() throws InterruptedException, ExecutionException, IOException {
1415 NodeRef nodeRef = createNodeRef();
1416 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1417 connectAndAssert(() -> allOf(matchesAddPeer(), Matchers.<String>hasItems(
1419 "ark.pubURI=public",
1423 "dsaGroup.q=subprime",
1424 "dsaPubKey.y=dsa-public",
1425 "physical.udp=1.2.3.4:5678",
1426 "auth.negTypes=3;5",
1429 replyWithPeer("id1");
1430 assertThat(peer.get().get().getIdentity(), is("id1"));
1433 private NodeRef createNodeRef() {
1434 NodeRef nodeRef = new NodeRef();
1435 nodeRef.setIdentity("id1");
1436 nodeRef.setName("name");
1437 nodeRef.setARK(new ARK("public", "1"));
1438 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1439 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1440 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1441 nodeRef.setDSAPublicKey("dsa-public");
1442 nodeRef.setSignature("sig");
1446 private Matcher<List<String>> matchesAddPeer() {
1447 return matchesFcpMessage(
1449 "Identifier=" + identifier,
1456 public class ModifyPeer {
1459 public void defaultFcpClientCanEnablePeerByName()
1460 throws InterruptedException, ExecutionException, IOException {
1461 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("id1").execute();
1462 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1463 replyWithPeer("id1");
1464 assertThat(peer.get().get().getIdentity(), is("id1"));
1468 public void defaultFcpClientCanDisablePeerByName()
1469 throws InterruptedException, ExecutionException, IOException {
1470 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("id1").execute();
1471 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", true));
1472 replyWithPeer("id1");
1473 assertThat(peer.get().get().getIdentity(), is("id1"));
1477 public void defaultFcpClientCanEnablePeerByIdentity()
1478 throws InterruptedException, ExecutionException, IOException {
1479 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1480 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1481 replyWithPeer("id1");
1482 assertThat(peer.get().get().getIdentity(), is("id1"));
1486 public void defaultFcpClientCanEnablePeerByHostAndPort()
1487 throws InterruptedException, ExecutionException, IOException {
1488 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
1489 connectAndAssert(() -> matchesModifyPeer("1.2.3.4:5678", "IsDisabled", false));
1490 replyWithPeer("id1");
1491 assertThat(peer.get().get().getIdentity(), is("id1"));
1495 public void allowLocalAddressesOfPeer() throws InterruptedException, ExecutionException, IOException {
1496 Future<Optional<Peer>> peer = fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
1497 connectAndAssert(() -> allOf(
1498 matchesModifyPeer("id1", "AllowLocalAddresses", true),
1499 not(contains(startsWith("IsDisabled=")))
1501 replyWithPeer("id1");
1502 assertThat(peer.get().get().getIdentity(), is("id1"));
1506 public void disallowLocalAddressesOfPeer() throws InterruptedException, ExecutionException, IOException {
1507 Future<Optional<Peer>> peer =
1508 fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
1509 connectAndAssert(() -> allOf(
1510 matchesModifyPeer("id1", "AllowLocalAddresses", false),
1511 not(contains(startsWith("IsDisabled=")))
1513 replyWithPeer("id1");
1514 assertThat(peer.get().get().getIdentity(), is("id1"));
1518 public void setBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1519 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
1520 connectAndAssert(() -> allOf(
1521 matchesModifyPeer("id1", "IsBurstOnly", true),
1522 not(contains(startsWith("AllowLocalAddresses="))),
1523 not(contains(startsWith("IsDisabled=")))
1525 replyWithPeer("id1");
1526 assertThat(peer.get().get().getIdentity(), is("id1"));
1530 public void clearBurstOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1531 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
1532 connectAndAssert(() -> allOf(
1533 matchesModifyPeer("id1", "IsBurstOnly", false),
1534 not(contains(startsWith("AllowLocalAddresses="))),
1535 not(contains(startsWith("IsDisabled=")))
1537 replyWithPeer("id1");
1538 assertThat(peer.get().get().getIdentity(), is("id1"));
1542 public void defaultFcpClientCanSetListenOnlyForPeer()
1543 throws InterruptedException, ExecutionException, IOException {
1544 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
1545 connectAndAssert(() -> allOf(
1546 matchesModifyPeer("id1", "IsListenOnly", true),
1547 not(contains(startsWith("AllowLocalAddresses="))),
1548 not(contains(startsWith("IsDisabled="))),
1549 not(contains(startsWith("IsBurstOnly=")))
1551 replyWithPeer("id1");
1552 assertThat(peer.get().get().getIdentity(), is("id1"));
1556 public void clearListenOnlyForPeer() throws InterruptedException, ExecutionException, IOException {
1557 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
1558 connectAndAssert(() -> allOf(
1559 matchesModifyPeer("id1", "IsListenOnly", false),
1560 not(contains(startsWith("AllowLocalAddresses="))),
1561 not(contains(startsWith("IsDisabled="))),
1562 not(contains(startsWith("IsBurstOnly=")))
1564 replyWithPeer("id1");
1565 assertThat(peer.get().get().getIdentity(), is("id1"));
1569 public void ignoreSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1570 Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
1571 connectAndAssert(() -> allOf(
1572 matchesModifyPeer("id1", "IgnoreSourcePort", true),
1573 not(contains(startsWith("AllowLocalAddresses="))),
1574 not(contains(startsWith("IsDisabled="))),
1575 not(contains(startsWith("IsBurstOnly="))),
1576 not(contains(startsWith("IsListenOnly=")))
1578 replyWithPeer("id1");
1579 assertThat(peer.get().get().getIdentity(), is("id1"));
1583 public void useSourceForPeer() throws InterruptedException, ExecutionException, IOException {
1584 Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
1585 connectAndAssert(() -> allOf(
1586 matchesModifyPeer("id1", "IgnoreSourcePort", false),
1587 not(contains(startsWith("AllowLocalAddresses="))),
1588 not(contains(startsWith("IsDisabled="))),
1589 not(contains(startsWith("IsBurstOnly="))),
1590 not(contains(startsWith("IsListenOnly=")))
1592 replyWithPeer("id1");
1593 assertThat(peer.get().get().getIdentity(), is("id1"));
1597 public void unknownNode() throws InterruptedException, ExecutionException, IOException {
1598 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1599 connectAndAssert(() -> matchesModifyPeer("id1", "IsDisabled", false));
1600 replyWithUnknownNodeIdentifier();
1601 assertThat(peer.get().isPresent(), is(false));
1604 private Matcher<List<String>> matchesModifyPeer(String nodeIdentifier, String setting, boolean value) {
1605 return matchesFcpMessage(
1607 "Identifier=" + identifier,
1608 "NodeIdentifier=" + nodeIdentifier,
1609 setting + "=" + value,
1616 public class RemovePeer {
1619 public void byName() throws InterruptedException, ExecutionException, IOException {
1620 Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
1621 connectAndAssert(() -> matchesRemovePeer("Friend1"));
1622 replyWithPeerRemoved("Friend1");
1623 assertThat(peer.get(), is(true));
1627 public void invalidName() throws InterruptedException, ExecutionException, IOException {
1628 Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
1629 connectAndAssert(() -> matchesRemovePeer("NotFriend1"));
1630 replyWithUnknownNodeIdentifier();
1631 assertThat(peer.get(), is(false));
1635 public void byIdentity() throws InterruptedException, ExecutionException, IOException {
1636 Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
1637 connectAndAssert(() -> matchesRemovePeer("id1"));
1638 replyWithPeerRemoved("id1");
1639 assertThat(peer.get(), is(true));
1643 public void byHostAndPort() throws InterruptedException, ExecutionException, IOException {
1644 Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
1645 connectAndAssert(() -> matchesRemovePeer("1.2.3.4:5678"));
1646 replyWithPeerRemoved("Friend1");
1647 assertThat(peer.get(), is(true));
1650 private Matcher<List<String>> matchesRemovePeer(String nodeIdentifier) {
1651 return matchesFcpMessage(
1653 "Identifier=" + identifier,
1654 "NodeIdentifier=" + nodeIdentifier,
1659 private void replyWithPeerRemoved(String nodeIdentifier) throws IOException {
1660 fcpServer.writeLine(
1662 "Identifier=" + identifier,
1663 "NodeIdentifier=" + nodeIdentifier,
1670 private void replyWithPeer(String peerId, String... additionalLines) throws IOException {
1671 fcpServer.writeLine(
1673 "Identifier=" + identifier,
1674 "identity=" + peerId,
1676 "ark.pubURI=SSK@3YEf.../ark",
1679 "version=Fred,0.7,1.0,1466",
1680 "lastGoodVersion=Fred,0.7,1.0,1466"
1682 fcpServer.writeLine(additionalLines);
1683 fcpServer.writeLine("EndMessage");
1686 private void replyWithUnknownNodeIdentifier() throws IOException {
1687 fcpServer.writeLine(
1688 "UnknownNodeIdentifier",
1689 "Identifier=" + identifier,
1690 "NodeIdentifier=id2",
1697 public class PluginCommands {
1699 private static final String CLASS_NAME = "foo.plugin.Plugin";
1701 private void replyWithPluginInfo() throws IOException {
1702 fcpServer.writeLine(
1704 "Identifier=" + identifier,
1705 "PluginName=superPlugin",
1707 "LongVersion=1.2.3",
1709 "OriginUri=superPlugin",
1715 private void verifyPluginInfo(Future<Optional<PluginInfo>> pluginInfo)
1716 throws InterruptedException, ExecutionException {
1717 assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
1718 assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
1719 assertThat(pluginInfo.get().get().isTalkable(), is(true));
1720 assertThat(pluginInfo.get().get().getVersion(), is("42"));
1721 assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
1722 assertThat(pluginInfo.get().get().isStarted(), is(true));
1725 public class LoadPlugin {
1727 public class OfficialPlugins {
1730 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1731 Future<Optional<PluginInfo>> pluginInfo =
1732 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1733 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1734 assertThat(lines, not(contains(startsWith("Store="))));
1735 replyWithPluginInfo();
1736 verifyPluginInfo(pluginInfo);
1740 public void persistentFromFreenet() throws ExecutionException, InterruptedException, IOException {
1741 Future<Optional<PluginInfo>> pluginInfo =
1742 fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
1743 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
1744 assertThat(lines, hasItem("Store=true"));
1745 replyWithPluginInfo();
1746 verifyPluginInfo(pluginInfo);
1750 public void fromHttps() throws ExecutionException, InterruptedException, IOException {
1751 Future<Optional<PluginInfo>> pluginInfo =
1752 fcpClient.loadPlugin().officialFromHttps("superPlugin").execute();
1753 connectAndAssert(() -> createMatcherForOfficialSource("https"));
1754 replyWithPluginInfo();
1755 verifyPluginInfo(pluginInfo);
1758 private Matcher<List<String>> createMatcherForOfficialSource(String officialSource) {
1759 return matchesFcpMessage(
1761 "Identifier=" + identifier,
1762 "PluginURL=superPlugin",
1764 "OfficialSource=" + officialSource,
1771 public class FromOtherSources {
1773 private static final String FILE_PATH = "/path/to/plugin.jar";
1774 private static final String URL = "http://server.com/plugin.jar";
1775 private static final String KEY = "KSK@plugin.jar";
1778 public void fromFile() throws ExecutionException, InterruptedException, IOException {
1779 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFile(FILE_PATH).execute();
1780 connectAndAssert(() -> createMatcher("file", FILE_PATH));
1781 replyWithPluginInfo();
1782 verifyPluginInfo(pluginInfo);
1786 public void fromUrl() throws ExecutionException, InterruptedException, IOException {
1787 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromUrl(URL).execute();
1788 connectAndAssert(() -> createMatcher("url", URL));
1789 replyWithPluginInfo();
1790 verifyPluginInfo(pluginInfo);
1794 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
1795 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFreenet(KEY).execute();
1796 connectAndAssert(() -> createMatcher("freenet", KEY));
1797 replyWithPluginInfo();
1798 verifyPluginInfo(pluginInfo);
1801 private Matcher<List<String>> createMatcher(String urlType, String url) {
1802 return matchesFcpMessage(
1804 "Identifier=" + identifier,
1806 "URLType=" + urlType,
1813 public class Failed {
1816 public void failedLoad() throws ExecutionException, InterruptedException, IOException {
1817 Future<Optional<PluginInfo>> pluginInfo =
1818 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
1819 connectAndAssert(() -> matchesFcpMessage("LoadPlugin", "EndMessage"));
1820 replyWithProtocolError();
1821 assertThat(pluginInfo.get().isPresent(), is(false));
1828 private void replyWithProtocolError() throws IOException {
1829 fcpServer.writeLine(
1831 "Identifier=" + identifier,
1836 public class ReloadPlugin {
1839 public void reloadingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1840 Future<Optional<PluginInfo>> pluginInfo = fcpClient.reloadPlugin().plugin(CLASS_NAME).execute();
1841 connectAndAssert(() -> matchReloadPluginMessage());
1842 replyWithPluginInfo();
1843 verifyPluginInfo(pluginInfo);
1847 public void reloadingPluginWithMaxWaitTimeWorks()
1848 throws InterruptedException, ExecutionException, IOException {
1849 Future<Optional<PluginInfo>> pluginInfo =
1850 fcpClient.reloadPlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1851 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("MaxWaitTime=1234")));
1852 replyWithPluginInfo();
1853 verifyPluginInfo(pluginInfo);
1857 public void reloadingPluginWithPurgeWorks()
1858 throws InterruptedException, ExecutionException, IOException {
1859 Future<Optional<PluginInfo>> pluginInfo =
1860 fcpClient.reloadPlugin().purge().plugin(CLASS_NAME).execute();
1861 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Purge=true")));
1862 replyWithPluginInfo();
1863 verifyPluginInfo(pluginInfo);
1867 public void reloadingPluginWithStoreWorks()
1868 throws InterruptedException, ExecutionException, IOException {
1869 Future<Optional<PluginInfo>> pluginInfo =
1870 fcpClient.reloadPlugin().addToConfig().plugin(CLASS_NAME).execute();
1871 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Store=true")));
1872 replyWithPluginInfo();
1873 verifyPluginInfo(pluginInfo);
1876 private Matcher<List<String>> matchReloadPluginMessage() {
1877 return matchesFcpMessage(
1879 "Identifier=" + identifier,
1880 "PluginName=" + CLASS_NAME,
1887 public class RemovePlugin {
1890 public void removingPluginWorks() throws InterruptedException, ExecutionException, IOException {
1891 Future<Boolean> pluginRemoved = fcpClient.removePlugin().plugin(CLASS_NAME).execute();
1892 connectAndAssert(() -> matchPluginRemovedMessage());
1893 replyWithPluginRemoved();
1894 assertThat(pluginRemoved.get(), is(true));
1898 public void removingPluginWithMaxWaitTimeWorks()
1899 throws InterruptedException, ExecutionException, IOException {
1900 Future<Boolean> pluginRemoved = fcpClient.removePlugin().waitFor(1234).plugin(CLASS_NAME).execute();
1901 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("MaxWaitTime=1234")));
1902 replyWithPluginRemoved();
1903 assertThat(pluginRemoved.get(), is(true));
1907 public void removingPluginWithPurgeWorks()
1908 throws InterruptedException, ExecutionException, IOException {
1909 Future<Boolean> pluginRemoved = fcpClient.removePlugin().purge().plugin(CLASS_NAME).execute();
1910 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("Purge=true")));
1911 replyWithPluginRemoved();
1912 assertThat(pluginRemoved.get(), is(true));
1915 private void replyWithPluginRemoved() throws IOException {
1916 fcpServer.writeLine(
1918 "Identifier=" + identifier,
1919 "PluginName=" + CLASS_NAME,
1924 private Matcher<List<String>> matchPluginRemovedMessage() {
1925 return matchesFcpMessage(
1927 "Identifier=" + identifier,
1928 "PluginName=" + CLASS_NAME,
1935 public class GetPluginInfo {
1938 public void gettingPluginInfoWorks() throws InterruptedException, ExecutionException, IOException {
1939 Future<Optional<PluginInfo>> pluginInfo = fcpClient.getPluginInfo().plugin(CLASS_NAME).execute();
1940 connectAndAssert(() -> matchGetPluginInfoMessage());
1941 replyWithPluginInfo();
1942 verifyPluginInfo(pluginInfo);
1946 public void gettingPluginInfoWithDetailsWorks()
1947 throws InterruptedException, ExecutionException, IOException {
1948 Future<Optional<PluginInfo>> pluginInfo =
1949 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1950 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1951 replyWithPluginInfo();
1952 verifyPluginInfo(pluginInfo);
1956 public void protocolErrorIsRecognizedAsFailure()
1957 throws InterruptedException, ExecutionException, IOException {
1958 Future<Optional<PluginInfo>> pluginInfo =
1959 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
1960 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
1961 replyWithProtocolError();
1962 assertThat(pluginInfo.get(), is(Optional.empty()));
1965 private Matcher<List<String>> matchGetPluginInfoMessage() {
1966 return matchesFcpMessage(
1968 "Identifier=" + identifier,
1969 "PluginName=" + CLASS_NAME,
1978 public class UskSubscriptionCommands {
1980 private static final String URI = "USK@some,uri/file.txt";
1983 public void subscriptionWorks() throws InterruptedException, ExecutionException, IOException {
1984 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
1985 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
1986 replyWithSubscribed();
1987 assertThat(uskSubscription.get().get().getUri(), is(URI));
1988 AtomicInteger edition = new AtomicInteger();
1989 CountDownLatch updated = new CountDownLatch(2);
1990 uskSubscription.get().get().onUpdate(e -> {
1992 updated.countDown();
1994 sendUpdateNotification(23);
1995 sendUpdateNotification(24);
1996 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
1997 assertThat(edition.get(), is(24));
2001 public void subscriptionUpdatesMultipleTimes() throws InterruptedException, ExecutionException, IOException {
2002 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
2003 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
2004 replyWithSubscribed();
2005 assertThat(uskSubscription.get().get().getUri(), is(URI));
2006 AtomicInteger edition = new AtomicInteger();
2007 CountDownLatch updated = new CountDownLatch(2);
2008 uskSubscription.get().get().onUpdate(e -> {
2010 updated.countDown();
2012 uskSubscription.get().get().onUpdate(e -> updated.countDown());
2013 sendUpdateNotification(23);
2014 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
2015 assertThat(edition.get(), is(23));
2019 public void subscriptionCanBeCancelled() throws InterruptedException, ExecutionException, IOException {
2020 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
2021 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
2022 replyWithSubscribed();
2023 assertThat(uskSubscription.get().get().getUri(), is(URI));
2024 AtomicBoolean updated = new AtomicBoolean();
2025 uskSubscription.get().get().onUpdate(e -> updated.set(true));
2026 uskSubscription.get().get().cancel();
2027 readMessage(() -> matchesFcpMessage("UnsubscribeUSK", "Identifier=" + identifier, "EndMessage"));
2028 sendUpdateNotification(23);
2029 assertThat(updated.get(), is(false));
2032 private void replyWithSubscribed() throws IOException {
2033 fcpServer.writeLine(
2035 "Identifier=" + identifier,
2042 private void sendUpdateNotification(int edition, String... additionalLines) throws IOException {
2043 fcpServer.writeLine(
2044 "SubscribedUSKUpdate",
2045 "Identifier=" + identifier,
2047 "Edition=" + edition
2049 fcpServer.writeLine(additionalLines);
2050 fcpServer.writeLine("EndMessage");