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",
82 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
83 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
85 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
86 String identifier = extractIdentifier(lines);
87 fcpServer.writeLine("SSKKeypair",
88 "InsertURI=" + INSERT_URI + "",
89 "RequestURI=" + REQUEST_URI + "",
90 "Identifier=" + identifier,
92 FcpKeyPair keyPair = keyPairFuture.get();
93 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
94 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
97 private void connectNode() throws InterruptedException, ExecutionException, IOException {
98 fcpServer.connect().get();
99 fcpServer.collectUntil(is("EndMessage"));
100 fcpServer.writeLine("NodeHello",
101 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
102 "Revision=build01466",
104 "Version=Fred,0.7,1.0,1466",
106 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
110 "NodeLanguage=ENGLISH",
117 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
118 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
120 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
121 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
122 String identifier = extractIdentifier(lines);
125 "Identifier=" + identifier,
127 "StartupTime=1435610539000",
128 "CompletionTime=1435610540000",
129 "Metadata.ContentType=text/plain;charset=utf-8",
133 Optional<Data> data = dataFuture.get();
134 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
135 assertThat(data.get().size(), is(6L));
136 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
137 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
140 private String extractIdentifier(List<String> lines) {
141 return lines.stream()
142 .filter(s -> s.startsWith("Identifier="))
143 .map(s -> s.substring(s.indexOf('=') + 1))
149 public void clientGetDownloadsDataForCorrectIdentifier()
150 throws InterruptedException, ExecutionException, IOException {
151 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
153 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
154 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
155 String identifier = extractIdentifier(lines);
158 "Identifier=not-test",
160 "StartupTime=1435610539000",
161 "CompletionTime=1435610540000",
162 "Metadata.ContentType=text/plain;charset=latin-9",
168 "Identifier=" + identifier,
170 "StartupTime=1435610539000",
171 "CompletionTime=1435610540000",
172 "Metadata.ContentType=text/plain;charset=utf-8",
176 Optional<Data> data = dataFuture.get();
177 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
178 assertThat(data.get().size(), is(6L));
179 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
180 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
184 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
185 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
187 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
188 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
189 String identifier = extractIdentifier(lines);
192 "Identifier=" + identifier,
196 Optional<Data> data = dataFuture.get();
197 assertThat(data.isPresent(), is(false));
201 public void clientGetRecognizesGetFailedForCorrectIdentifier()
202 throws InterruptedException, ExecutionException, IOException {
203 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
205 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
206 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
207 String identifier = extractIdentifier(lines);
210 "Identifier=not-test",
216 "Identifier=" + identifier,
220 Optional<Data> data = dataFuture.get();
221 assertThat(data.isPresent(), is(false));
225 public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
226 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
228 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
229 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
231 Optional<Data> data = dataFuture.get();
232 assertThat(data.isPresent(), is(false));
236 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
237 throws InterruptedException, ExecutionException, IOException {
238 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
240 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
241 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
245 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
246 throws InterruptedException, ExecutionException, IOException {
247 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
249 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
250 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
254 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
255 throws InterruptedException, ExecutionException, IOException {
256 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
258 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
259 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
263 public void clientGetWithPrioritySettingSendsCorrectCommands()
264 throws InterruptedException, ExecutionException, IOException {
265 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
267 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
268 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
272 public void clientGetWithRealTimeSettingSendsCorrectCommands()
273 throws InterruptedException, ExecutionException, IOException {
274 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
276 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
277 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
281 public void clientGetWithGlobalSettingSendsCorrectCommands()
282 throws InterruptedException, ExecutionException, IOException {
283 fcpClient.clientGet().global().uri("KSK@foo.txt");
285 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
286 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
289 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
290 return new TypeSafeDiagnosingMatcher<List<String>>() {
292 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
293 if (!item.get(0).equals(name)) {
294 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
297 for (String requiredLine : requiredLines) {
298 if (item.indexOf(requiredLine) < 1) {
299 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
307 public void describeTo(Description description) {
308 description.appendText("FCP message named ").appendValue(name);
309 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
315 public void clientPutWithDirectDataSendsCorrectCommand()
316 throws IOException, ExecutionException, InterruptedException {
317 fcpClient.clientPut()
318 .from(new ByteArrayInputStream("Hello\n".getBytes()))
322 List<String> lines = fcpServer.collectUntil(is("Hello"));
323 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
327 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
328 throws InterruptedException, ExecutionException, IOException {
329 Future<Optional<Key>> key = fcpClient.clientPut()
330 .from(new ByteArrayInputStream("Hello\n".getBytes()))
334 List<String> lines = fcpServer.collectUntil(is("Hello"));
335 String identifier = extractIdentifier(lines);
338 "Identifier=not-the-right-one",
344 "Identifier=" + identifier,
347 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
351 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
352 throws InterruptedException, ExecutionException, IOException {
353 Future<Optional<Key>> key = fcpClient.clientPut()
354 .from(new ByteArrayInputStream("Hello\n".getBytes()))
358 List<String> lines = fcpServer.collectUntil(is("Hello"));
359 String identifier = extractIdentifier(lines);
362 "Identifier=not-the-right-one",
368 "Identifier=" + identifier,
371 assertThat(key.get().isPresent(), is(false));
375 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
376 throws InterruptedException, ExecutionException, IOException {
377 fcpClient.clientPut()
378 .named("otherName.txt")
379 .from(new ByteArrayInputStream("Hello\n".getBytes()))
383 List<String> lines = fcpServer.collectUntil(is("Hello"));
384 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
385 "DataLength=6", "URI=KSK@foo.txt"));
389 public void clientPutWithRedirectSendsCorrectCommand()
390 throws IOException, ExecutionException, InterruptedException {
391 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt");
393 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
395 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
399 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
400 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
402 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
404 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
408 public void clientPutWithFileCanCompleteTestDdaSequence()
409 throws IOException, ExecutionException, InterruptedException {
410 File tempFile = createTempFile();
411 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
413 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
414 String identifier = extractIdentifier(lines);
417 "Identifier=" + identifier,
421 lines = fcpServer.collectUntil(is("EndMessage"));
422 assertThat(lines, matchesFcpMessage(
424 "Directory=" + tempFile.getParent(),
425 "WantReadDirectory=true",
426 "WantWriteDirectory=false",
431 "Directory=" + tempFile.getParent(),
432 "ReadFilename=" + tempFile,
435 lines = fcpServer.collectUntil(is("EndMessage"));
436 assertThat(lines, matchesFcpMessage(
438 "Directory=" + tempFile.getParent(),
439 "ReadContent=test-content",
444 "Directory=" + tempFile.getParent(),
445 "ReadDirectoryAllowed=true",
448 lines = fcpServer.collectUntil(is("EndMessage"));
450 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
451 "Filename=" + new File(tempFile.getParent(), "test.dat")));
454 private File createTempFile() throws IOException {
455 File tempFile = File.createTempFile("test-dda-", ".dat");
456 tempFile.deleteOnExit();
457 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
462 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
463 throws InterruptedException, ExecutionException, IOException {
464 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
466 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
467 String identifier = extractIdentifier(lines);
470 "Identifier=not-the-right-one",
476 "Identifier=" + identifier,
480 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
484 public void clientPutAbortsOnProtocolErrorOtherThan25()
485 throws InterruptedException, ExecutionException, IOException {
486 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
488 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
489 String identifier = extractIdentifier(lines);
492 "Identifier=" + identifier,
496 assertThat(key.get().isPresent(), is(false));
500 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
501 InterruptedException {
502 File tempFile = createTempFile();
503 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
505 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
506 String identifier = extractIdentifier(lines);
509 "Identifier=" + identifier,
513 lines = fcpServer.collectUntil(is("EndMessage"));
514 assertThat(lines, matchesFcpMessage(
516 "Directory=" + tempFile.getParent(),
517 "WantReadDirectory=true",
518 "WantWriteDirectory=false",
523 "Directory=/some-other-directory",
524 "ReadFilename=" + tempFile,
529 "Directory=" + tempFile.getParent(),
530 "ReadFilename=" + tempFile,
533 lines = fcpServer.collectUntil(is("EndMessage"));
534 assertThat(lines, matchesFcpMessage(
536 "Directory=" + tempFile.getParent(),
537 "ReadContent=test-content",
543 public void clientPutSendsResponseEvenIfFileCanNotBeRead()
544 throws IOException, ExecutionException, InterruptedException {
545 File tempFile = createTempFile();
546 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
548 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
549 String identifier = extractIdentifier(lines);
552 "Identifier=" + identifier,
556 lines = fcpServer.collectUntil(is("EndMessage"));
557 assertThat(lines, matchesFcpMessage(
559 "Directory=" + tempFile.getParent(),
560 "WantReadDirectory=true",
561 "WantWriteDirectory=false",
566 "Directory=" + tempFile.getParent(),
567 "ReadFilename=" + tempFile + ".foo",
570 lines = fcpServer.collectUntil(is("EndMessage"));
571 assertThat(lines, matchesFcpMessage(
573 "Directory=" + tempFile.getParent(),
574 "ReadContent=failed-to-read",
580 public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
581 throws IOException, ExecutionException, InterruptedException {
582 File tempFile = createTempFile();
583 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
585 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
586 String identifier = extractIdentifier(lines);
589 "Directory=/some-other-directory",
594 "Identifier=" + identifier,
598 lines = fcpServer.collectUntil(is("EndMessage"));
599 assertThat(lines, matchesFcpMessage(
601 "Directory=" + tempFile.getParent(),
602 "WantReadDirectory=true",
603 "WantWriteDirectory=false",
609 public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
610 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
612 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
613 assertThat(lines, matchesFcpMessage(
615 "WithVolatile=false",
616 "WithMetadata=false",
619 String identifier = extractIdentifier(lines);
622 "Identifier=" + identifier,
628 "Identifier=" + identifier,
634 "Identifier=" + identifier,
637 assertThat(peers.get(), hasSize(2));
638 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
639 containsInAnyOrder("id1", "id2"));
643 public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
644 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
646 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
647 assertThat(lines, matchesFcpMessage(
649 "WithVolatile=false",
653 String identifier = extractIdentifier(lines);
656 "Identifier=" + identifier,
663 "Identifier=" + identifier,
670 "Identifier=" + identifier,
673 assertThat(peers.get(), hasSize(2));
674 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
675 containsInAnyOrder("bar1", "bar2"));
679 public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
680 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
682 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
683 assertThat(lines, matchesFcpMessage(
686 "WithMetadata=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.getVolatile("foo")).collect(Collectors.toList()),
711 containsInAnyOrder("bar1", "bar2"));