1 package net.pterodactylus.fcp.quelaton;
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.is;
6 import java.io.ByteArrayInputStream;
8 import java.io.IOException;
9 import java.nio.charset.StandardCharsets;
10 import java.util.List;
11 import java.util.Optional;
12 import java.util.concurrent.ExecutionException;
13 import java.util.concurrent.ExecutorService;
14 import java.util.concurrent.Executors;
15 import java.util.concurrent.Future;
17 import net.pterodactylus.fcp.FcpKeyPair;
18 import net.pterodactylus.fcp.Key;
19 import net.pterodactylus.fcp.Priority;
20 import net.pterodactylus.fcp.fake.FakeTcpServer;
21 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
23 import com.google.common.io.ByteStreams;
24 import com.google.common.io.Files;
25 import org.hamcrest.Description;
26 import org.hamcrest.Matcher;
27 import org.hamcrest.TypeSafeDiagnosingMatcher;
28 import org.junit.After;
29 import org.junit.Test;
32 * Unit test for {@link DefaultFcpClient}.
34 * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
36 public class DefaultFcpClientTest {
38 private static final String INSERT_URI =
39 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
40 private static final String REQUEST_URI =
41 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
43 private static int threadCounter = 0;
44 private final ExecutorService threadPool =
45 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
46 private final FakeTcpServer fcpServer;
47 private final DefaultFcpClient fcpClient;
49 public DefaultFcpClientTest() throws IOException {
50 fcpServer = new FakeTcpServer(threadPool);
51 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test", () -> "2.0");
55 public void tearDown() throws IOException {
60 public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
61 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
63 fcpServer.collectUntil(is("EndMessage"));
64 fcpServer.writeLine("SSKKeypair",
65 "InsertURI=" + INSERT_URI + "",
66 "RequestURI=" + REQUEST_URI + "",
67 "Identifier=My Identifier from GenerateSSK",
69 FcpKeyPair keyPair = keyPairFuture.get();
70 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
71 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
74 private void connectNode() throws InterruptedException, ExecutionException, IOException {
75 fcpServer.connect().get();
76 fcpServer.collectUntil(is("EndMessage"));
77 fcpServer.writeLine("NodeHello",
78 "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
79 "Revision=build01466",
81 "Version=Fred,0.7,1.0,1466",
83 "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
87 "NodeLanguage=ENGLISH",
94 public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
95 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
97 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
98 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
99 String identifier = extractIdentifier(lines);
102 "Identifier=" + identifier,
104 "StartupTime=1435610539000",
105 "CompletionTime=1435610540000",
106 "Metadata.ContentType=text/plain;charset=utf-8",
110 Optional<Data> data = dataFuture.get();
111 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
112 assertThat(data.get().size(), is(6L));
113 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
114 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
117 private String extractIdentifier(List<String> lines) {
118 return lines.stream()
119 .filter(s -> s.startsWith("Identifier="))
120 .map(s -> s.substring(s.indexOf('=') + 1))
126 public void clientGetDownloadsDataForCorrectIdentifier()
127 throws InterruptedException, ExecutionException, IOException {
128 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
130 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
131 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
132 String identifier = extractIdentifier(lines);
135 "Identifier=not-test",
137 "StartupTime=1435610539000",
138 "CompletionTime=1435610540000",
139 "Metadata.ContentType=text/plain;charset=latin-9",
145 "Identifier=" + identifier,
147 "StartupTime=1435610539000",
148 "CompletionTime=1435610540000",
149 "Metadata.ContentType=text/plain;charset=utf-8",
153 Optional<Data> data = dataFuture.get();
154 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
155 assertThat(data.get().size(), is(6L));
156 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
157 is("Hello\n".getBytes(StandardCharsets.UTF_8)));
161 public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
162 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
164 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
165 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
166 String identifier = extractIdentifier(lines);
169 "Identifier=" + identifier,
173 Optional<Data> data = dataFuture.get();
174 assertThat(data.isPresent(), is(false));
178 public void clientGetRecognizesGetFailedForCorrectIdentifier()
179 throws InterruptedException, ExecutionException, IOException {
180 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
182 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
183 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
184 String identifier = extractIdentifier(lines);
187 "Identifier=not-test",
193 "Identifier=" + identifier,
197 Optional<Data> data = dataFuture.get();
198 assertThat(data.isPresent(), is(false));
202 public void clientGetRecognizesConnectionClosed() 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"));
208 Optional<Data> data = dataFuture.get();
209 assertThat(data.isPresent(), is(false));
213 public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
214 throws InterruptedException, ExecutionException, IOException {
215 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
217 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
218 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
222 public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
223 throws InterruptedException, ExecutionException, IOException {
224 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
226 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
227 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
231 public void clientGetWithMaxSizeSettingSendsCorrectCommands()
232 throws InterruptedException, ExecutionException, IOException {
233 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
235 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
236 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
240 public void clientGetWithPrioritySettingSendsCorrectCommands()
241 throws InterruptedException, ExecutionException, IOException {
242 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
244 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
245 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
249 public void clientGetWithRealTimeSettingSendsCorrectCommands()
250 throws InterruptedException, ExecutionException, IOException {
251 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
253 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
254 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
258 public void clientGetWithGlobalSettingSendsCorrectCommands()
259 throws InterruptedException, ExecutionException, IOException {
260 fcpClient.clientGet().global().uri("KSK@foo.txt");
262 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
263 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
266 private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
267 return new TypeSafeDiagnosingMatcher<List<String>>() {
269 protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
270 if (!item.get(0).equals(name)) {
271 mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
274 for (String requiredLine : requiredLines) {
275 if (item.indexOf(requiredLine) < 1) {
276 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
284 public void describeTo(Description description) {
285 description.appendText("FCP message named ").appendValue(name);
286 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
292 public void clientPutWithDirectDataSendsCorrectCommand()
293 throws IOException, ExecutionException, InterruptedException {
294 fcpClient.clientPut()
295 .from(new ByteArrayInputStream("Hello\n".getBytes()))
297 .key(new Key("KSK@foo.txt"));
299 List<String> lines = fcpServer.collectUntil(is("Hello"));
300 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
304 public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
305 throws InterruptedException, ExecutionException, IOException {
306 Future<Optional<Key>> key = fcpClient.clientPut()
307 .from(new ByteArrayInputStream("Hello\n".getBytes()))
309 .key(new Key("KSK@foo.txt"));
311 List<String> lines = fcpServer.collectUntil(is("Hello"));
312 String identifier = extractIdentifier(lines);
315 "Identifier=not-the-right-one",
321 "Identifier=" + identifier,
324 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
328 public void clientPutWithDirectDataFailsOnCorrectIdentifier()
329 throws InterruptedException, ExecutionException, IOException {
330 Future<Optional<Key>> key = fcpClient.clientPut()
331 .from(new ByteArrayInputStream("Hello\n".getBytes()))
333 .key(new Key("KSK@foo.txt"));
335 List<String> lines = fcpServer.collectUntil(is("Hello"));
336 String identifier = extractIdentifier(lines);
339 "Identifier=not-the-right-one",
345 "Identifier=" + identifier,
348 assertThat(key.get().isPresent(), is(false));
352 public void clientPutWithRenamedDirectDataSendsCorrectCommand()
353 throws InterruptedException, ExecutionException, IOException {
354 fcpClient.clientPut()
355 .named("otherName.txt")
356 .from(new ByteArrayInputStream("Hello\n".getBytes()))
358 .key(new Key("KSK@foo.txt"));
360 List<String> lines = fcpServer.collectUntil(is("Hello"));
361 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
362 "DataLength=6", "URI=KSK@foo.txt"));
366 public void clientPutWithRedirectSendsCorrectCommand()
367 throws IOException, ExecutionException, InterruptedException {
368 fcpClient.clientPut().redirectTo(new Key("KSK@bar.txt")).key(new Key("KSK@foo.txt"));
370 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
372 matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
376 public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
377 fcpClient.clientPut().from(new File("/tmp/data.txt")).key(new Key("KSK@foo.txt"));
379 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
381 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
385 public void clientPutWithFileCanCompleteTestDdaSequence()
386 throws IOException, ExecutionException, InterruptedException {
387 File tempFile = createTempFile();
388 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).key(new Key("KSK@foo.txt"));
390 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
391 String identifier = extractIdentifier(lines);
394 "Identifier=" + identifier,
398 lines = fcpServer.collectUntil(is("EndMessage"));
399 assertThat(lines, matchesFcpMessage(
401 "Directory=" + tempFile.getParent(),
402 "WantReadDirectory=true",
403 "WantWriteDirectory=false",
408 "Directory=" + tempFile.getParent(),
409 "ReadFilename=" + tempFile,
412 lines = fcpServer.collectUntil(is("EndMessage"));
413 assertThat(lines, matchesFcpMessage(
415 "Directory=" + tempFile.getParent(),
416 "ReadContent=test-content",
421 "Directory=" + tempFile.getParent(),
422 "ReadDirectoryAllowed=true",
425 lines = fcpServer.collectUntil(is("EndMessage"));
427 matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
428 "Filename=" + new File(tempFile.getParent(), "test.dat")));
431 private File createTempFile() throws IOException {
432 File tempFile = File.createTempFile("test-dda-", ".dat");
433 tempFile.deleteOnExit();
434 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
439 public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
440 throws InterruptedException, ExecutionException, IOException {
441 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).key(new Key("KSK@foo.txt"));
443 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
444 String identifier = extractIdentifier(lines);
447 "Identifier=not-the-right-one",
453 "Identifier=" + identifier,
457 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
461 public void clientPutAbortsOnProtocolErrorOtherThan25()
462 throws InterruptedException, ExecutionException, IOException {
463 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).key(new Key("KSK@foo.txt"));
465 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
466 String identifier = extractIdentifier(lines);
469 "Identifier=" + identifier,
473 assertThat(key.get().isPresent(), is(false));
477 public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
478 InterruptedException {
479 File tempFile = createTempFile();
480 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).key(new Key("KSK@foo.txt"));
482 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
483 String identifier = extractIdentifier(lines);
486 "Identifier=" + identifier,
490 lines = fcpServer.collectUntil(is("EndMessage"));
491 assertThat(lines, matchesFcpMessage(
493 "Directory=" + tempFile.getParent(),
494 "WantReadDirectory=true",
495 "WantWriteDirectory=false",
500 "Directory=/some-other-directory",
501 "ReadFilename=" + tempFile,
506 "Directory=" + tempFile.getParent(),
507 "ReadFilename=" + tempFile,
510 lines = fcpServer.collectUntil(is("EndMessage"));
511 assertThat(lines, matchesFcpMessage(
513 "Directory=" + tempFile.getParent(),
514 "ReadContent=test-content",