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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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").execute();
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()))
355 List<String> lines = fcpServer.collectUntil(is("Hello"));
356 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
360 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
361 throws InterruptedException, ExecutionException, IOException {
362 Future<Optional<Key>> key = fcpClient.clientPut()
363 .from(new ByteArrayInputStream("Hello\n".getBytes()))
368 List<String> lines = fcpServer.collectUntil(is("Hello"));
369 String identifier = extractIdentifier(lines);
372 "Identifier=not-the-right-one",
378 "Identifier=" + identifier,
381 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
385 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
386 throws InterruptedException, ExecutionException, IOException {
387 Future<Optional<Key>> key = fcpClient.clientPut()
388 .from(new ByteArrayInputStream("Hello\n".getBytes()))
393 List<String> lines = fcpServer.collectUntil(is("Hello"));
394 String identifier = extractIdentifier(lines);
397 "Identifier=not-the-right-one",
403 "Identifier=" + identifier,
406 assertThat(key.get().isPresent(), is(false));
410 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
411 throws InterruptedException, ExecutionException, IOException {
412 fcpClient.clientPut()
413 .named("otherName.txt")
414 .from(new ByteArrayInputStream("Hello\n".getBytes()))
419 List<String> lines = fcpServer.collectUntil(is("Hello"));
420 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
421 "DataLength=6", "URI=KSK@foo.txt"));
425 public void clientPutWithRedirectSendsCorrectCommand()
426 throws IOException, ExecutionException, InterruptedException {
427 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
429 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
431 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
435 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
436 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
438 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
440 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
444 public void clientPutWithFileCanCompleteTestDdaSequence()
445 throws IOException, ExecutionException, InterruptedException {
446 File tempFile = createTempFile();
447 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
449 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
450 String identifier = extractIdentifier(lines);
453 "Identifier=" + identifier,
457 lines = fcpServer.collectUntil(is("EndMessage"));
458 assertThat(lines, matchesFcpMessage(
460 "Directory=" + tempFile.getParent(),
461 "WantReadDirectory=true",
462 "WantWriteDirectory=false",
467 "Directory=" + tempFile.getParent(),
468 "ReadFilename=" + tempFile,
471 lines = fcpServer.collectUntil(is("EndMessage"));
472 assertThat(lines, matchesFcpMessage(
474 "Directory=" + tempFile.getParent(),
475 "ReadContent=test-content",
480 "Directory=" + tempFile.getParent(),
481 "ReadDirectoryAllowed=true",
484 lines = fcpServer.collectUntil(is("EndMessage"));
486 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
487 "Filename=" + new File(tempFile.getParent(), "test.dat")));
490 private File createTempFile() throws IOException {
491 File tempFile = File.createTempFile("test-dda-", ".dat");
492 tempFile.deleteOnExit();
493 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
498 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
499 throws InterruptedException, ExecutionException, IOException {
500 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
502 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
503 String identifier = extractIdentifier(lines);
506 "Identifier=not-the-right-one",
512 "Identifier=" + identifier,
516 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
520 public void clientPutAbortsOnProtocolErrorOtherThan25()
521 throws InterruptedException, ExecutionException, IOException {
522 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
524 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
525 String identifier = extractIdentifier(lines);
528 "Identifier=" + identifier,
532 assertThat(key.get().isPresent(), is(false));
536 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
537 InterruptedException {
538 File tempFile = createTempFile();
539 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
541 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
542 String identifier = extractIdentifier(lines);
545 "Identifier=" + identifier,
549 lines = fcpServer.collectUntil(is("EndMessage"));
550 assertThat(lines, matchesFcpMessage(
552 "Directory=" + tempFile.getParent(),
553 "WantReadDirectory=true",
554 "WantWriteDirectory=false",
559 "Directory=/some-other-directory",
560 "ReadFilename=" + tempFile,
565 "Directory=" + tempFile.getParent(),
566 "ReadFilename=" + tempFile,
569 lines = fcpServer.collectUntil(is("EndMessage"));
570 assertThat(lines, matchesFcpMessage(
572 "Directory=" + tempFile.getParent(),
573 "ReadContent=test-content",
579 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
580 throws IOException, ExecutionException, InterruptedException {
581 File tempFile = createTempFile();
582 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
584 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
585 String identifier = extractIdentifier(lines);
588 "Identifier=" + identifier,
592 lines = fcpServer.collectUntil(is("EndMessage"));
593 assertThat(lines, matchesFcpMessage(
595 "Directory=" + tempFile.getParent(),
596 "WantReadDirectory=true",
597 "WantWriteDirectory=false",
602 "Directory=" + tempFile.getParent(),
603 "ReadFilename=" + tempFile + ".foo",
606 lines = fcpServer.collectUntil(is("EndMessage"));
607 assertThat(lines, matchesFcpMessage(
609 "Directory=" + tempFile.getParent(),
610 "ReadContent=failed-to-read",
616 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
617 throws IOException, ExecutionException, InterruptedException {
618 File tempFile = createTempFile();
619 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
621 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
622 String identifier = extractIdentifier(lines);
625 "Directory=/some-other-directory",
630 "Identifier=" + identifier,
634 lines = fcpServer.collectUntil(is("EndMessage"));
635 assertThat(lines, matchesFcpMessage(
637 "Directory=" + tempFile.getParent(),
638 "WantReadDirectory=true",
639 "WantWriteDirectory=false",
645 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
646 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
648 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
649 assertThat(lines, matchesFcpMessage(
651 "WithVolatile=false",
652 "WithMetadata=false",
655 String identifier = extractIdentifier(lines);
658 "Identifier=" + identifier,
664 "Identifier=" + identifier,
670 "Identifier=" + identifier,
673 assertThat(peers.get(), hasSize(2));
674 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
675 containsInAnyOrder("id1", "id2"));
679 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
680 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
682 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
683 assertThat(lines, matchesFcpMessage(
685 "WithVolatile=false",
689 String identifier = extractIdentifier(lines);
692 "Identifier=" + identifier,
699 "Identifier=" + identifier,
706 "Identifier=" + identifier,
709 assertThat(peers.get(), hasSize(2));
710 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
711 containsInAnyOrder("bar1", "bar2"));
715 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
716 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
718 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
719 assertThat(lines, matchesFcpMessage(
722 "WithMetadata=false",
725 String identifier = extractIdentifier(lines);
728 "Identifier=" + identifier,
735 "Identifier=" + identifier,
742 "Identifier=" + identifier,
745 assertThat(peers.get(), hasSize(2));
746 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
747 containsInAnyOrder("bar1", "bar2"));