1 package net.pterodactylus.fcp.quelaton;
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.containsInAnyOrder;
5 import static org.hamcrest.Matchers.hasSize;
6 import static org.hamcrest.Matchers.is;
8 import java.io.ByteArrayInputStream;
10 import java.io.IOException;
11 import java.nio.charset.StandardCharsets;
12 import java.util.Collection;
13 import java.util.List;
14 import java.util.Optional;
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.ExecutorService;
17 import java.util.concurrent.Executors;
18 import java.util.concurrent.Future;
19 import java.util.logging.Level;
20 import java.util.logging.Logger;
21 import java.util.stream.Collectors;
23 import net.pterodactylus.fcp.FcpKeyPair;
24 import net.pterodactylus.fcp.Key;
25 import net.pterodactylus.fcp.Peer;
26 import net.pterodactylus.fcp.Priority;
27 import net.pterodactylus.fcp.fake.FakeTcpServer;
28 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
30 import com.google.common.io.ByteStreams;
31 import com.google.common.io.Files;
32 import org.hamcrest.Description;
33 import org.hamcrest.Matcher;
34 import org.hamcrest.TypeSafeDiagnosingMatcher;
35 import org.junit.After;
36 import org.junit.Test;
39 * Unit test for {@link DefaultFcpClient}.
41 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
43 public class DefaultFcpClientTest {
45 private static final String INSERT_URI =
46 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
47 private static final String REQUEST_URI =
48 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
50 private static int threadCounter = 0;
51 private final ExecutorService threadPool =
52 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
53 private final FakeTcpServer fcpServer;
54 private final DefaultFcpClient fcpClient;
56 public DefaultFcpClientTest() throws IOException {
57 fcpServer = new FakeTcpServer(threadPool);
58 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
62 public void tearDown() throws IOException {
66 @Test(expected = ExecutionException.class)
67 public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
68 throws IOException, ExecutionException, InterruptedException {
69 Logger.getAnonymousLogger().getParent().setLevel(Level.FINEST);
70 Logger.getAnonymousLogger().getParent().getHandlers()[0].setLevel(Level.FINEST);
71 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
72 fcpServer.connect().get();
73 fcpServer.collectUntil(is("EndMessage"));
75 "CloseConnectionDuplicateClientName",
81 @Test(expected = ExecutionException.class)
82 public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
83 throws IOException, ExecutionException, InterruptedException {
84 Logger.getAnonymousLogger().getParent().setLevel(Level.FINEST);
85 Logger.getAnonymousLogger().getParent().getHandlers()[0].setLevel(Level.FINEST);
86 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
87 fcpServer.connect().get();
88 fcpServer.collectUntil(is("EndMessage"));
94 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
95 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
97 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
98 String identifier = extractIdentifier(lines);
99 fcpServer.writeLine("SSKKeypair",
100 "InsertURI=" + INSERT_URI + "",
101 "RequestURI=" + REQUEST_URI + "",
102 "Identifier=" + identifier,
104 FcpKeyPair keyPair = keyPairFuture.get();
105 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
106 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
109 private void connectNode() throws InterruptedException, ExecutionException, IOException {
110 fcpServer.connect().get();
111 fcpServer.collectUntil(is("EndMessage"));
112 fcpServer.writeLine("NodeHello",
113 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
114 "Revision=build01466",
116 "Version=Fred,0.7,1.0,1466",
118 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
122 "NodeLanguage=ENGLISH",
129 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
130 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
132 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
133 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
134 String identifier = extractIdentifier(lines);
137 "Identifier=" + identifier,
139 "StartupTime=1435610539000",
140 "CompletionTime=1435610540000",
141 "Metadata.ContentType=text/plain;charset=utf-8",
145 Optional<Data> data = dataFuture.get();
146 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
147 assertThat(data.get().size(), is(6L));
148 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
149 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
152 private String extractIdentifier(List<String> lines) {
153 return lines.stream()
154 .filter(s -> s.startsWith("Identifier="))
155 .map(s -> s.substring(s.indexOf('=') + 1))
161 public void clientGetDownloadsDataForCorrectIdentifier()
162 throws InterruptedException, ExecutionException, IOException {
163 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
165 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
166 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
167 String identifier = extractIdentifier(lines);
170 "Identifier=not-test",
172 "StartupTime=1435610539000",
173 "CompletionTime=1435610540000",
174 "Metadata.ContentType=text/plain;charset=latin-9",
180 "Identifier=" + identifier,
182 "StartupTime=1435610539000",
183 "CompletionTime=1435610540000",
184 "Metadata.ContentType=text/plain;charset=utf-8",
188 Optional<Data> data = dataFuture.get();
189 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
190 assertThat(data.get().size(), is(6L));
191 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
192 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
196 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
197 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
199 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
200 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
201 String identifier = extractIdentifier(lines);
204 "Identifier=" + identifier,
208 Optional<Data> data = dataFuture.get();
209 assertThat(data.isPresent(), is(false));
213 public void clientGetRecognizesGetFailedForCorrectIdentifier()
214 throws InterruptedException, ExecutionException, IOException {
215 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
217 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
218 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
219 String identifier = extractIdentifier(lines);
222 "Identifier=not-test",
228 "Identifier=" + identifier,
232 Optional<Data> data = dataFuture.get();
233 assertThat(data.isPresent(), is(false));
236 @Test(expected = ExecutionException.class)
237 public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
238 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
240 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
241 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
247 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
248 throws InterruptedException, ExecutionException, IOException {
249 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
251 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
252 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
256 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
257 throws InterruptedException, ExecutionException, IOException {
258 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
260 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
261 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
265 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
266 throws InterruptedException, ExecutionException, IOException {
267 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
269 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
270 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
274 public void clientGetWithPrioritySettingSendsCorrectCommands()
275 throws InterruptedException, ExecutionException, IOException {
276 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
278 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
279 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
283 public void clientGetWithRealTimeSettingSendsCorrectCommands()
284 throws InterruptedException, ExecutionException, IOException {
285 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
287 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
288 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
292 public void clientGetWithGlobalSettingSendsCorrectCommands()
293 throws InterruptedException, ExecutionException, IOException {
294 fcpClient.clientGet().global().uri("KSK@foo.txt");
296 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
297 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
300 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
301 return new TypeSafeDiagnosingMatcher<List<String>>() {
303 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
304 if (!item.get(0).equals(name)) {
305 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
308 for (String requiredLine : requiredLines) {
309 if (item.indexOf(requiredLine) < 1) {
310 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
318 public void describeTo(Description description) {
319 description.appendText("FCP message named ").appendValue(name);
320 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
326 public void clientPutWithDirectDataSendsCorrectCommand()
327 throws IOException, ExecutionException, InterruptedException {
328 fcpClient.clientPut()
329 .from(new ByteArrayInputStream("Hello\n".getBytes()))
333 List<String> lines = fcpServer.collectUntil(is("Hello"));
334 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
338 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
339 throws InterruptedException, ExecutionException, IOException {
340 Future<Optional<Key>> key = fcpClient.clientPut()
341 .from(new ByteArrayInputStream("Hello\n".getBytes()))
345 List<String> lines = fcpServer.collectUntil(is("Hello"));
346 String identifier = extractIdentifier(lines);
349 "Identifier=not-the-right-one",
355 "Identifier=" + identifier,
358 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
362 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
363 throws InterruptedException, ExecutionException, IOException {
364 Future<Optional<Key>> key = fcpClient.clientPut()
365 .from(new ByteArrayInputStream("Hello\n".getBytes()))
369 List<String> lines = fcpServer.collectUntil(is("Hello"));
370 String identifier = extractIdentifier(lines);
373 "Identifier=not-the-right-one",
379 "Identifier=" + identifier,
382 assertThat(key.get().isPresent(), is(false));
386 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
387 throws InterruptedException, ExecutionException, IOException {
388 fcpClient.clientPut()
389 .named("otherName.txt")
390 .from(new ByteArrayInputStream("Hello\n".getBytes()))
394 List<String> lines = fcpServer.collectUntil(is("Hello"));
395 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
396 "DataLength=6", "URI=KSK@foo.txt"));
400 public void clientPutWithRedirectSendsCorrectCommand()
401 throws IOException, ExecutionException, InterruptedException {
402 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt");
404 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
406 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
410 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
411 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
413 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
415 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
419 public void clientPutWithFileCanCompleteTestDdaSequence()
420 throws IOException, ExecutionException, InterruptedException {
421 File tempFile = createTempFile();
422 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
424 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
425 String identifier = extractIdentifier(lines);
428 "Identifier=" + identifier,
432 lines = fcpServer.collectUntil(is("EndMessage"));
433 assertThat(lines, matchesFcpMessage(
435 "Directory=" + tempFile.getParent(),
436 "WantReadDirectory=true",
437 "WantWriteDirectory=false",
442 "Directory=" + tempFile.getParent(),
443 "ReadFilename=" + tempFile,
446 lines = fcpServer.collectUntil(is("EndMessage"));
447 assertThat(lines, matchesFcpMessage(
449 "Directory=" + tempFile.getParent(),
450 "ReadContent=test-content",
455 "Directory=" + tempFile.getParent(),
456 "ReadDirectoryAllowed=true",
459 lines = fcpServer.collectUntil(is("EndMessage"));
461 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
462 "Filename=" + new File(tempFile.getParent(), "test.dat")));
465 private File createTempFile() throws IOException {
466 File tempFile = File.createTempFile("test-dda-", ".dat");
467 tempFile.deleteOnExit();
468 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
473 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
474 throws InterruptedException, ExecutionException, IOException {
475 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
477 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
478 String identifier = extractIdentifier(lines);
481 "Identifier=not-the-right-one",
487 "Identifier=" + identifier,
491 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
495 public void clientPutAbortsOnProtocolErrorOtherThan25()
496 throws InterruptedException, ExecutionException, IOException {
497 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
499 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
500 String identifier = extractIdentifier(lines);
503 "Identifier=" + identifier,
507 assertThat(key.get().isPresent(), is(false));
511 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
512 InterruptedException {
513 File tempFile = createTempFile();
514 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
516 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
517 String identifier = extractIdentifier(lines);
520 "Identifier=" + identifier,
524 lines = fcpServer.collectUntil(is("EndMessage"));
525 assertThat(lines, matchesFcpMessage(
527 "Directory=" + tempFile.getParent(),
528 "WantReadDirectory=true",
529 "WantWriteDirectory=false",
534 "Directory=/some-other-directory",
535 "ReadFilename=" + tempFile,
540 "Directory=" + tempFile.getParent(),
541 "ReadFilename=" + tempFile,
544 lines = fcpServer.collectUntil(is("EndMessage"));
545 assertThat(lines, matchesFcpMessage(
547 "Directory=" + tempFile.getParent(),
548 "ReadContent=test-content",
554 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
555 throws IOException, ExecutionException, InterruptedException {
556 File tempFile = createTempFile();
557 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
559 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
560 String identifier = extractIdentifier(lines);
563 "Identifier=" + identifier,
567 lines = fcpServer.collectUntil(is("EndMessage"));
568 assertThat(lines, matchesFcpMessage(
570 "Directory=" + tempFile.getParent(),
571 "WantReadDirectory=true",
572 "WantWriteDirectory=false",
577 "Directory=" + tempFile.getParent(),
578 "ReadFilename=" + tempFile + ".foo",
581 lines = fcpServer.collectUntil(is("EndMessage"));
582 assertThat(lines, matchesFcpMessage(
584 "Directory=" + tempFile.getParent(),
585 "ReadContent=failed-to-read",
591 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
592 throws IOException, ExecutionException, InterruptedException {
593 File tempFile = createTempFile();
594 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
596 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
597 String identifier = extractIdentifier(lines);
600 "Directory=/some-other-directory",
605 "Identifier=" + identifier,
609 lines = fcpServer.collectUntil(is("EndMessage"));
610 assertThat(lines, matchesFcpMessage(
612 "Directory=" + tempFile.getParent(),
613 "WantReadDirectory=true",
614 "WantWriteDirectory=false",
620 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
621 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
623 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
624 assertThat(lines, matchesFcpMessage(
626 "WithVolatile=false",
627 "WithMetadata=false",
630 String identifier = extractIdentifier(lines);
633 "Identifier=" + identifier,
639 "Identifier=" + identifier,
645 "Identifier=" + identifier,
648 assertThat(peers.get(), hasSize(2));
649 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
650 containsInAnyOrder("id1", "id2"));
654 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
655 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
657 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
658 assertThat(lines, matchesFcpMessage(
660 "WithVolatile=false",
664 String identifier = extractIdentifier(lines);
667 "Identifier=" + identifier,
674 "Identifier=" + identifier,
681 "Identifier=" + identifier,
684 assertThat(peers.get(), hasSize(2));
685 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
686 containsInAnyOrder("bar1", "bar2"));
690 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
691 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
693 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
694 assertThat(lines, matchesFcpMessage(
697 "WithMetadata=false",
700 String identifier = extractIdentifier(lines);
703 "Identifier=" + identifier,
710 "Identifier=" + identifier,
717 "Identifier=" + identifier,
720 assertThat(peers.get(), hasSize(2));
721 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
722 containsInAnyOrder("bar1", "bar2"));