da454df3b145cddaa14697695cad0eb12d7b33d1
[jFCPlib.git] / src / test / java / net / pterodactylus / fcp / quelaton / DefaultFcpClientTest.java
1 package net.pterodactylus.fcp.quelaton;
2
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.is;
5
6 import java.io.ByteArrayInputStream;
7 import java.io.File;
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;
16
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;
22
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;
30
31 /**
32  * Unit test for {@link DefaultFcpClient}.
33  *
34  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
35  */
36 public class DefaultFcpClientTest {
37
38         private static final String INSERT_URI =
39                 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
40         private static final String REQUEST_URI =
41                 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
42
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;
48
49         public DefaultFcpClientTest() throws IOException {
50                 fcpServer = new FakeTcpServer(threadPool);
51                 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test", () -> "2.0");
52         }
53
54         @After
55         public void tearDown() throws IOException {
56                 fcpServer.close();
57         }
58
59         @Test
60         public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
61                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
62                 connectNode();
63                 fcpServer.collectUntil(is("EndMessage"));
64                 fcpServer.writeLine("SSKKeypair",
65                         "InsertURI=" + INSERT_URI + "",
66                         "RequestURI=" + REQUEST_URI + "",
67                         "Identifier=My Identifier from GenerateSSK",
68                         "EndMessage");
69                 FcpKeyPair keyPair = keyPairFuture.get();
70                 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
71                 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
72         }
73
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",
80                         "Testnet=false",
81                         "Version=Fred,0.7,1.0,1466",
82                         "Build=1466",
83                         "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
84                         "Node=Fred",
85                         "ExtBuild=29",
86                         "FCPVersion=2.0",
87                         "NodeLanguage=ENGLISH",
88                         "ExtRevision=v29",
89                         "EndMessage"
90                 );
91         }
92
93         @Test
94         public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
95                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
96                 connectNode();
97                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
98                 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
99                 String identifier = extractIdentifier(lines);
100                 fcpServer.writeLine(
101                         "AllData",
102                         "Identifier=" + identifier,
103                         "DataLength=6",
104                         "StartupTime=1435610539000",
105                         "CompletionTime=1435610540000",
106                         "Metadata.ContentType=text/plain;charset=utf-8",
107                         "Data",
108                         "Hello"
109                 );
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)));
115         }
116
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))
121                         .findFirst()
122                         .orElse("");
123         }
124
125         @Test
126         public void clientGetDownloadsDataForCorrectIdentifier()
127         throws InterruptedException, ExecutionException, IOException {
128                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
129                 connectNode();
130                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
131                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
132                 String identifier = extractIdentifier(lines);
133                 fcpServer.writeLine(
134                         "AllData",
135                         "Identifier=not-test",
136                         "DataLength=12",
137                         "StartupTime=1435610539000",
138                         "CompletionTime=1435610540000",
139                         "Metadata.ContentType=text/plain;charset=latin-9",
140                         "Data",
141                         "Hello World"
142                 );
143                 fcpServer.writeLine(
144                         "AllData",
145                         "Identifier=" + identifier,
146                         "DataLength=6",
147                         "StartupTime=1435610539000",
148                         "CompletionTime=1435610540000",
149                         "Metadata.ContentType=text/plain;charset=utf-8",
150                         "Data",
151                         "Hello"
152                 );
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)));
158         }
159
160         @Test
161         public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
162                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
163                 connectNode();
164                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
165                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
166                 String identifier = extractIdentifier(lines);
167                 fcpServer.writeLine(
168                         "GetFailed",
169                         "Identifier=" + identifier,
170                         "Code=3",
171                         "EndMessage"
172                 );
173                 Optional<Data> data = dataFuture.get();
174                 assertThat(data.isPresent(), is(false));
175         }
176
177         @Test
178         public void clientGetRecognizesGetFailedForCorrectIdentifier()
179         throws InterruptedException, ExecutionException, IOException {
180                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
181                 connectNode();
182                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
183                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
184                 String identifier = extractIdentifier(lines);
185                 fcpServer.writeLine(
186                         "GetFailed",
187                         "Identifier=not-test",
188                         "Code=3",
189                         "EndMessage"
190                 );
191                 fcpServer.writeLine(
192                         "GetFailed",
193                         "Identifier=" + identifier,
194                         "Code=3",
195                         "EndMessage"
196                 );
197                 Optional<Data> data = dataFuture.get();
198                 assertThat(data.isPresent(), is(false));
199         }
200
201         @Test
202         public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
203                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
204                 connectNode();
205                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
206                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
207                 fcpServer.close();
208                 Optional<Data> data = dataFuture.get();
209                 assertThat(data.isPresent(), is(false));
210         }
211
212         @Test
213         public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
214         throws InterruptedException, ExecutionException, IOException {
215                 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
216                 connectNode();
217                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
218                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
219         }
220
221         @Test
222         public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
223         throws InterruptedException, ExecutionException, IOException {
224                 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
225                 connectNode();
226                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
227                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
228         }
229
230         @Test
231         public void clientGetWithMaxSizeSettingSendsCorrectCommands()
232         throws InterruptedException, ExecutionException, IOException {
233                 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
234                 connectNode();
235                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
236                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
237         }
238
239         @Test
240         public void clientGetWithPrioritySettingSendsCorrectCommands()
241         throws InterruptedException, ExecutionException, IOException {
242                 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
243                 connectNode();
244                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
245                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
246         }
247
248         @Test
249         public void clientGetWithRealTimeSettingSendsCorrectCommands()
250         throws InterruptedException, ExecutionException, IOException {
251                 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
252                 connectNode();
253                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
254                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
255         }
256
257         @Test
258         public void clientGetWithGlobalSettingSendsCorrectCommands()
259         throws InterruptedException, ExecutionException, IOException {
260                 fcpClient.clientGet().global().uri("KSK@foo.txt");
261                 connectNode();
262                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
263                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
264         }
265
266         private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
267                 return new TypeSafeDiagnosingMatcher<List<String>>() {
268                         @Override
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));
272                                         return false;
273                                 }
274                                 for (String requiredLine : requiredLines) {
275                                         if (item.indexOf(requiredLine) < 1) {
276                                                 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
277                                                 return false;
278                                         }
279                                 }
280                                 return true;
281                         }
282
283                         @Override
284                         public void describeTo(Description description) {
285                                 description.appendText("FCP message named ").appendValue(name);
286                                 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
287                         }
288                 };
289         }
290
291         @Test
292         public void clientPutWithDirectDataSendsCorrectCommand()
293         throws IOException, ExecutionException, InterruptedException {
294                 fcpClient.clientPut()
295                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
296                         .length(6)
297                         .key(new Key("KSK@foo.txt"));
298                 connectNode();
299                 List<String> lines = fcpServer.collectUntil(is("Hello"));
300                 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
301         }
302
303         @Test
304         public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
305         throws InterruptedException, ExecutionException, IOException {
306                 Future<Optional<Key>> key = fcpClient.clientPut()
307                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
308                         .length(6)
309                         .key(new Key("KSK@foo.txt"));
310                 connectNode();
311                 List<String> lines = fcpServer.collectUntil(is("Hello"));
312                 String identifier = extractIdentifier(lines);
313                 fcpServer.writeLine(
314                         "PutFailed",
315                         "Identifier=not-the-right-one",
316                         "EndMessage"
317                 );
318                 fcpServer.writeLine(
319                         "PutSuccessful",
320                         "URI=KSK@foo.txt",
321                         "Identifier=" + identifier,
322                         "EndMessage"
323                 );
324                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
325         }
326
327         @Test
328         public void clientPutWithDirectDataFailsOnCorrectIdentifier()
329         throws InterruptedException, ExecutionException, IOException {
330                 Future<Optional<Key>> key = fcpClient.clientPut()
331                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
332                         .length(6)
333                         .key(new Key("KSK@foo.txt"));
334                 connectNode();
335                 List<String> lines = fcpServer.collectUntil(is("Hello"));
336                 String identifier = extractIdentifier(lines);
337                 fcpServer.writeLine(
338                         "PutSuccessful",
339                         "Identifier=not-the-right-one",
340                         "URI=KSK@foo.txt",
341                         "EndMessage"
342                 );
343                 fcpServer.writeLine(
344                         "PutFailed",
345                         "Identifier=" + identifier,
346                         "EndMessage"
347                 );
348                 assertThat(key.get().isPresent(), is(false));
349         }
350
351         @Test
352         public void clientPutWithRenamedDirectDataSendsCorrectCommand()
353         throws InterruptedException, ExecutionException, IOException {
354                 fcpClient.clientPut()
355                         .named("otherName.txt")
356                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
357                         .length(6)
358                         .key(new Key("KSK@foo.txt"));
359                 connectNode();
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"));
363         }
364
365         @Test
366         public void clientPutWithRedirectSendsCorrectCommand()
367         throws IOException, ExecutionException, InterruptedException {
368                 fcpClient.clientPut().redirectTo(new Key("KSK@bar.txt")).key(new Key("KSK@foo.txt"));
369                 connectNode();
370                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
371                 assertThat(lines,
372                         matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
373         }
374
375         @Test
376         public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
377                 fcpClient.clientPut().from(new File("/tmp/data.txt")).key(new Key("KSK@foo.txt"));
378                 connectNode();
379                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
380                 assertThat(lines,
381                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
382         }
383
384         @Test
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"));
389                 connectNode();
390                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
391                 String identifier = extractIdentifier(lines);
392                 fcpServer.writeLine(
393                         "ProtocolError",
394                         "Identifier=" + identifier,
395                         "Code=25",
396                         "EndMessage"
397                 );
398                 lines = fcpServer.collectUntil(is("EndMessage"));
399                 assertThat(lines, matchesFcpMessage(
400                         "TestDDARequest",
401                         "Directory=" + tempFile.getParent(),
402                         "WantReadDirectory=true",
403                         "WantWriteDirectory=false",
404                         "EndMessage"
405                 ));
406                 fcpServer.writeLine(
407                         "TestDDAReply",
408                         "Directory=" + tempFile.getParent(),
409                         "ReadFilename=" + tempFile,
410                         "EndMessage"
411                 );
412                 lines = fcpServer.collectUntil(is("EndMessage"));
413                 assertThat(lines, matchesFcpMessage(
414                         "TestDDAResponse",
415                         "Directory=" + tempFile.getParent(),
416                         "ReadContent=test-content",
417                         "EndMessage"
418                 ));
419                 fcpServer.writeLine(
420                         "TestDDAComplete",
421                         "Directory=" + tempFile.getParent(),
422                         "ReadDirectoryAllowed=true",
423                         "EndMessage"
424                 );
425                 lines = fcpServer.collectUntil(is("EndMessage"));
426                 assertThat(lines,
427                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
428                                 "Filename=" + new File(tempFile.getParent(), "test.dat")));
429         }
430
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);
435                 return tempFile;
436         }
437
438         @Test
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"));
442                 connectNode();
443                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
444                 String identifier = extractIdentifier(lines);
445                 fcpServer.writeLine(
446                         "ProtocolError",
447                         "Identifier=not-the-right-one",
448                         "Code=25",
449                         "EndMessage"
450                 );
451                 fcpServer.writeLine(
452                         "PutSuccessful",
453                         "Identifier=" + identifier,
454                         "URI=KSK@foo.txt",
455                         "EndMessage"
456                 );
457                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
458         }
459
460         @Test
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"));
464                 connectNode();
465                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
466                 String identifier = extractIdentifier(lines);
467                 fcpServer.writeLine(
468                         "ProtocolError",
469                         "Identifier=" + identifier,
470                         "Code=1",
471                         "EndMessage"
472                 );
473                 assertThat(key.get().isPresent(), is(false));
474         }
475
476         @Test
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"));
481                 connectNode();
482                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
483                 String identifier = extractIdentifier(lines);
484                 fcpServer.writeLine(
485                         "ProtocolError",
486                         "Identifier=" + identifier,
487                         "Code=25",
488                         "EndMessage"
489                 );
490                 lines = fcpServer.collectUntil(is("EndMessage"));
491                 assertThat(lines, matchesFcpMessage(
492                         "TestDDARequest",
493                         "Directory=" + tempFile.getParent(),
494                         "WantReadDirectory=true",
495                         "WantWriteDirectory=false",
496                         "EndMessage"
497                 ));
498                 fcpServer.writeLine(
499                         "TestDDAReply",
500                         "Directory=/some-other-directory",
501                         "ReadFilename=" + tempFile,
502                         "EndMessage"
503                 );
504                 fcpServer.writeLine(
505                         "TestDDAReply",
506                         "Directory=" + tempFile.getParent(),
507                         "ReadFilename=" + tempFile,
508                         "EndMessage"
509                 );
510                 lines = fcpServer.collectUntil(is("EndMessage"));
511                 assertThat(lines, matchesFcpMessage(
512                         "TestDDAResponse",
513                         "Directory=" + tempFile.getParent(),
514                         "ReadContent=test-content",
515                         "EndMessage"
516                 ));
517         }
518
519 }