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.stream.Collectors;
21 import net.pterodactylus.fcp.FcpKeyPair;
22 import net.pterodactylus.fcp.Key;
23 import net.pterodactylus.fcp.Peer;
24 import net.pterodactylus.fcp.Priority;
25 import net.pterodactylus.fcp.fake.FakeTcpServer;
26 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
28 import com.google.common.io.ByteStreams;
29 import com.google.common.io.Files;
30 import org.hamcrest.Description;
31 import org.hamcrest.Matcher;
32 import org.hamcrest.TypeSafeDiagnosingMatcher;
33 import org.junit.After;
34 import org.junit.Test;
37 * Unit test for {@link DefaultFcpClient}.
39 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
41 public class DefaultFcpClientTest {
43 private static final String INSERT_URI =
44 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
45 private static final String REQUEST_URI =
46 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
48 private static int threadCounter = 0;
49 private final ExecutorService threadPool =
50 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
51 private final FakeTcpServer fcpServer;
52 private final DefaultFcpClient fcpClient;
54 public DefaultFcpClientTest() throws IOException {
55 fcpServer = new FakeTcpServer(threadPool);
56 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
60 public void tearDown() throws IOException {
64 @Test(expected = ExecutionException.class)
65 public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
66 throws IOException, ExecutionException, InterruptedException {
67 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
68 fcpServer.connect().get();
69 fcpServer.collectUntil(is("EndMessage"));
71 "CloseConnectionDuplicateClientName",
77 @Test(expected = ExecutionException.class)
78 public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
79 throws IOException, ExecutionException, InterruptedException {
80 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
81 fcpServer.connect().get();
82 fcpServer.collectUntil(is("EndMessage"));
88 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
89 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
91 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
92 String identifier = extractIdentifier(lines);
93 fcpServer.writeLine("SSKKeypair",
94 "InsertURI=" + INSERT_URI + "",
95 "RequestURI=" + REQUEST_URI + "",
96 "Identifier=" + identifier,
98 FcpKeyPair keyPair = keyPairFuture.get();
99 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
100 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
103 private void connectNode() throws InterruptedException, ExecutionException, IOException {
104 fcpServer.connect().get();
105 fcpServer.collectUntil(is("EndMessage"));
106 fcpServer.writeLine("NodeHello",
107 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
108 "Revision=build01466",
110 "Version=Fred,0.7,1.0,1466",
112 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
116 "NodeLanguage=ENGLISH",
123 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
124 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
126 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
127 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
128 String identifier = extractIdentifier(lines);
131 "Identifier=" + identifier,
133 "StartupTime=1435610539000",
134 "CompletionTime=1435610540000",
135 "Metadata.ContentType=text/plain;charset=utf-8",
139 Optional<Data> data = dataFuture.get();
140 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
141 assertThat(data.get().size(), is(6L));
142 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
143 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
146 private String extractIdentifier(List<String> lines) {
147 return lines.stream()
148 .filter(s -> s.startsWith("Identifier="))
149 .map(s -> s.substring(s.indexOf('=') + 1))
155 public void clientGetDownloadsDataForCorrectIdentifier()
156 throws InterruptedException, ExecutionException, IOException {
157 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
159 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
160 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
161 String identifier = extractIdentifier(lines);
164 "Identifier=not-test",
166 "StartupTime=1435610539000",
167 "CompletionTime=1435610540000",
168 "Metadata.ContentType=text/plain;charset=latin-9",
174 "Identifier=" + identifier,
176 "StartupTime=1435610539000",
177 "CompletionTime=1435610540000",
178 "Metadata.ContentType=text/plain;charset=utf-8",
182 Optional<Data> data = dataFuture.get();
183 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
184 assertThat(data.get().size(), is(6L));
185 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
186 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
190 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
191 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
193 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
194 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
195 String identifier = extractIdentifier(lines);
198 "Identifier=" + identifier,
202 Optional<Data> data = dataFuture.get();
203 assertThat(data.isPresent(), is(false));
207 public void clientGetRecognizesGetFailedForCorrectIdentifier()
208 throws InterruptedException, ExecutionException, IOException {
209 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
211 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
212 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
213 String identifier = extractIdentifier(lines);
216 "Identifier=not-test",
222 "Identifier=" + identifier,
226 Optional<Data> data = dataFuture.get();
227 assertThat(data.isPresent(), is(false));
230 @Test(expected = ExecutionException.class)
231 public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
232 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
234 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
235 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
241 public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
242 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
244 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
245 String identifier = extractIdentifier(lines);
248 "InsertURI=" + INSERT_URI + "",
249 "RequestURI=" + REQUEST_URI + "",
250 "Identifier=" + identifier,
254 keyPair = fcpClient.generateKeypair().execute();
255 lines = fcpServer.collectUntil(is("EndMessage"));
256 identifier = extractIdentifier(lines);
259 "InsertURI=" + INSERT_URI + "",
260 "RequestURI=" + REQUEST_URI + "",
261 "Identifier=" + identifier,
268 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
269 throws InterruptedException, ExecutionException, IOException {
270 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
272 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
273 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
277 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
278 throws InterruptedException, ExecutionException, IOException {
279 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
281 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
282 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
286 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
287 throws InterruptedException, ExecutionException, IOException {
288 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
290 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
291 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
295 public void clientGetWithPrioritySettingSendsCorrectCommands()
296 throws InterruptedException, ExecutionException, IOException {
297 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
299 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
300 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
304 public void clientGetWithRealTimeSettingSendsCorrectCommands()
305 throws InterruptedException, ExecutionException, IOException {
306 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
308 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
309 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
313 public void clientGetWithGlobalSettingSendsCorrectCommands()
314 throws InterruptedException, ExecutionException, IOException {
315 fcpClient.clientGet().global().uri("KSK@foo.txt");
317 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
318 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
321 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
322 return new TypeSafeDiagnosingMatcher<List<String>>() {
324 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
325 if (!item.get(0).equals(name)) {
326 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
329 for (String requiredLine : requiredLines) {
330 if (item.indexOf(requiredLine) < 1) {
331 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
339 public void describeTo(Description description) {
340 description.appendText("FCP message named ").appendValue(name);
341 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
347 public void clientPutWithDirectDataSendsCorrectCommand()
348 throws IOException, ExecutionException, InterruptedException {
349 fcpClient.clientPut()
350 .from(new ByteArrayInputStream("Hello\n".getBytes()))
354 List<String> lines = fcpServer.collectUntil(is("Hello"));
355 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
359 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
360 throws InterruptedException, ExecutionException, IOException {
361 Future<Optional<Key>> key = fcpClient.clientPut()
362 .from(new ByteArrayInputStream("Hello\n".getBytes()))
366 List<String> lines = fcpServer.collectUntil(is("Hello"));
367 String identifier = extractIdentifier(lines);
370 "Identifier=not-the-right-one",
376 "Identifier=" + identifier,
379 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
383 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
384 throws InterruptedException, ExecutionException, IOException {
385 Future<Optional<Key>> key = fcpClient.clientPut()
386 .from(new ByteArrayInputStream("Hello\n".getBytes()))
390 List<String> lines = fcpServer.collectUntil(is("Hello"));
391 String identifier = extractIdentifier(lines);
394 "Identifier=not-the-right-one",
400 "Identifier=" + identifier,
403 assertThat(key.get().isPresent(), is(false));
407 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
408 throws InterruptedException, ExecutionException, IOException {
409 fcpClient.clientPut()
410 .named("otherName.txt")
411 .from(new ByteArrayInputStream("Hello\n".getBytes()))
415 List<String> lines = fcpServer.collectUntil(is("Hello"));
416 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
417 "DataLength=6", "URI=KSK@foo.txt"));
421 public void clientPutWithRedirectSendsCorrectCommand()
422 throws IOException, ExecutionException, InterruptedException {
423 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt");
425 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
427 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
431 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
432 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
434 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
436 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
440 public void clientPutWithFileCanCompleteTestDdaSequence()
441 throws IOException, ExecutionException, InterruptedException {
442 File tempFile = createTempFile();
443 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
445 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
446 String identifier = extractIdentifier(lines);
449 "Identifier=" + identifier,
453 lines = fcpServer.collectUntil(is("EndMessage"));
454 assertThat(lines, matchesFcpMessage(
456 "Directory=" + tempFile.getParent(),
457 "WantReadDirectory=true",
458 "WantWriteDirectory=false",
463 "Directory=" + tempFile.getParent(),
464 "ReadFilename=" + tempFile,
467 lines = fcpServer.collectUntil(is("EndMessage"));
468 assertThat(lines, matchesFcpMessage(
470 "Directory=" + tempFile.getParent(),
471 "ReadContent=test-content",
476 "Directory=" + tempFile.getParent(),
477 "ReadDirectoryAllowed=true",
480 lines = fcpServer.collectUntil(is("EndMessage"));
482 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
483 "Filename=" + new File(tempFile.getParent(), "test.dat")));
486 private File createTempFile() throws IOException {
487 File tempFile = File.createTempFile("test-dda-", ".dat");
488 tempFile.deleteOnExit();
489 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
494 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
495 throws InterruptedException, ExecutionException, IOException {
496 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
498 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
499 String identifier = extractIdentifier(lines);
502 "Identifier=not-the-right-one",
508 "Identifier=" + identifier,
512 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
516 public void clientPutAbortsOnProtocolErrorOtherThan25()
517 throws InterruptedException, ExecutionException, IOException {
518 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
520 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
521 String identifier = extractIdentifier(lines);
524 "Identifier=" + identifier,
528 assertThat(key.get().isPresent(), is(false));
532 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
533 InterruptedException {
534 File tempFile = createTempFile();
535 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
537 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
538 String identifier = extractIdentifier(lines);
541 "Identifier=" + identifier,
545 lines = fcpServer.collectUntil(is("EndMessage"));
546 assertThat(lines, matchesFcpMessage(
548 "Directory=" + tempFile.getParent(),
549 "WantReadDirectory=true",
550 "WantWriteDirectory=false",
555 "Directory=/some-other-directory",
556 "ReadFilename=" + tempFile,
561 "Directory=" + tempFile.getParent(),
562 "ReadFilename=" + tempFile,
565 lines = fcpServer.collectUntil(is("EndMessage"));
566 assertThat(lines, matchesFcpMessage(
568 "Directory=" + tempFile.getParent(),
569 "ReadContent=test-content",
575 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
576 throws IOException, ExecutionException, InterruptedException {
577 File tempFile = createTempFile();
578 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
580 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
581 String identifier = extractIdentifier(lines);
584 "Identifier=" + identifier,
588 lines = fcpServer.collectUntil(is("EndMessage"));
589 assertThat(lines, matchesFcpMessage(
591 "Directory=" + tempFile.getParent(),
592 "WantReadDirectory=true",
593 "WantWriteDirectory=false",
598 "Directory=" + tempFile.getParent(),
599 "ReadFilename=" + tempFile + ".foo",
602 lines = fcpServer.collectUntil(is("EndMessage"));
603 assertThat(lines, matchesFcpMessage(
605 "Directory=" + tempFile.getParent(),
606 "ReadContent=failed-to-read",
612 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
613 throws IOException, ExecutionException, InterruptedException {
614 File tempFile = createTempFile();
615 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
617 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
618 String identifier = extractIdentifier(lines);
621 "Directory=/some-other-directory",
626 "Identifier=" + identifier,
630 lines = fcpServer.collectUntil(is("EndMessage"));
631 assertThat(lines, matchesFcpMessage(
633 "Directory=" + tempFile.getParent(),
634 "WantReadDirectory=true",
635 "WantWriteDirectory=false",
641 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
642 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
644 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
645 assertThat(lines, matchesFcpMessage(
647 "WithVolatile=false",
648 "WithMetadata=false",
651 String identifier = extractIdentifier(lines);
654 "Identifier=" + identifier,
660 "Identifier=" + identifier,
666 "Identifier=" + identifier,
669 assertThat(peers.get(), hasSize(2));
670 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
671 containsInAnyOrder("id1", "id2"));
675 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
676 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
678 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
679 assertThat(lines, matchesFcpMessage(
681 "WithVolatile=false",
685 String identifier = extractIdentifier(lines);
688 "Identifier=" + identifier,
695 "Identifier=" + identifier,
702 "Identifier=" + identifier,
705 assertThat(peers.get(), hasSize(2));
706 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
707 containsInAnyOrder("bar1", "bar2"));
711 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
712 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
714 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
715 assertThat(lines, matchesFcpMessage(
718 "WithMetadata=false",
721 String identifier = extractIdentifier(lines);
724 "Identifier=" + identifier,
731 "Identifier=" + identifier,
738 "Identifier=" + identifier,
741 assertThat(peers.get(), hasSize(2));
742 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
743 containsInAnyOrder("bar1", "bar2"));