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.AtomicInteger;
30 import java.util.function.Supplier;
31 import java.util.stream.Collectors;
33 import net.pterodactylus.fcp.ARK;
34 import net.pterodactylus.fcp.ConfigData;
35 import net.pterodactylus.fcp.DSAGroup;
36 import net.pterodactylus.fcp.FcpKeyPair;
37 import net.pterodactylus.fcp.Key;
38 import net.pterodactylus.fcp.NodeData;
39 import net.pterodactylus.fcp.NodeRef;
40 import net.pterodactylus.fcp.Peer;
41 import net.pterodactylus.fcp.PeerNote;
42 import net.pterodactylus.fcp.PluginInfo;
43 import net.pterodactylus.fcp.Priority;
44 import net.pterodactylus.fcp.fake.FakeTcpServer;
45 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
47 import com.google.common.io.ByteStreams;
48 import com.google.common.io.Files;
49 import com.nitorcreations.junit.runners.NestedRunner;
50 import org.hamcrest.Description;
51 import org.hamcrest.Matcher;
52 import org.hamcrest.TypeSafeDiagnosingMatcher;
53 import org.junit.After;
54 import org.junit.Assert;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
59 * Unit test for {@link DefaultFcpClient}.
61 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
63 @RunWith(NestedRunner.class)
64 public class DefaultFcpClientTest {
66 private static final String INSERT_URI =
67 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
68 private static final String REQUEST_URI =
69 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
71 private int threadCounter = 0;
72 private final ExecutorService threadPool =
73 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
74 private final FakeTcpServer fcpServer;
75 private final DefaultFcpClient fcpClient;
77 public DefaultFcpClientTest() throws IOException {
78 fcpServer = new FakeTcpServer(threadPool);
79 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
83 public void tearDown() throws IOException {
85 threadPool.shutdown();
88 @Test(expected = ExecutionException.class)
89 public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
90 throws IOException, ExecutionException, InterruptedException {
91 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
92 fcpServer.connect().get();
93 fcpServer.collectUntil(is("EndMessage"));
95 "CloseConnectionDuplicateClientName",
101 @Test(expected = ExecutionException.class)
102 public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
103 throws IOException, ExecutionException, InterruptedException {
104 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
105 fcpServer.connect().get();
106 fcpServer.collectUntil(is("EndMessage"));
112 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
113 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
115 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
116 String identifier = extractIdentifier(lines);
117 fcpServer.writeLine("SSKKeypair",
118 "InsertURI=" + INSERT_URI + "",
119 "RequestURI=" + REQUEST_URI + "",
120 "Identifier=" + identifier,
122 FcpKeyPair keyPair = keyPairFuture.get();
123 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
124 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
127 private void connectNode() throws InterruptedException, ExecutionException, IOException {
128 fcpServer.connect().get();
129 fcpServer.collectUntil(is("EndMessage"));
130 fcpServer.writeLine("NodeHello",
131 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
132 "Revision=build01466",
134 "Version=Fred,0.7,1.0,1466",
136 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
140 "NodeLanguage=ENGLISH",
147 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
148 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
150 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
151 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
152 String identifier = extractIdentifier(lines);
155 "Identifier=" + identifier,
157 "StartupTime=1435610539000",
158 "CompletionTime=1435610540000",
159 "Metadata.ContentType=text/plain;charset=utf-8",
163 Optional<Data> data = dataFuture.get();
164 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
165 assertThat(data.get().size(), is(6L));
166 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
167 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
170 private String extractIdentifier(List<String> lines) {
171 return lines.stream()
172 .filter(s -> s.startsWith("Identifier="))
173 .map(s -> s.substring(s.indexOf('=') + 1))
179 public void clientGetDownloadsDataForCorrectIdentifier()
180 throws InterruptedException, ExecutionException, IOException {
181 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
183 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
184 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
185 String identifier = extractIdentifier(lines);
188 "Identifier=not-test",
190 "StartupTime=1435610539000",
191 "CompletionTime=1435610540000",
192 "Metadata.ContentType=text/plain;charset=latin-9",
198 "Identifier=" + identifier,
200 "StartupTime=1435610539000",
201 "CompletionTime=1435610540000",
202 "Metadata.ContentType=text/plain;charset=utf-8",
206 Optional<Data> data = dataFuture.get();
207 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
208 assertThat(data.get().size(), is(6L));
209 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
210 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
214 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
215 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
217 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
218 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
219 String identifier = extractIdentifier(lines);
222 "Identifier=" + identifier,
226 Optional<Data> data = dataFuture.get();
227 assertThat(data.isPresent(), is(false));
231 public void clientGetRecognizesGetFailedForCorrectIdentifier()
232 throws InterruptedException, ExecutionException, IOException {
233 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
235 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
236 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
237 String identifier = extractIdentifier(lines);
240 "Identifier=not-test",
246 "Identifier=" + identifier,
250 Optional<Data> data = dataFuture.get();
251 assertThat(data.isPresent(), is(false));
254 @Test(expected = ExecutionException.class)
255 public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
256 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
258 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
259 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
265 public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
266 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
268 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
269 String identifier = extractIdentifier(lines);
272 "InsertURI=" + INSERT_URI + "",
273 "RequestURI=" + REQUEST_URI + "",
274 "Identifier=" + identifier,
278 keyPair = fcpClient.generateKeypair().execute();
279 lines = fcpServer.collectUntil(is("EndMessage"));
280 identifier = extractIdentifier(lines);
283 "InsertURI=" + INSERT_URI + "",
284 "RequestURI=" + REQUEST_URI + "",
285 "Identifier=" + identifier,
292 public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
293 throws InterruptedException, ExecutionException, IOException {
294 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
296 fcpServer.collectUntil(is("EndMessage"));
301 } catch (ExecutionException e) {
303 keyPair = fcpClient.generateKeypair().execute();
305 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
306 String identifier = extractIdentifier(lines);
309 "InsertURI=" + INSERT_URI + "",
310 "RequestURI=" + REQUEST_URI + "",
311 "Identifier=" + identifier,
318 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
319 throws InterruptedException, ExecutionException, IOException {
320 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
322 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
323 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
327 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
328 throws InterruptedException, ExecutionException, IOException {
329 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
331 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
332 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
336 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
337 throws InterruptedException, ExecutionException, IOException {
338 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
340 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
341 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
345 public void clientGetWithPrioritySettingSendsCorrectCommands()
346 throws InterruptedException, ExecutionException, IOException {
347 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
349 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
350 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
354 public void clientGetWithRealTimeSettingSendsCorrectCommands()
355 throws InterruptedException, ExecutionException, IOException {
356 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
358 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
359 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
363 public void clientGetWithGlobalSettingSendsCorrectCommands()
364 throws InterruptedException, ExecutionException, IOException {
365 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
367 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
368 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
371 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
372 return new TypeSafeDiagnosingMatcher<List<String>>() {
374 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
375 if (!item.get(0).equals(name)) {
376 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
379 for (String requiredLine : requiredLines) {
380 if (item.indexOf(requiredLine) < 1) {
381 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
389 public void describeTo(Description description) {
390 description.appendText("FCP message named ").appendValue(name);
391 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
397 public void clientPutWithDirectDataSendsCorrectCommand()
398 throws IOException, ExecutionException, InterruptedException {
399 fcpClient.clientPut()
400 .from(new ByteArrayInputStream("Hello\n".getBytes()))
405 List<String> lines = fcpServer.collectUntil(is("Hello"));
406 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
410 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
411 throws InterruptedException, ExecutionException, IOException {
412 Future<Optional<Key>> key = fcpClient.clientPut()
413 .from(new ByteArrayInputStream("Hello\n".getBytes()))
418 List<String> lines = fcpServer.collectUntil(is("Hello"));
419 String identifier = extractIdentifier(lines);
422 "Identifier=not-the-right-one",
428 "Identifier=" + identifier,
431 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
435 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
436 throws InterruptedException, ExecutionException, IOException {
437 Future<Optional<Key>> key = fcpClient.clientPut()
438 .from(new ByteArrayInputStream("Hello\n".getBytes()))
443 List<String> lines = fcpServer.collectUntil(is("Hello"));
444 String identifier = extractIdentifier(lines);
447 "Identifier=not-the-right-one",
453 "Identifier=" + identifier,
456 assertThat(key.get().isPresent(), is(false));
460 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
461 throws InterruptedException, ExecutionException, IOException {
462 fcpClient.clientPut()
463 .named("otherName.txt")
464 .from(new ByteArrayInputStream("Hello\n".getBytes()))
469 List<String> lines = fcpServer.collectUntil(is("Hello"));
470 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
471 "DataLength=6", "URI=KSK@foo.txt"));
475 public void clientPutWithRedirectSendsCorrectCommand()
476 throws IOException, ExecutionException, InterruptedException {
477 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
479 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
481 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
485 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
486 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
488 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
490 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
494 public void clientPutWithFileCanCompleteTestDdaSequence()
495 throws IOException, ExecutionException, InterruptedException {
496 File tempFile = createTempFile();
497 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
499 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
500 String identifier = extractIdentifier(lines);
503 "Identifier=" + identifier,
507 lines = fcpServer.collectUntil(is("EndMessage"));
508 assertThat(lines, matchesFcpMessage(
510 "Directory=" + tempFile.getParent(),
511 "WantReadDirectory=true",
512 "WantWriteDirectory=false",
517 "Directory=" + tempFile.getParent(),
518 "ReadFilename=" + tempFile,
521 lines = fcpServer.collectUntil(is("EndMessage"));
522 assertThat(lines, matchesFcpMessage(
524 "Directory=" + tempFile.getParent(),
525 "ReadContent=test-content",
530 "Directory=" + tempFile.getParent(),
531 "ReadDirectoryAllowed=true",
534 lines = fcpServer.collectUntil(is("EndMessage"));
536 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
537 "Filename=" + new File(tempFile.getParent(), "test.dat")));
540 private File createTempFile() throws IOException {
541 File tempFile = File.createTempFile("test-dda-", ".dat");
542 tempFile.deleteOnExit();
543 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
548 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
549 throws InterruptedException, ExecutionException, IOException {
550 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
552 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
553 String identifier = extractIdentifier(lines);
556 "Identifier=not-the-right-one",
562 "Identifier=" + identifier,
566 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
570 public void clientPutAbortsOnProtocolErrorOtherThan25()
571 throws InterruptedException, ExecutionException, IOException {
572 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
574 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
575 String identifier = extractIdentifier(lines);
578 "Identifier=" + identifier,
582 assertThat(key.get().isPresent(), is(false));
586 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
587 InterruptedException {
588 File tempFile = createTempFile();
589 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
591 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
592 String identifier = extractIdentifier(lines);
595 "Identifier=" + identifier,
599 lines = fcpServer.collectUntil(is("EndMessage"));
600 assertThat(lines, matchesFcpMessage(
602 "Directory=" + tempFile.getParent(),
603 "WantReadDirectory=true",
604 "WantWriteDirectory=false",
609 "Directory=/some-other-directory",
610 "ReadFilename=" + tempFile,
615 "Directory=" + tempFile.getParent(),
616 "ReadFilename=" + tempFile,
619 lines = fcpServer.collectUntil(is("EndMessage"));
620 assertThat(lines, matchesFcpMessage(
622 "Directory=" + tempFile.getParent(),
623 "ReadContent=test-content",
629 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
630 throws IOException, ExecutionException, InterruptedException {
631 File tempFile = createTempFile();
632 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
634 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
635 String identifier = extractIdentifier(lines);
638 "Identifier=" + identifier,
642 lines = fcpServer.collectUntil(is("EndMessage"));
643 assertThat(lines, matchesFcpMessage(
645 "Directory=" + tempFile.getParent(),
646 "WantReadDirectory=true",
647 "WantWriteDirectory=false",
652 "Directory=" + tempFile.getParent(),
653 "ReadFilename=" + tempFile + ".foo",
656 lines = fcpServer.collectUntil(is("EndMessage"));
657 assertThat(lines, matchesFcpMessage(
659 "Directory=" + tempFile.getParent(),
660 "ReadContent=failed-to-read",
666 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
667 throws IOException, ExecutionException, InterruptedException {
668 File tempFile = createTempFile();
669 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
671 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
672 String identifier = extractIdentifier(lines);
675 "Directory=/some-other-directory",
680 "Identifier=" + identifier,
684 lines = fcpServer.collectUntil(is("EndMessage"));
685 assertThat(lines, matchesFcpMessage(
687 "Directory=" + tempFile.getParent(),
688 "WantReadDirectory=true",
689 "WantWriteDirectory=false",
695 public void clientPutSendsNotificationsForGeneratedKeys()
696 throws InterruptedException, ExecutionException, IOException {
697 List<String> generatedKeys = new CopyOnWriteArrayList<>();
698 Future<Optional<Key>> key = fcpClient.clientPut()
699 .onKeyGenerated(generatedKeys::add)
700 .from(new ByteArrayInputStream("Hello\n".getBytes()))
705 List<String> lines = fcpServer.collectUntil(is("Hello"));
706 String identifier = extractIdentifier(lines);
709 "Identifier=" + identifier,
716 "Identifier=" + identifier,
719 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
720 assertThat(generatedKeys, contains("KSK@foo.txt"));
724 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
725 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
727 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
728 assertThat(lines, matchesFcpMessage(
730 "WithVolatile=false",
731 "WithMetadata=false",
734 String identifier = extractIdentifier(lines);
737 "Identifier=" + identifier,
743 "Identifier=" + identifier,
749 "Identifier=" + identifier,
752 assertThat(peers.get(), hasSize(2));
753 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
754 containsInAnyOrder("id1", "id2"));
758 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
759 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
761 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
762 assertThat(lines, matchesFcpMessage(
764 "WithVolatile=false",
768 String identifier = extractIdentifier(lines);
771 "Identifier=" + identifier,
778 "Identifier=" + identifier,
785 "Identifier=" + identifier,
788 assertThat(peers.get(), hasSize(2));
789 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
790 containsInAnyOrder("bar1", "bar2"));
794 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
795 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
797 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
798 assertThat(lines, matchesFcpMessage(
801 "WithMetadata=false",
804 String identifier = extractIdentifier(lines);
807 "Identifier=" + identifier,
814 "Identifier=" + identifier,
821 "Identifier=" + identifier,
824 assertThat(peers.get(), hasSize(2));
825 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
826 containsInAnyOrder("bar1", "bar2"));
830 public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
831 Future<NodeData> nodeData = fcpClient.getNode().execute();
833 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
834 String identifier = extractIdentifier(lines);
835 assertThat(lines, matchesFcpMessage(
837 "Identifier=" + identifier,
838 "GiveOpennetRef=false",
840 "WithVolatile=false",
845 "Identifier=" + identifier,
846 "ark.pubURI=SSK@3YEf.../ark",
849 "version=Fred,0.7,1.0,1466",
850 "lastGoodVersion=Fred,0.7,1.0,1466",
853 assertThat(nodeData.get(), notNullValue());
857 public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
858 throws InterruptedException, ExecutionException, IOException {
859 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
861 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
862 String identifier = extractIdentifier(lines);
863 assertThat(lines, matchesFcpMessage(
865 "Identifier=" + identifier,
866 "GiveOpennetRef=true",
868 "WithVolatile=false",
873 "Identifier=" + identifier,
875 "ark.pubURI=SSK@3YEf.../ark",
878 "version=Fred,0.7,1.0,1466",
879 "lastGoodVersion=Fred,0.7,1.0,1466",
882 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
886 public void defaultFcpClientCanGetNodeInformationWithPrivateData()
887 throws InterruptedException, ExecutionException, IOException {
888 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
890 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
891 String identifier = extractIdentifier(lines);
892 assertThat(lines, matchesFcpMessage(
894 "Identifier=" + identifier,
895 "GiveOpennetRef=false",
897 "WithVolatile=false",
902 "Identifier=" + identifier,
904 "ark.pubURI=SSK@3YEf.../ark",
907 "version=Fred,0.7,1.0,1466",
908 "lastGoodVersion=Fred,0.7,1.0,1466",
909 "ark.privURI=SSK@XdHMiRl",
912 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
916 public void defaultFcpClientCanGetNodeInformationWithVolatileData()
917 throws InterruptedException, ExecutionException, IOException {
918 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
920 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
921 String identifier = extractIdentifier(lines);
922 assertThat(lines, matchesFcpMessage(
924 "Identifier=" + identifier,
925 "GiveOpennetRef=false",
932 "Identifier=" + identifier,
934 "ark.pubURI=SSK@3YEf.../ark",
937 "version=Fred,0.7,1.0,1466",
938 "lastGoodVersion=Fred,0.7,1.0,1466",
939 "volatile.freeJavaMemory=205706528",
942 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
946 public void defaultFcpClientCanListSinglePeerByIdentity()
947 throws InterruptedException, ExecutionException, IOException {
948 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
950 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
951 String identifier = extractIdentifier(lines);
952 assertThat(lines, matchesFcpMessage(
954 "Identifier=" + identifier,
955 "NodeIdentifier=id1",
960 "Identifier=" + identifier,
963 "ark.pubURI=SSK@3YEf.../ark",
966 "version=Fred,0.7,1.0,1466",
967 "lastGoodVersion=Fred,0.7,1.0,1466",
970 assertThat(peer.get().get().getIdentity(), is("id1"));
974 public void defaultFcpClientCanListSinglePeerByHostAndPort()
975 throws InterruptedException, ExecutionException, IOException {
976 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
978 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
979 String identifier = extractIdentifier(lines);
980 assertThat(lines, matchesFcpMessage(
982 "Identifier=" + identifier,
983 "NodeIdentifier=host.free.net:12345",
988 "Identifier=" + identifier,
991 "ark.pubURI=SSK@3YEf.../ark",
994 "version=Fred,0.7,1.0,1466",
995 "lastGoodVersion=Fred,0.7,1.0,1466",
998 assertThat(peer.get().get().getIdentity(), is("id1"));
1002 public void defaultFcpClientCanListSinglePeerByName()
1003 throws InterruptedException, ExecutionException, IOException {
1004 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
1006 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1007 String identifier = extractIdentifier(lines);
1008 assertThat(lines, matchesFcpMessage(
1010 "Identifier=" + identifier,
1011 "NodeIdentifier=FriendNode",
1014 fcpServer.writeLine(
1016 "Identifier=" + identifier,
1019 "ark.pubURI=SSK@3YEf.../ark",
1022 "version=Fred,0.7,1.0,1466",
1023 "lastGoodVersion=Fred,0.7,1.0,1466",
1026 assertThat(peer.get().get().getIdentity(), is("id1"));
1030 public void defaultFcpClientRecognizesUnknownNodeIdentifiers()
1031 throws InterruptedException, ExecutionException, IOException {
1032 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
1034 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1035 String identifier = extractIdentifier(lines);
1036 assertThat(lines, matchesFcpMessage(
1038 "Identifier=" + identifier,
1039 "NodeIdentifier=id2",
1042 fcpServer.writeLine(
1043 "UnknownNodeIdentifier",
1044 "Identifier=" + identifier,
1045 "NodeIdentifier=id2",
1048 assertThat(peer.get().isPresent(), is(false));
1052 public void defaultFcpClientCanAddPeerFromFile() throws InterruptedException, ExecutionException, IOException {
1053 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1055 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1056 String identifier = extractIdentifier(lines);
1057 assertThat(lines, matchesFcpMessage(
1059 "Identifier=" + identifier,
1060 "File=/tmp/ref.txt",
1063 fcpServer.writeLine(
1065 "Identifier=" + identifier,
1068 "ark.pubURI=SSK@3YEf.../ark",
1071 "version=Fred,0.7,1.0,1466",
1072 "lastGoodVersion=Fred,0.7,1.0,1466",
1075 assertThat(peer.get().get().getIdentity(), is("id1"));
1079 public void defaultFcpClientCanAddPeerFromURL() throws InterruptedException, ExecutionException, IOException {
1080 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1082 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1083 String identifier = extractIdentifier(lines);
1084 assertThat(lines, matchesFcpMessage(
1086 "Identifier=" + identifier,
1087 "URL=http://node.ref/",
1090 fcpServer.writeLine(
1092 "Identifier=" + identifier,
1095 "ark.pubURI=SSK@3YEf.../ark",
1098 "version=Fred,0.7,1.0,1466",
1099 "lastGoodVersion=Fred,0.7,1.0,1466",
1102 assertThat(peer.get().get().getIdentity(), is("id1"));
1106 public void defaultFcpClientCanAddPeerFromNodeRef() throws InterruptedException, ExecutionException, IOException {
1107 NodeRef nodeRef = new NodeRef();
1108 nodeRef.setIdentity("id1");
1109 nodeRef.setName("name");
1110 nodeRef.setARK(new ARK("public", "1"));
1111 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1112 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1113 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1114 nodeRef.setDSAPublicKey("dsa-public");
1115 nodeRef.setSignature("sig");
1116 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1118 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1119 String identifier = extractIdentifier(lines);
1120 assertThat(lines, matchesFcpMessage(
1122 "Identifier=" + identifier,
1125 "ark.pubURI=public",
1129 "dsaGroup.q=subprime",
1130 "dsaPubKey.y=dsa-public",
1131 "physical.udp=1.2.3.4:5678",
1132 "auth.negTypes=3;5",
1136 fcpServer.writeLine(
1138 "Identifier=" + identifier,
1141 "ark.pubURI=SSK@3YEf.../ark",
1144 "version=Fred,0.7,1.0,1466",
1145 "lastGoodVersion=Fred,0.7,1.0,1466",
1148 assertThat(peer.get().get().getIdentity(), is("id1"));
1152 public void listPeerNotesCanGetPeerNotesByNodeName() throws InterruptedException, ExecutionException, IOException {
1153 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1155 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1156 String identifier = extractIdentifier(lines);
1157 assertThat(lines, matchesFcpMessage(
1159 "NodeIdentifier=Friend1",
1162 fcpServer.writeLine(
1164 "Identifier=" + identifier,
1165 "NodeIdentifier=Friend1",
1166 "NoteText=RXhhbXBsZSBUZXh0Lg==",
1170 fcpServer.writeLine(
1172 "Identifier=" + identifier,
1175 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1176 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1180 public void listPeerNotesReturnsEmptyOptionalWhenNodeIdenfierUnknown()
1181 throws InterruptedException, ExecutionException,
1183 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
1185 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1186 String identifier = extractIdentifier(lines);
1187 assertThat(lines, matchesFcpMessage(
1189 "NodeIdentifier=Friend1",
1192 fcpServer.writeLine(
1193 "UnknownNodeIdentifier",
1194 "Identifier=" + identifier,
1195 "NodeIdentifier=Friend1",
1198 assertThat(peerNote.get().isPresent(), is(false));
1202 public void listPeerNotesCanGetPeerNotesByNodeIdentifier()
1203 throws InterruptedException, ExecutionException, IOException {
1204 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
1206 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1207 String identifier = extractIdentifier(lines);
1208 assertThat(lines, matchesFcpMessage(
1210 "NodeIdentifier=id1",
1213 fcpServer.writeLine(
1215 "Identifier=" + identifier,
1216 "NodeIdentifier=id1",
1217 "NoteText=RXhhbXBsZSBUZXh0Lg==",
1221 fcpServer.writeLine(
1223 "Identifier=" + identifier,
1226 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1227 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1231 public void listPeerNotesCanGetPeerNotesByHostNameAndPortNumber()
1232 throws InterruptedException, ExecutionException, IOException {
1233 Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
1235 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1236 String identifier = extractIdentifier(lines);
1237 assertThat(lines, matchesFcpMessage(
1239 "NodeIdentifier=1.2.3.4:5678",
1242 fcpServer.writeLine(
1244 "Identifier=" + identifier,
1245 "NodeIdentifier=id1",
1246 "NoteText=RXhhbXBsZSBUZXh0Lg==",
1250 fcpServer.writeLine(
1252 "Identifier=" + identifier,
1255 assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
1256 assertThat(peerNote.get().get().getPeerNoteType(), is(1));
1260 public void defaultFcpClientCanEnablePeerByName() throws InterruptedException, ExecutionException, IOException {
1261 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("Friend1").execute();
1263 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1264 String identifier = extractIdentifier(lines);
1265 assertThat(lines, matchesFcpMessage(
1267 "Identifier=" + identifier,
1268 "NodeIdentifier=Friend1",
1272 fcpServer.writeLine(
1274 "Identifier=" + identifier,
1275 "NodeIdentifier=Friend1",
1279 assertThat(peer.get().get().getIdentity(), is("id1"));
1283 public void defaultFcpClientCanDisablePeerByName() throws InterruptedException, ExecutionException, IOException {
1284 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("Friend1").execute();
1286 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1287 String identifier = extractIdentifier(lines);
1288 assertThat(lines, matchesFcpMessage(
1290 "Identifier=" + identifier,
1291 "NodeIdentifier=Friend1",
1295 fcpServer.writeLine(
1297 "Identifier=" + identifier,
1298 "NodeIdentifier=Friend1",
1302 assertThat(peer.get().get().getIdentity(), is("id1"));
1306 public void defaultFcpClientCanEnablePeerByIdentity() throws InterruptedException, ExecutionException, IOException {
1307 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1309 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1310 String identifier = extractIdentifier(lines);
1311 assertThat(lines, matchesFcpMessage(
1313 "Identifier=" + identifier,
1314 "NodeIdentifier=id1",
1318 fcpServer.writeLine(
1320 "Identifier=" + identifier,
1321 "NodeIdentifier=Friend1",
1325 assertThat(peer.get().get().getIdentity(), is("id1"));
1329 public void defaultFcpClientCanEnablePeerByHostAndPort()
1330 throws InterruptedException, ExecutionException, IOException {
1331 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
1333 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1334 String identifier = extractIdentifier(lines);
1335 assertThat(lines, matchesFcpMessage(
1337 "Identifier=" + identifier,
1338 "NodeIdentifier=1.2.3.4:5678",
1342 fcpServer.writeLine(
1344 "Identifier=" + identifier,
1345 "NodeIdentifier=Friend1",
1349 assertThat(peer.get().get().getIdentity(), is("id1"));
1353 public void defaultFcpClientCanNotModifyPeerOfUnknownNode()
1354 throws InterruptedException, ExecutionException, IOException {
1355 Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
1357 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1358 String identifier = extractIdentifier(lines);
1359 assertThat(lines, matchesFcpMessage(
1361 "Identifier=" + identifier,
1362 "NodeIdentifier=id1",
1366 fcpServer.writeLine(
1367 "UnknownNodeIdentifier",
1368 "Identifier=" + identifier,
1369 "NodeIdentifier=id1",
1372 assertThat(peer.get().isPresent(), is(false));
1376 public void defaultFcpClientCanAllowLocalAddressesOfPeer()
1377 throws InterruptedException, ExecutionException, IOException {
1378 Future<Optional<Peer>> peer = fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
1380 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1381 String identifier = extractIdentifier(lines);
1382 assertThat(lines, matchesFcpMessage(
1384 "Identifier=" + identifier,
1385 "NodeIdentifier=id1",
1386 "AllowLocalAddresses=true",
1389 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1390 fcpServer.writeLine(
1392 "Identifier=" + identifier,
1393 "NodeIdentifier=Friend1",
1397 assertThat(peer.get().get().getIdentity(), is("id1"));
1401 public void defaultFcpClientCanDisallowLocalAddressesOfPeer()
1402 throws InterruptedException, ExecutionException, IOException {
1403 Future<Optional<Peer>> peer = fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
1405 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1406 String identifier = extractIdentifier(lines);
1407 assertThat(lines, matchesFcpMessage(
1409 "Identifier=" + identifier,
1410 "NodeIdentifier=id1",
1411 "AllowLocalAddresses=false",
1414 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1415 fcpServer.writeLine(
1417 "Identifier=" + identifier,
1418 "NodeIdentifier=Friend1",
1422 assertThat(peer.get().get().getIdentity(), is("id1"));
1426 public void defaultFcpClientCanSetBurstOnlyForPeer()
1427 throws InterruptedException, ExecutionException, IOException {
1428 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
1430 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1431 String identifier = extractIdentifier(lines);
1432 assertThat(lines, matchesFcpMessage(
1434 "Identifier=" + identifier,
1435 "NodeIdentifier=id1",
1439 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1440 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1441 fcpServer.writeLine(
1443 "Identifier=" + identifier,
1444 "NodeIdentifier=Friend1",
1448 assertThat(peer.get().get().getIdentity(), is("id1"));
1452 public void defaultFcpClientCanClearBurstOnlyForPeer()
1453 throws InterruptedException, ExecutionException, IOException {
1454 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
1456 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1457 String identifier = extractIdentifier(lines);
1458 assertThat(lines, matchesFcpMessage(
1460 "Identifier=" + identifier,
1461 "NodeIdentifier=id1",
1462 "IsBurstOnly=false",
1465 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1466 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1467 fcpServer.writeLine(
1469 "Identifier=" + identifier,
1470 "NodeIdentifier=Friend1",
1474 assertThat(peer.get().get().getIdentity(), is("id1"));
1478 public void defaultFcpClientCanSetListenOnlyForPeer()
1479 throws InterruptedException, ExecutionException, IOException {
1480 Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
1482 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1483 String identifier = extractIdentifier(lines);
1484 assertThat(lines, matchesFcpMessage(
1486 "Identifier=" + identifier,
1487 "NodeIdentifier=id1",
1488 "IsListenOnly=true",
1491 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1492 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1493 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1494 fcpServer.writeLine(
1496 "Identifier=" + identifier,
1497 "NodeIdentifier=Friend1",
1501 assertThat(peer.get().get().getIdentity(), is("id1"));
1505 public void defaultFcpClientCanClearListenOnlyForPeer()
1506 throws InterruptedException, ExecutionException, IOException {
1507 Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
1509 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1510 String identifier = extractIdentifier(lines);
1511 assertThat(lines, matchesFcpMessage(
1513 "Identifier=" + identifier,
1514 "NodeIdentifier=id1",
1515 "IsListenOnly=false",
1518 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1519 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1520 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1521 fcpServer.writeLine(
1523 "Identifier=" + identifier,
1524 "NodeIdentifier=Friend1",
1528 assertThat(peer.get().get().getIdentity(), is("id1"));
1532 public void defaultFcpClientCanIgnoreSourceForPeer()
1533 throws InterruptedException, ExecutionException, IOException {
1534 Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
1536 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1537 String identifier = extractIdentifier(lines);
1538 assertThat(lines, matchesFcpMessage(
1540 "Identifier=" + identifier,
1541 "NodeIdentifier=id1",
1542 "IgnoreSourcePort=true",
1545 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1546 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1547 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1548 assertThat(lines, not(contains(startsWith("IsListenOnly="))));
1549 fcpServer.writeLine(
1551 "Identifier=" + identifier,
1552 "NodeIdentifier=Friend1",
1556 assertThat(peer.get().get().getIdentity(), is("id1"));
1560 public void defaultFcpClientCanUseSourceForPeer()
1561 throws InterruptedException, ExecutionException, IOException {
1562 Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
1564 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1565 String identifier = extractIdentifier(lines);
1566 assertThat(lines, matchesFcpMessage(
1568 "Identifier=" + identifier,
1569 "NodeIdentifier=id1",
1570 "IgnoreSourcePort=false",
1573 assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
1574 assertThat(lines, not(contains(startsWith("IsDisabled="))));
1575 assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
1576 assertThat(lines, not(contains(startsWith("IsListenOnly="))));
1577 fcpServer.writeLine(
1579 "Identifier=" + identifier,
1580 "NodeIdentifier=Friend1",
1584 assertThat(peer.get().get().getIdentity(), is("id1"));
1588 public void defaultFcpClientCanRemovePeerByName() throws InterruptedException, ExecutionException, IOException {
1589 Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
1591 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1592 String identifier = extractIdentifier(lines);
1593 assertThat(lines, matchesFcpMessage(
1595 "Identifier=" + identifier,
1596 "NodeIdentifier=Friend1",
1599 fcpServer.writeLine(
1601 "Identifier=" + identifier,
1602 "NodeIdentifier=Friend1",
1605 assertThat(peer.get(), is(true));
1609 public void defaultFcpClientCanNotRemovePeerByInvalidName()
1610 throws InterruptedException, ExecutionException, IOException {
1611 Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
1613 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1614 String identifier = extractIdentifier(lines);
1615 assertThat(lines, matchesFcpMessage(
1617 "Identifier=" + identifier,
1618 "NodeIdentifier=NotFriend1",
1621 fcpServer.writeLine(
1622 "UnknownNodeIdentifier",
1623 "Identifier=" + identifier,
1626 assertThat(peer.get(), is(false));
1630 public void defaultFcpClientCanRemovePeerByIdentity() throws InterruptedException, ExecutionException, IOException {
1631 Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
1633 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1634 String identifier = extractIdentifier(lines);
1635 assertThat(lines, matchesFcpMessage(
1637 "Identifier=" + identifier,
1638 "NodeIdentifier=id1",
1641 fcpServer.writeLine(
1643 "Identifier=" + identifier,
1644 "NodeIdentifier=Friend1",
1647 assertThat(peer.get(), is(true));
1651 public void defaultFcpClientCanRemovePeerByHostAndPort()
1652 throws InterruptedException, ExecutionException, IOException {
1653 Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
1655 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1656 String identifier = extractIdentifier(lines);
1657 assertThat(lines, matchesFcpMessage(
1659 "Identifier=" + identifier,
1660 "NodeIdentifier=1.2.3.4:5678",
1663 fcpServer.writeLine(
1665 "Identifier=" + identifier,
1666 "NodeIdentifier=Friend1",
1669 assertThat(peer.get(), is(true));
1673 public void defaultFcpClientCanModifyPeerNoteByName()
1674 throws InterruptedException, ExecutionException, IOException {
1675 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1677 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1678 String identifier = extractIdentifier(lines);
1679 assertThat(lines, matchesFcpMessage(
1681 "Identifier=" + identifier,
1682 "NodeIdentifier=Friend1",
1687 fcpServer.writeLine(
1689 "Identifier=" + identifier,
1690 "NodeIdentifier=Friend1",
1695 assertThat(noteUpdated.get(), is(true));
1699 public void defaultFcpClientKnowsPeerNoteWasNotModifiedOnUnknownNodeIdentifier()
1700 throws InterruptedException, ExecutionException, IOException {
1701 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
1703 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1704 String identifier = extractIdentifier(lines);
1705 assertThat(lines, matchesFcpMessage(
1707 "Identifier=" + identifier,
1708 "NodeIdentifier=Friend1",
1713 fcpServer.writeLine(
1714 "UnknownNodeIdentifier",
1715 "Identifier=" + identifier,
1716 "NodeIdentifier=Friend1",
1719 assertThat(noteUpdated.get(), is(false));
1723 public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
1724 throws InterruptedException, ExecutionException, IOException {
1725 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
1726 assertThat(noteUpdated.get(), is(false));
1730 public void defaultFcpClientCanModifyPeerNoteByIdentifier()
1731 throws InterruptedException, ExecutionException, IOException {
1732 Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
1734 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1735 String identifier = extractIdentifier(lines);
1736 assertThat(lines, matchesFcpMessage(
1738 "Identifier=" + identifier,
1739 "NodeIdentifier=id1",
1744 fcpServer.writeLine(
1746 "Identifier=" + identifier,
1747 "NodeIdentifier=id1",
1752 assertThat(noteUpdated.get(), is(true));
1756 public void defaultFcpClientCanModifyPeerNoteByHostAndPort()
1757 throws InterruptedException, ExecutionException, IOException {
1758 Future<Boolean> noteUpdated =
1759 fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
1761 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1762 String identifier = extractIdentifier(lines);
1763 assertThat(lines, matchesFcpMessage(
1765 "Identifier=" + identifier,
1766 "NodeIdentifier=1.2.3.4:5678",
1771 fcpServer.writeLine(
1773 "Identifier=" + identifier,
1774 "NodeIdentifier=1.2.3.4:5678",
1779 assertThat(noteUpdated.get(), is(true));
1783 public void defaultFcpClientCanGetConfigWithoutDetails()
1784 throws InterruptedException, ExecutionException, IOException {
1785 Future<ConfigData> configData = fcpClient.getConfig().execute();
1787 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1788 String identifier = extractIdentifier(lines);
1789 assertThat(lines, matchesFcpMessage(
1791 "Identifier=" + identifier,
1794 fcpServer.writeLine(
1796 "Identifier=" + identifier,
1799 assertThat(configData.get(), notNullValue());
1803 public void defaultFcpClientCanGetConfigWithCurrent()
1804 throws InterruptedException, ExecutionException, IOException {
1805 Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
1807 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1808 String identifier = extractIdentifier(lines);
1809 assertThat(lines, matchesFcpMessage(
1811 "Identifier=" + identifier,
1815 fcpServer.writeLine(
1817 "Identifier=" + identifier,
1821 assertThat(configData.get().getCurrent("foo"), is("bar"));
1825 public void defaultFcpClientCanGetConfigWithDefaults()
1826 throws InterruptedException, ExecutionException, IOException {
1827 Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
1829 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1830 String identifier = extractIdentifier(lines);
1831 assertThat(lines, matchesFcpMessage(
1833 "Identifier=" + identifier,
1834 "WithDefaults=true",
1837 fcpServer.writeLine(
1839 "Identifier=" + identifier,
1843 assertThat(configData.get().getDefault("foo"), is("bar"));
1847 public void defaultFcpClientCanGetConfigWithSortOrder()
1848 throws InterruptedException, ExecutionException, IOException {
1849 Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
1851 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1852 String identifier = extractIdentifier(lines);
1853 assertThat(lines, matchesFcpMessage(
1855 "Identifier=" + identifier,
1856 "WithSortOrder=true",
1859 fcpServer.writeLine(
1861 "Identifier=" + identifier,
1865 assertThat(configData.get().getSortOrder("foo"), is(17));
1869 public void defaultFcpClientCanGetConfigWithExpertFlag()
1870 throws InterruptedException, ExecutionException, IOException {
1871 Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
1873 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1874 String identifier = extractIdentifier(lines);
1875 assertThat(lines, matchesFcpMessage(
1877 "Identifier=" + identifier,
1878 "WithExpertFlag=true",
1881 fcpServer.writeLine(
1883 "Identifier=" + identifier,
1884 "expertFlag.foo=true",
1887 assertThat(configData.get().getExpertFlag("foo"), is(true));
1891 public void defaultFcpClientCanGetConfigWithForceWriteFlag()
1892 throws InterruptedException, ExecutionException, IOException {
1893 Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
1895 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1896 String identifier = extractIdentifier(lines);
1897 assertThat(lines, matchesFcpMessage(
1899 "Identifier=" + identifier,
1900 "WithForceWriteFlag=true",
1903 fcpServer.writeLine(
1905 "Identifier=" + identifier,
1906 "forceWriteFlag.foo=true",
1909 assertThat(configData.get().getForceWriteFlag("foo"), is(true));
1913 public void defaultFcpClientCanGetConfigWithShortDescription()
1914 throws InterruptedException, ExecutionException, IOException {
1915 Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
1917 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1918 String identifier = extractIdentifier(lines);
1919 assertThat(lines, matchesFcpMessage(
1921 "Identifier=" + identifier,
1922 "WithShortDescription=true",
1925 fcpServer.writeLine(
1927 "Identifier=" + identifier,
1928 "shortDescription.foo=bar",
1931 assertThat(configData.get().getShortDescription("foo"), is("bar"));
1935 public void defaultFcpClientCanGetConfigWithLongDescription()
1936 throws InterruptedException, ExecutionException, IOException {
1937 Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
1939 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1940 String identifier = extractIdentifier(lines);
1941 assertThat(lines, matchesFcpMessage(
1943 "Identifier=" + identifier,
1944 "WithLongDescription=true",
1947 fcpServer.writeLine(
1949 "Identifier=" + identifier,
1950 "longDescription.foo=bar",
1953 assertThat(configData.get().getLongDescription("foo"), is("bar"));
1957 public void defaultFcpClientCanGetConfigWithDataTypes()
1958 throws InterruptedException, ExecutionException, IOException {
1959 Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
1961 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1962 String identifier = extractIdentifier(lines);
1963 assertThat(lines, matchesFcpMessage(
1965 "Identifier=" + identifier,
1966 "WithDataTypes=true",
1969 fcpServer.writeLine(
1971 "Identifier=" + identifier,
1972 "dataType.foo=number",
1975 assertThat(configData.get().getDataType("foo"), is("number"));
1979 public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
1980 Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
1982 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1983 String identifier = extractIdentifier(lines);
1984 assertThat(lines, matchesFcpMessage(
1986 "Identifier=" + identifier,
1990 fcpServer.writeLine(
1992 "Identifier=" + identifier,
1993 "current.foo.bar=baz",
1996 assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
1999 private List<String> lines;
2000 private String identifier;
2002 private void connectAndAssert(Supplier<Matcher<List<String>>> requestMatcher)
2003 throws InterruptedException, ExecutionException, IOException {
2005 readMessage(requestMatcher);
2008 private void readMessage(Supplier<Matcher<List<String>>> requestMatcher) throws IOException {
2009 lines = fcpServer.collectUntil(is("EndMessage"));
2010 identifier = extractIdentifier(lines);
2011 assertThat(lines, requestMatcher.get());
2014 public class PluginCommands {
2016 private static final String CLASS_NAME = "foo.plugin.Plugin";
2018 private void replyWithPluginInfo() throws IOException {
2019 fcpServer.writeLine(
2021 "Identifier=" + identifier,
2022 "PluginName=superPlugin",
2024 "LongVersion=1.2.3",
2026 "OriginUri=superPlugin",
2032 private void verifyPluginInfo(Future<Optional<PluginInfo>> pluginInfo)
2033 throws InterruptedException, ExecutionException {
2034 assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
2035 assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
2036 assertThat(pluginInfo.get().get().isTalkable(), is(true));
2037 assertThat(pluginInfo.get().get().getVersion(), is("42"));
2038 assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
2039 assertThat(pluginInfo.get().get().isStarted(), is(true));
2042 public class LoadPlugin {
2044 public class OfficialPlugins {
2047 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
2048 Future<Optional<PluginInfo>> pluginInfo =
2049 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
2050 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
2051 assertThat(lines, not(contains(startsWith("Store="))));
2052 replyWithPluginInfo();
2053 verifyPluginInfo(pluginInfo);
2057 public void persistentFromFreenet() throws ExecutionException, InterruptedException, IOException {
2058 Future<Optional<PluginInfo>> pluginInfo =
2059 fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
2060 connectAndAssert(() -> createMatcherForOfficialSource("freenet"));
2061 assertThat(lines, hasItem("Store=true"));
2062 replyWithPluginInfo();
2063 verifyPluginInfo(pluginInfo);
2067 public void fromHttps() throws ExecutionException, InterruptedException, IOException {
2068 Future<Optional<PluginInfo>> pluginInfo =
2069 fcpClient.loadPlugin().officialFromHttps("superPlugin").execute();
2070 connectAndAssert(() -> createMatcherForOfficialSource("https"));
2071 replyWithPluginInfo();
2072 verifyPluginInfo(pluginInfo);
2075 private Matcher<List<String>> createMatcherForOfficialSource(String officialSource) {
2076 return matchesFcpMessage(
2078 "Identifier=" + identifier,
2079 "PluginURL=superPlugin",
2081 "OfficialSource=" + officialSource,
2088 public class FromOtherSources {
2090 private static final String FILE_PATH = "/path/to/plugin.jar";
2091 private static final String URL = "http://server.com/plugin.jar";
2092 private static final String KEY = "KSK@plugin.jar";
2095 public void fromFile() throws ExecutionException, InterruptedException, IOException {
2096 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFile(FILE_PATH).execute();
2097 connectAndAssert(() -> createMatcher("file", FILE_PATH));
2098 replyWithPluginInfo();
2099 verifyPluginInfo(pluginInfo);
2103 public void fromUrl() throws ExecutionException, InterruptedException, IOException {
2104 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromUrl(URL).execute();
2105 connectAndAssert(() -> createMatcher("url", URL));
2106 replyWithPluginInfo();
2107 verifyPluginInfo(pluginInfo);
2111 public void fromFreenet() throws ExecutionException, InterruptedException, IOException {
2112 Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().fromFreenet(KEY).execute();
2113 connectAndAssert(() -> createMatcher("freenet", KEY));
2114 replyWithPluginInfo();
2115 verifyPluginInfo(pluginInfo);
2118 private Matcher<List<String>> createMatcher(String urlType, String url) {
2119 return matchesFcpMessage(
2121 "Identifier=" + identifier,
2123 "URLType=" + urlType,
2130 public class Failed {
2133 public void failedLoad() throws ExecutionException, InterruptedException, IOException {
2134 Future<Optional<PluginInfo>> pluginInfo =
2135 fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
2136 connectAndAssert(() -> matchesFcpMessage("LoadPlugin", "EndMessage"));
2137 replyWithProtocolError();
2138 assertThat(pluginInfo.get().isPresent(), is(false));
2145 private void replyWithProtocolError() throws IOException {
2146 fcpServer.writeLine(
2148 "Identifier=" + identifier,
2153 public class ReloadPlugin {
2156 public void reloadingPluginWorks() throws InterruptedException, ExecutionException, IOException {
2157 Future<Optional<PluginInfo>> pluginInfo = fcpClient.reloadPlugin().plugin(CLASS_NAME).execute();
2158 connectAndAssert(() -> matchReloadPluginMessage());
2159 replyWithPluginInfo();
2160 verifyPluginInfo(pluginInfo);
2164 public void reloadingPluginWithMaxWaitTimeWorks()
2165 throws InterruptedException, ExecutionException, IOException {
2166 Future<Optional<PluginInfo>> pluginInfo =
2167 fcpClient.reloadPlugin().waitFor(1234).plugin(CLASS_NAME).execute();
2168 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("MaxWaitTime=1234")));
2169 replyWithPluginInfo();
2170 verifyPluginInfo(pluginInfo);
2174 public void reloadingPluginWithPurgeWorks()
2175 throws InterruptedException, ExecutionException, IOException {
2176 Future<Optional<PluginInfo>> pluginInfo =
2177 fcpClient.reloadPlugin().purge().plugin(CLASS_NAME).execute();
2178 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Purge=true")));
2179 replyWithPluginInfo();
2180 verifyPluginInfo(pluginInfo);
2184 public void reloadingPluginWithStoreWorks()
2185 throws InterruptedException, ExecutionException, IOException {
2186 Future<Optional<PluginInfo>> pluginInfo =
2187 fcpClient.reloadPlugin().addToConfig().plugin(CLASS_NAME).execute();
2188 connectAndAssert(() -> allOf(matchReloadPluginMessage(), hasItem("Store=true")));
2189 replyWithPluginInfo();
2190 verifyPluginInfo(pluginInfo);
2193 private Matcher<List<String>> matchReloadPluginMessage() {
2194 return matchesFcpMessage(
2196 "Identifier=" + identifier,
2197 "PluginName=" + CLASS_NAME,
2204 public class RemovePlugin {
2207 public void removingPluginWorks() throws InterruptedException, ExecutionException, IOException {
2208 Future<Boolean> pluginRemoved = fcpClient.removePlugin().plugin(CLASS_NAME).execute();
2209 connectAndAssert(() -> matchPluginRemovedMessage());
2210 replyWithPluginRemoved();
2211 assertThat(pluginRemoved.get(), is(true));
2215 public void removingPluginWithMaxWaitTimeWorks()
2216 throws InterruptedException, ExecutionException, IOException {
2217 Future<Boolean> pluginRemoved = fcpClient.removePlugin().waitFor(1234).plugin(CLASS_NAME).execute();
2218 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("MaxWaitTime=1234")));
2219 replyWithPluginRemoved();
2220 assertThat(pluginRemoved.get(), is(true));
2224 public void removingPluginWithPurgeWorks()
2225 throws InterruptedException, ExecutionException, IOException {
2226 Future<Boolean> pluginRemoved = fcpClient.removePlugin().purge().plugin(CLASS_NAME).execute();
2227 connectAndAssert(() -> allOf(matchPluginRemovedMessage(), hasItem("Purge=true")));
2228 replyWithPluginRemoved();
2229 assertThat(pluginRemoved.get(), is(true));
2232 private void replyWithPluginRemoved() throws IOException {
2233 fcpServer.writeLine(
2235 "Identifier=" + identifier,
2236 "PluginName=" + CLASS_NAME,
2241 private Matcher<List<String>> matchPluginRemovedMessage() {
2242 return matchesFcpMessage(
2244 "Identifier=" + identifier,
2245 "PluginName=" + CLASS_NAME,
2252 public class GetPluginInfo {
2255 public void gettingPluginInfoWorks() throws InterruptedException, ExecutionException, IOException {
2256 Future<Optional<PluginInfo>> pluginInfo = fcpClient.getPluginInfo().plugin(CLASS_NAME).execute();
2257 connectAndAssert(() -> matchGetPluginInfoMessage());
2258 replyWithPluginInfo();
2259 verifyPluginInfo(pluginInfo);
2263 public void gettingPluginInfoWithDetailsWorks()
2264 throws InterruptedException, ExecutionException, IOException {
2265 Future<Optional<PluginInfo>> pluginInfo =
2266 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
2267 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
2268 replyWithPluginInfo();
2269 verifyPluginInfo(pluginInfo);
2273 public void protocolErrorIsRecognizedAsFailure()
2274 throws InterruptedException, ExecutionException, IOException {
2275 Future<Optional<PluginInfo>> pluginInfo =
2276 fcpClient.getPluginInfo().detailed().plugin(CLASS_NAME).execute();
2277 connectAndAssert(() -> allOf(matchGetPluginInfoMessage(), hasItem("Detailed=true")));
2278 replyWithProtocolError();
2279 assertThat(pluginInfo.get(), is(Optional.empty()));
2282 private Matcher<List<String>> matchGetPluginInfoMessage() {
2283 return matchesFcpMessage(
2285 "Identifier=" + identifier,
2286 "PluginName=" + CLASS_NAME,
2295 public class UskSubscriptionCommands {
2297 private static final String URI = "USK@some,uri/file.txt";
2300 public void subscriptionWorks() throws InterruptedException, ExecutionException, IOException {
2301 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
2302 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
2303 replyWithSubscribed();
2304 assertThat(uskSubscription.get().get().getUri(), is(URI));
2305 AtomicInteger edition = new AtomicInteger();
2306 CountDownLatch updated = new CountDownLatch(2);
2307 uskSubscription.get().get().onUpdate(e -> {
2309 updated.countDown();
2311 sendUpdateNotification(23);
2312 sendUpdateNotification(24);
2313 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
2314 assertThat(edition.get(), is(24));
2318 public void subscriptionUpdatesMultipleTimes() throws InterruptedException, ExecutionException, IOException {
2319 Future<Optional<UskSubscription>> uskSubscription = fcpClient.subscribeUsk().uri(URI).execute();
2320 connectAndAssert(() -> matchesFcpMessage("SubscribeUSK", "URI=" + URI, "EndMessage"));
2321 replyWithSubscribed();
2322 assertThat(uskSubscription.get().get().getUri(), is(URI));
2323 AtomicInteger edition = new AtomicInteger();
2324 CountDownLatch updated = new CountDownLatch(2);
2325 uskSubscription.get().get().onUpdate(e -> {
2327 updated.countDown();
2329 uskSubscription.get().get().onUpdate(e -> updated.countDown());
2330 sendUpdateNotification(23);
2331 assertThat("updated in time", updated.await(5, TimeUnit.SECONDS), is(true));
2332 assertThat(edition.get(), is(23));
2335 private void replyWithSubscribed() throws IOException {
2336 fcpServer.writeLine(
2338 "Identifier=" + identifier,
2345 private void sendUpdateNotification(int edition, String... additionalLines) throws IOException {
2346 fcpServer.writeLine(
2347 "SubscribedUSKUpdate",
2348 "Identifier=" + identifier,
2350 "Edition=" + edition
2352 fcpServer.writeLine(additionalLines);
2353 fcpServer.writeLine("EndMessage");