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 {
65 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
66 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
68 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
69 String identifier = extractIdentifier(lines);
70 fcpServer.writeLine("SSKKeypair",
71 "InsertURI=" + INSERT_URI + "",
72 "RequestURI=" + REQUEST_URI + "",
73 "Identifier=" + identifier,
75 FcpKeyPair keyPair = keyPairFuture.get();
76 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
77 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
80 private void connectNode() throws InterruptedException, ExecutionException, IOException {
81 fcpServer.connect().get();
82 fcpServer.collectUntil(is("EndMessage"));
83 fcpServer.writeLine("NodeHello",
84 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
85 "Revision=build01466",
87 "Version=Fred,0.7,1.0,1466",
89 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
93 "NodeLanguage=ENGLISH",
100 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
101 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
103 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
104 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
105 String identifier = extractIdentifier(lines);
108 "Identifier=" + identifier,
110 "StartupTime=1435610539000",
111 "CompletionTime=1435610540000",
112 "Metadata.ContentType=text/plain;charset=utf-8",
116 Optional<Data> data = dataFuture.get();
117 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
118 assertThat(data.get().size(), is(6L));
119 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
120 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
123 private String extractIdentifier(List<String> lines) {
124 return lines.stream()
125 .filter(s -> s.startsWith("Identifier="))
126 .map(s -> s.substring(s.indexOf('=') + 1))
132 public void clientGetDownloadsDataForCorrectIdentifier()
133 throws InterruptedException, ExecutionException, IOException {
134 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
136 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
137 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
138 String identifier = extractIdentifier(lines);
141 "Identifier=not-test",
143 "StartupTime=1435610539000",
144 "CompletionTime=1435610540000",
145 "Metadata.ContentType=text/plain;charset=latin-9",
151 "Identifier=" + identifier,
153 "StartupTime=1435610539000",
154 "CompletionTime=1435610540000",
155 "Metadata.ContentType=text/plain;charset=utf-8",
159 Optional<Data> data = dataFuture.get();
160 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
161 assertThat(data.get().size(), is(6L));
162 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
163 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
167 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
168 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
170 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
171 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
172 String identifier = extractIdentifier(lines);
175 "Identifier=" + identifier,
179 Optional<Data> data = dataFuture.get();
180 assertThat(data.isPresent(), is(false));
184 public void clientGetRecognizesGetFailedForCorrectIdentifier()
185 throws InterruptedException, ExecutionException, IOException {
186 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
188 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
189 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
190 String identifier = extractIdentifier(lines);
193 "Identifier=not-test",
199 "Identifier=" + identifier,
203 Optional<Data> data = dataFuture.get();
204 assertThat(data.isPresent(), is(false));
208 public void clientGetRecognizesConnectionClosed() 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"));
214 Optional<Data> data = dataFuture.get();
215 assertThat(data.isPresent(), is(false));
219 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
220 throws InterruptedException, ExecutionException, IOException {
221 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
223 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
224 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
228 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
229 throws InterruptedException, ExecutionException, IOException {
230 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
232 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
233 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
237 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
238 throws InterruptedException, ExecutionException, IOException {
239 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
241 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
242 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
246 public void clientGetWithPrioritySettingSendsCorrectCommands()
247 throws InterruptedException, ExecutionException, IOException {
248 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
250 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
251 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
255 public void clientGetWithRealTimeSettingSendsCorrectCommands()
256 throws InterruptedException, ExecutionException, IOException {
257 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
259 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
260 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
264 public void clientGetWithGlobalSettingSendsCorrectCommands()
265 throws InterruptedException, ExecutionException, IOException {
266 fcpClient.clientGet().global().uri("KSK@foo.txt");
268 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
269 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
272 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
273 return new TypeSafeDiagnosingMatcher<List<String>>() {
275 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
276 if (!item.get(0).equals(name)) {
277 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
280 for (String requiredLine : requiredLines) {
281 if (item.indexOf(requiredLine) < 1) {
282 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
290 public void describeTo(Description description) {
291 description.appendText("FCP message named ").appendValue(name);
292 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
298 public void clientPutWithDirectDataSendsCorrectCommand()
299 throws IOException, ExecutionException, InterruptedException {
300 fcpClient.clientPut()
301 .from(new ByteArrayInputStream("Hello\n".getBytes()))
305 List<String> lines = fcpServer.collectUntil(is("Hello"));
306 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
310 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
311 throws InterruptedException, ExecutionException, IOException {
312 Future<Optional<Key>> key = fcpClient.clientPut()
313 .from(new ByteArrayInputStream("Hello\n".getBytes()))
317 List<String> lines = fcpServer.collectUntil(is("Hello"));
318 String identifier = extractIdentifier(lines);
321 "Identifier=not-the-right-one",
327 "Identifier=" + identifier,
330 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
334 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
335 throws InterruptedException, ExecutionException, IOException {
336 Future<Optional<Key>> key = fcpClient.clientPut()
337 .from(new ByteArrayInputStream("Hello\n".getBytes()))
341 List<String> lines = fcpServer.collectUntil(is("Hello"));
342 String identifier = extractIdentifier(lines);
345 "Identifier=not-the-right-one",
351 "Identifier=" + identifier,
354 assertThat(key.get().isPresent(), is(false));
358 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
359 throws InterruptedException, ExecutionException, IOException {
360 fcpClient.clientPut()
361 .named("otherName.txt")
362 .from(new ByteArrayInputStream("Hello\n".getBytes()))
366 List<String> lines = fcpServer.collectUntil(is("Hello"));
367 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
368 "DataLength=6", "URI=KSK@foo.txt"));
372 public void clientPutWithRedirectSendsCorrectCommand()
373 throws IOException, ExecutionException, InterruptedException {
374 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt");
376 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
378 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
382 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
383 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
385 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
387 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
391 public void clientPutWithFileCanCompleteTestDdaSequence()
392 throws IOException, ExecutionException, InterruptedException {
393 File tempFile = createTempFile();
394 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
396 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
397 String identifier = extractIdentifier(lines);
400 "Identifier=" + identifier,
404 lines = fcpServer.collectUntil(is("EndMessage"));
405 assertThat(lines, matchesFcpMessage(
407 "Directory=" + tempFile.getParent(),
408 "WantReadDirectory=true",
409 "WantWriteDirectory=false",
414 "Directory=" + tempFile.getParent(),
415 "ReadFilename=" + tempFile,
418 lines = fcpServer.collectUntil(is("EndMessage"));
419 assertThat(lines, matchesFcpMessage(
421 "Directory=" + tempFile.getParent(),
422 "ReadContent=test-content",
427 "Directory=" + tempFile.getParent(),
428 "ReadDirectoryAllowed=true",
431 lines = fcpServer.collectUntil(is("EndMessage"));
433 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
434 "Filename=" + new File(tempFile.getParent(), "test.dat")));
437 private File createTempFile() throws IOException {
438 File tempFile = File.createTempFile("test-dda-", ".dat");
439 tempFile.deleteOnExit();
440 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
445 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
446 throws InterruptedException, ExecutionException, IOException {
447 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
449 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
450 String identifier = extractIdentifier(lines);
453 "Identifier=not-the-right-one",
459 "Identifier=" + identifier,
463 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
467 public void clientPutAbortsOnProtocolErrorOtherThan25()
468 throws InterruptedException, ExecutionException, IOException {
469 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
471 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
472 String identifier = extractIdentifier(lines);
475 "Identifier=" + identifier,
479 assertThat(key.get().isPresent(), is(false));
483 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
484 InterruptedException {
485 File tempFile = createTempFile();
486 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
488 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
489 String identifier = extractIdentifier(lines);
492 "Identifier=" + identifier,
496 lines = fcpServer.collectUntil(is("EndMessage"));
497 assertThat(lines, matchesFcpMessage(
499 "Directory=" + tempFile.getParent(),
500 "WantReadDirectory=true",
501 "WantWriteDirectory=false",
506 "Directory=/some-other-directory",
507 "ReadFilename=" + tempFile,
512 "Directory=" + tempFile.getParent(),
513 "ReadFilename=" + tempFile,
516 lines = fcpServer.collectUntil(is("EndMessage"));
517 assertThat(lines, matchesFcpMessage(
519 "Directory=" + tempFile.getParent(),
520 "ReadContent=test-content",
526 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
527 throws IOException, ExecutionException, InterruptedException {
528 File tempFile = createTempFile();
529 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
531 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
532 String identifier = extractIdentifier(lines);
535 "Identifier=" + identifier,
539 lines = fcpServer.collectUntil(is("EndMessage"));
540 assertThat(lines, matchesFcpMessage(
542 "Directory=" + tempFile.getParent(),
543 "WantReadDirectory=true",
544 "WantWriteDirectory=false",
549 "Directory=" + tempFile.getParent(),
550 "ReadFilename=" + tempFile + ".foo",
553 lines = fcpServer.collectUntil(is("EndMessage"));
554 assertThat(lines, matchesFcpMessage(
556 "Directory=" + tempFile.getParent(),
557 "ReadContent=failed-to-read",
563 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
564 throws IOException, ExecutionException, InterruptedException {
565 File tempFile = createTempFile();
566 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
568 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
569 String identifier = extractIdentifier(lines);
572 "Directory=/some-other-directory",
577 "Identifier=" + identifier,
581 lines = fcpServer.collectUntil(is("EndMessage"));
582 assertThat(lines, matchesFcpMessage(
584 "Directory=" + tempFile.getParent(),
585 "WantReadDirectory=true",
586 "WantWriteDirectory=false",
592 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
593 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
595 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
596 assertThat(lines, matchesFcpMessage(
598 "WithVolatile=false",
599 "WithMetadata=false",
602 String identifier = extractIdentifier(lines);
605 "Identifier=" + identifier,
611 "Identifier=" + identifier,
617 "Identifier=" + identifier,
620 assertThat(peers.get(), hasSize(2));
621 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
622 containsInAnyOrder("id1", "id2"));
626 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
627 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
629 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
630 assertThat(lines, matchesFcpMessage(
632 "WithVolatile=false",
636 String identifier = extractIdentifier(lines);
639 "Identifier=" + identifier,
646 "Identifier=" + identifier,
653 "Identifier=" + identifier,
656 assertThat(peers.get(), hasSize(2));
657 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
658 containsInAnyOrder("bar1", "bar2"));
662 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
663 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
665 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
666 assertThat(lines, matchesFcpMessage(
669 "WithMetadata=false",
672 String identifier = extractIdentifier(lines);
675 "Identifier=" + identifier,
682 "Identifier=" + identifier,
689 "Identifier=" + identifier,
692 assertThat(peers.get(), hasSize(2));
693 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
694 containsInAnyOrder("bar1", "bar2"));