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 defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
248 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
250 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
251 String identifier = extractIdentifier(lines);
254 "InsertURI=" + INSERT_URI + "",
255 "RequestURI=" + REQUEST_URI + "",
256 "Identifier=" + identifier,
260 keyPair = fcpClient.generateKeypair().execute();
261 lines = fcpServer.collectUntil(is("EndMessage"));
262 identifier = extractIdentifier(lines);
265 "InsertURI=" + INSERT_URI + "",
266 "RequestURI=" + REQUEST_URI + "",
267 "Identifier=" + identifier,
274 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
275 throws InterruptedException, ExecutionException, IOException {
276 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
278 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
279 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
283 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
284 throws InterruptedException, ExecutionException, IOException {
285 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
287 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
288 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
292 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
293 throws InterruptedException, ExecutionException, IOException {
294 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
296 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
297 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
301 public void clientGetWithPrioritySettingSendsCorrectCommands()
302 throws InterruptedException, ExecutionException, IOException {
303 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
305 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
306 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
310 public void clientGetWithRealTimeSettingSendsCorrectCommands()
311 throws InterruptedException, ExecutionException, IOException {
312 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
314 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
315 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
319 public void clientGetWithGlobalSettingSendsCorrectCommands()
320 throws InterruptedException, ExecutionException, IOException {
321 fcpClient.clientGet().global().uri("KSK@foo.txt");
323 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
324 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
327 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
328 return new TypeSafeDiagnosingMatcher<List<String>>() {
330 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
331 if (!item.get(0).equals(name)) {
332 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
335 for (String requiredLine : requiredLines) {
336 if (item.indexOf(requiredLine) < 1) {
337 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
345 public void describeTo(Description description) {
346 description.appendText("FCP message named ").appendValue(name);
347 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
353 public void clientPutWithDirectDataSendsCorrectCommand()
354 throws IOException, ExecutionException, InterruptedException {
355 fcpClient.clientPut()
356 .from(new ByteArrayInputStream("Hello\n".getBytes()))
360 List<String> lines = fcpServer.collectUntil(is("Hello"));
361 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
365 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
366 throws InterruptedException, ExecutionException, IOException {
367 Future<Optional<Key>> key = fcpClient.clientPut()
368 .from(new ByteArrayInputStream("Hello\n".getBytes()))
372 List<String> lines = fcpServer.collectUntil(is("Hello"));
373 String identifier = extractIdentifier(lines);
376 "Identifier=not-the-right-one",
382 "Identifier=" + identifier,
385 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
389 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
390 throws InterruptedException, ExecutionException, IOException {
391 Future<Optional<Key>> key = fcpClient.clientPut()
392 .from(new ByteArrayInputStream("Hello\n".getBytes()))
396 List<String> lines = fcpServer.collectUntil(is("Hello"));
397 String identifier = extractIdentifier(lines);
400 "Identifier=not-the-right-one",
406 "Identifier=" + identifier,
409 assertThat(key.get().isPresent(), is(false));
413 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
414 throws InterruptedException, ExecutionException, IOException {
415 fcpClient.clientPut()
416 .named("otherName.txt")
417 .from(new ByteArrayInputStream("Hello\n".getBytes()))
421 List<String> lines = fcpServer.collectUntil(is("Hello"));
422 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
423 "DataLength=6", "URI=KSK@foo.txt"));
427 public void clientPutWithRedirectSendsCorrectCommand()
428 throws IOException, ExecutionException, InterruptedException {
429 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt");
431 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
433 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
437 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
438 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
440 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
442 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
446 public void clientPutWithFileCanCompleteTestDdaSequence()
447 throws IOException, ExecutionException, InterruptedException {
448 File tempFile = createTempFile();
449 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
451 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
452 String identifier = extractIdentifier(lines);
455 "Identifier=" + identifier,
459 lines = fcpServer.collectUntil(is("EndMessage"));
460 assertThat(lines, matchesFcpMessage(
462 "Directory=" + tempFile.getParent(),
463 "WantReadDirectory=true",
464 "WantWriteDirectory=false",
469 "Directory=" + tempFile.getParent(),
470 "ReadFilename=" + tempFile,
473 lines = fcpServer.collectUntil(is("EndMessage"));
474 assertThat(lines, matchesFcpMessage(
476 "Directory=" + tempFile.getParent(),
477 "ReadContent=test-content",
482 "Directory=" + tempFile.getParent(),
483 "ReadDirectoryAllowed=true",
486 lines = fcpServer.collectUntil(is("EndMessage"));
488 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
489 "Filename=" + new File(tempFile.getParent(), "test.dat")));
492 private File createTempFile() throws IOException {
493 File tempFile = File.createTempFile("test-dda-", ".dat");
494 tempFile.deleteOnExit();
495 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
500 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
501 throws InterruptedException, ExecutionException, IOException {
502 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
504 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
505 String identifier = extractIdentifier(lines);
508 "Identifier=not-the-right-one",
514 "Identifier=" + identifier,
518 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
522 public void clientPutAbortsOnProtocolErrorOtherThan25()
523 throws InterruptedException, ExecutionException, IOException {
524 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
526 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
527 String identifier = extractIdentifier(lines);
530 "Identifier=" + identifier,
534 assertThat(key.get().isPresent(), is(false));
538 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
539 InterruptedException {
540 File tempFile = createTempFile();
541 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
543 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
544 String identifier = extractIdentifier(lines);
547 "Identifier=" + identifier,
551 lines = fcpServer.collectUntil(is("EndMessage"));
552 assertThat(lines, matchesFcpMessage(
554 "Directory=" + tempFile.getParent(),
555 "WantReadDirectory=true",
556 "WantWriteDirectory=false",
561 "Directory=/some-other-directory",
562 "ReadFilename=" + tempFile,
567 "Directory=" + tempFile.getParent(),
568 "ReadFilename=" + tempFile,
571 lines = fcpServer.collectUntil(is("EndMessage"));
572 assertThat(lines, matchesFcpMessage(
574 "Directory=" + tempFile.getParent(),
575 "ReadContent=test-content",
581 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
582 throws IOException, ExecutionException, InterruptedException {
583 File tempFile = createTempFile();
584 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
586 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
587 String identifier = extractIdentifier(lines);
590 "Identifier=" + identifier,
594 lines = fcpServer.collectUntil(is("EndMessage"));
595 assertThat(lines, matchesFcpMessage(
597 "Directory=" + tempFile.getParent(),
598 "WantReadDirectory=true",
599 "WantWriteDirectory=false",
604 "Directory=" + tempFile.getParent(),
605 "ReadFilename=" + tempFile + ".foo",
608 lines = fcpServer.collectUntil(is("EndMessage"));
609 assertThat(lines, matchesFcpMessage(
611 "Directory=" + tempFile.getParent(),
612 "ReadContent=failed-to-read",
618 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
619 throws IOException, ExecutionException, InterruptedException {
620 File tempFile = createTempFile();
621 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
623 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
624 String identifier = extractIdentifier(lines);
627 "Directory=/some-other-directory",
632 "Identifier=" + identifier,
636 lines = fcpServer.collectUntil(is("EndMessage"));
637 assertThat(lines, matchesFcpMessage(
639 "Directory=" + tempFile.getParent(),
640 "WantReadDirectory=true",
641 "WantWriteDirectory=false",
647 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
648 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
650 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
651 assertThat(lines, matchesFcpMessage(
653 "WithVolatile=false",
654 "WithMetadata=false",
657 String identifier = extractIdentifier(lines);
660 "Identifier=" + identifier,
666 "Identifier=" + identifier,
672 "Identifier=" + identifier,
675 assertThat(peers.get(), hasSize(2));
676 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
677 containsInAnyOrder("id1", "id2"));
681 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
682 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
684 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
685 assertThat(lines, matchesFcpMessage(
687 "WithVolatile=false",
691 String identifier = extractIdentifier(lines);
694 "Identifier=" + identifier,
701 "Identifier=" + identifier,
708 "Identifier=" + identifier,
711 assertThat(peers.get(), hasSize(2));
712 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
713 containsInAnyOrder("bar1", "bar2"));
717 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
718 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
720 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
721 assertThat(lines, matchesFcpMessage(
724 "WithMetadata=false",
727 String identifier = extractIdentifier(lines);
730 "Identifier=" + identifier,
737 "Identifier=" + identifier,
744 "Identifier=" + identifier,
747 assertThat(peers.get(), hasSize(2));
748 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
749 containsInAnyOrder("bar1", "bar2"));