Add event for URIGenerated messages on ClientPut command
[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.contains;
5 import static org.hamcrest.Matchers.containsInAnyOrder;
6 import static org.hamcrest.Matchers.hasSize;
7 import static org.hamcrest.Matchers.is;
8 import static org.hamcrest.Matchers.notNullValue;
9
10 import java.io.ByteArrayInputStream;
11 import java.io.File;
12 import java.io.IOException;
13 import java.net.URL;
14 import java.nio.charset.StandardCharsets;
15 import java.util.Collection;
16 import java.util.List;
17 import java.util.Optional;
18 import java.util.concurrent.CopyOnWriteArrayList;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.ExecutorService;
21 import java.util.concurrent.Executors;
22 import java.util.concurrent.Future;
23 import java.util.stream.Collectors;
24
25 import net.pterodactylus.fcp.ARK;
26 import net.pterodactylus.fcp.DSAGroup;
27 import net.pterodactylus.fcp.FcpKeyPair;
28 import net.pterodactylus.fcp.Key;
29 import net.pterodactylus.fcp.NodeData;
30 import net.pterodactylus.fcp.NodeRef;
31 import net.pterodactylus.fcp.Peer;
32 import net.pterodactylus.fcp.Priority;
33 import net.pterodactylus.fcp.fake.FakeTcpServer;
34 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
35
36 import com.google.common.io.ByteStreams;
37 import com.google.common.io.Files;
38 import org.hamcrest.Description;
39 import org.hamcrest.Matcher;
40 import org.hamcrest.TypeSafeDiagnosingMatcher;
41 import org.junit.After;
42 import org.junit.Assert;
43 import org.junit.Test;
44
45 /**
46  * Unit test for {@link DefaultFcpClient}.
47  *
48  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
49  */
50 public class DefaultFcpClientTest {
51
52         private static final String INSERT_URI =
53                 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
54         private static final String REQUEST_URI =
55                 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
56
57         private static int threadCounter = 0;
58         private final FakeTcpServer fcpServer;
59         private final DefaultFcpClient fcpClient;
60
61         public DefaultFcpClientTest() throws IOException {
62                 ExecutorService threadPool =
63                         Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
64                 fcpServer = new FakeTcpServer(threadPool);
65                 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
66         }
67
68         @After
69         public void tearDown() throws IOException {
70                 fcpServer.close();
71         }
72
73         @Test(expected = ExecutionException.class)
74         public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
75         throws IOException, ExecutionException, InterruptedException {
76                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
77                 fcpServer.connect().get();
78                 fcpServer.collectUntil(is("EndMessage"));
79                 fcpServer.writeLine(
80                         "CloseConnectionDuplicateClientName",
81                         "EndMessage"
82                 );
83                 keyPairFuture.get();
84         }
85
86         @Test(expected = ExecutionException.class)
87         public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
88         throws IOException, ExecutionException, InterruptedException {
89                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
90                 fcpServer.connect().get();
91                 fcpServer.collectUntil(is("EndMessage"));
92                 fcpServer.close();
93                 keyPairFuture.get();
94         }
95
96         @Test
97         public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
98                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
99                 connectNode();
100                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
101                 String identifier = extractIdentifier(lines);
102                 fcpServer.writeLine("SSKKeypair",
103                         "InsertURI=" + INSERT_URI + "",
104                         "RequestURI=" + REQUEST_URI + "",
105                         "Identifier=" + identifier,
106                         "EndMessage");
107                 FcpKeyPair keyPair = keyPairFuture.get();
108                 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
109                 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
110         }
111
112         private void connectNode() throws InterruptedException, ExecutionException, IOException {
113                 fcpServer.connect().get();
114                 fcpServer.collectUntil(is("EndMessage"));
115                 fcpServer.writeLine("NodeHello",
116                         "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
117                         "Revision=build01466",
118                         "Testnet=false",
119                         "Version=Fred,0.7,1.0,1466",
120                         "Build=1466",
121                         "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
122                         "Node=Fred",
123                         "ExtBuild=29",
124                         "FCPVersion=2.0",
125                         "NodeLanguage=ENGLISH",
126                         "ExtRevision=v29",
127                         "EndMessage"
128                 );
129         }
130
131         @Test
132         public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
133                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
134                 connectNode();
135                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
136                 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
137                 String identifier = extractIdentifier(lines);
138                 fcpServer.writeLine(
139                         "AllData",
140                         "Identifier=" + identifier,
141                         "DataLength=6",
142                         "StartupTime=1435610539000",
143                         "CompletionTime=1435610540000",
144                         "Metadata.ContentType=text/plain;charset=utf-8",
145                         "Data",
146                         "Hello"
147                 );
148                 Optional<Data> data = dataFuture.get();
149                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
150                 assertThat(data.get().size(), is(6L));
151                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
152                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
153         }
154
155         private String extractIdentifier(List<String> lines) {
156                 return lines.stream()
157                         .filter(s -> s.startsWith("Identifier="))
158                         .map(s -> s.substring(s.indexOf('=') + 1))
159                         .findFirst()
160                         .orElse("");
161         }
162
163         @Test
164         public void clientGetDownloadsDataForCorrectIdentifier()
165         throws InterruptedException, ExecutionException, IOException {
166                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
167                 connectNode();
168                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
169                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
170                 String identifier = extractIdentifier(lines);
171                 fcpServer.writeLine(
172                         "AllData",
173                         "Identifier=not-test",
174                         "DataLength=12",
175                         "StartupTime=1435610539000",
176                         "CompletionTime=1435610540000",
177                         "Metadata.ContentType=text/plain;charset=latin-9",
178                         "Data",
179                         "Hello World"
180                 );
181                 fcpServer.writeLine(
182                         "AllData",
183                         "Identifier=" + identifier,
184                         "DataLength=6",
185                         "StartupTime=1435610539000",
186                         "CompletionTime=1435610540000",
187                         "Metadata.ContentType=text/plain;charset=utf-8",
188                         "Data",
189                         "Hello"
190                 );
191                 Optional<Data> data = dataFuture.get();
192                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
193                 assertThat(data.get().size(), is(6L));
194                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
195                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
196         }
197
198         @Test
199         public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
200                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
201                 connectNode();
202                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
203                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
204                 String identifier = extractIdentifier(lines);
205                 fcpServer.writeLine(
206                         "GetFailed",
207                         "Identifier=" + identifier,
208                         "Code=3",
209                         "EndMessage"
210                 );
211                 Optional<Data> data = dataFuture.get();
212                 assertThat(data.isPresent(), is(false));
213         }
214
215         @Test
216         public void clientGetRecognizesGetFailedForCorrectIdentifier()
217         throws InterruptedException, ExecutionException, IOException {
218                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
219                 connectNode();
220                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
221                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
222                 String identifier = extractIdentifier(lines);
223                 fcpServer.writeLine(
224                         "GetFailed",
225                         "Identifier=not-test",
226                         "Code=3",
227                         "EndMessage"
228                 );
229                 fcpServer.writeLine(
230                         "GetFailed",
231                         "Identifier=" + identifier,
232                         "Code=3",
233                         "EndMessage"
234                 );
235                 Optional<Data> data = dataFuture.get();
236                 assertThat(data.isPresent(), is(false));
237         }
238
239         @Test(expected = ExecutionException.class)
240         public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
241                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
242                 connectNode();
243                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
244                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
245                 fcpServer.close();
246                 dataFuture.get();
247         }
248
249         @Test
250         public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
251                 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
252                 connectNode();
253                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
254                 String identifier = extractIdentifier(lines);
255                 fcpServer.writeLine(
256                         "SSKKeypair",
257                         "InsertURI=" + INSERT_URI + "",
258                         "RequestURI=" + REQUEST_URI + "",
259                         "Identifier=" + identifier,
260                         "EndMessage"
261                 );
262                 keyPair.get();
263                 keyPair = fcpClient.generateKeypair().execute();
264                 lines = fcpServer.collectUntil(is("EndMessage"));
265                 identifier = extractIdentifier(lines);
266                 fcpServer.writeLine(
267                         "SSKKeypair",
268                         "InsertURI=" + INSERT_URI + "",
269                         "RequestURI=" + REQUEST_URI + "",
270                         "Identifier=" + identifier,
271                         "EndMessage"
272                 );
273                 keyPair.get();
274         }
275
276         @Test
277         public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
278         throws InterruptedException, ExecutionException, IOException {
279                 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
280                 connectNode();
281                 fcpServer.collectUntil(is("EndMessage"));
282                 fcpServer.close();
283                 try {
284                         keyPair.get();
285                         Assert.fail();
286                 } catch (ExecutionException e) {
287                 }
288                 keyPair = fcpClient.generateKeypair().execute();
289                 connectNode();
290                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
291                 String identifier = extractIdentifier(lines);
292                 fcpServer.writeLine(
293                         "SSKKeypair",
294                         "InsertURI=" + INSERT_URI + "",
295                         "RequestURI=" + REQUEST_URI + "",
296                         "Identifier=" + identifier,
297                         "EndMessage"
298                 );
299                 keyPair.get();
300         }
301
302         @Test
303         public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
304         throws InterruptedException, ExecutionException, IOException {
305                 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
306                 connectNode();
307                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
308                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
309         }
310
311         @Test
312         public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
313         throws InterruptedException, ExecutionException, IOException {
314                 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
315                 connectNode();
316                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
317                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
318         }
319
320         @Test
321         public void clientGetWithMaxSizeSettingSendsCorrectCommands()
322         throws InterruptedException, ExecutionException, IOException {
323                 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
324                 connectNode();
325                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
326                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
327         }
328
329         @Test
330         public void clientGetWithPrioritySettingSendsCorrectCommands()
331         throws InterruptedException, ExecutionException, IOException {
332                 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
333                 connectNode();
334                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
335                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
336         }
337
338         @Test
339         public void clientGetWithRealTimeSettingSendsCorrectCommands()
340         throws InterruptedException, ExecutionException, IOException {
341                 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
342                 connectNode();
343                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
344                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
345         }
346
347         @Test
348         public void clientGetWithGlobalSettingSendsCorrectCommands()
349         throws InterruptedException, ExecutionException, IOException {
350                 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
351                 connectNode();
352                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
353                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
354         }
355
356         private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
357                 return new TypeSafeDiagnosingMatcher<List<String>>() {
358                         @Override
359                         protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
360                                 if (!item.get(0).equals(name)) {
361                                         mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
362                                         return false;
363                                 }
364                                 for (String requiredLine : requiredLines) {
365                                         if (item.indexOf(requiredLine) < 1) {
366                                                 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
367                                                 return false;
368                                         }
369                                 }
370                                 return true;
371                         }
372
373                         @Override
374                         public void describeTo(Description description) {
375                                 description.appendText("FCP message named ").appendValue(name);
376                                 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
377                         }
378                 };
379         }
380
381         @Test
382         public void clientPutWithDirectDataSendsCorrectCommand()
383         throws IOException, ExecutionException, InterruptedException {
384                 fcpClient.clientPut()
385                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
386                         .length(6)
387                         .uri("KSK@foo.txt")
388                         .execute();
389                 connectNode();
390                 List<String> lines = fcpServer.collectUntil(is("Hello"));
391                 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
392         }
393
394         @Test
395         public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
396         throws InterruptedException, ExecutionException, IOException {
397                 Future<Optional<Key>> key = fcpClient.clientPut()
398                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
399                         .length(6)
400                         .uri("KSK@foo.txt")
401                         .execute();
402                 connectNode();
403                 List<String> lines = fcpServer.collectUntil(is("Hello"));
404                 String identifier = extractIdentifier(lines);
405                 fcpServer.writeLine(
406                         "PutFailed",
407                         "Identifier=not-the-right-one",
408                         "EndMessage"
409                 );
410                 fcpServer.writeLine(
411                         "PutSuccessful",
412                         "URI=KSK@foo.txt",
413                         "Identifier=" + identifier,
414                         "EndMessage"
415                 );
416                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
417         }
418
419         @Test
420         public void clientPutWithDirectDataFailsOnCorrectIdentifier()
421         throws InterruptedException, ExecutionException, IOException {
422                 Future<Optional<Key>> key = fcpClient.clientPut()
423                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
424                         .length(6)
425                         .uri("KSK@foo.txt")
426                         .execute();
427                 connectNode();
428                 List<String> lines = fcpServer.collectUntil(is("Hello"));
429                 String identifier = extractIdentifier(lines);
430                 fcpServer.writeLine(
431                         "PutSuccessful",
432                         "Identifier=not-the-right-one",
433                         "URI=KSK@foo.txt",
434                         "EndMessage"
435                 );
436                 fcpServer.writeLine(
437                         "PutFailed",
438                         "Identifier=" + identifier,
439                         "EndMessage"
440                 );
441                 assertThat(key.get().isPresent(), is(false));
442         }
443
444         @Test
445         public void clientPutWithRenamedDirectDataSendsCorrectCommand()
446         throws InterruptedException, ExecutionException, IOException {
447                 fcpClient.clientPut()
448                         .named("otherName.txt")
449                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
450                         .length(6)
451                         .uri("KSK@foo.txt")
452                         .execute();
453                 connectNode();
454                 List<String> lines = fcpServer.collectUntil(is("Hello"));
455                 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
456                         "DataLength=6", "URI=KSK@foo.txt"));
457         }
458
459         @Test
460         public void clientPutWithRedirectSendsCorrectCommand()
461         throws IOException, ExecutionException, InterruptedException {
462                 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
463                 connectNode();
464                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
465                 assertThat(lines,
466                         matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
467         }
468
469         @Test
470         public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
471                 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
472                 connectNode();
473                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
474                 assertThat(lines,
475                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
476         }
477
478         @Test
479         public void clientPutWithFileCanCompleteTestDdaSequence()
480         throws IOException, ExecutionException, InterruptedException {
481                 File tempFile = createTempFile();
482                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
483                 connectNode();
484                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
485                 String identifier = extractIdentifier(lines);
486                 fcpServer.writeLine(
487                         "ProtocolError",
488                         "Identifier=" + identifier,
489                         "Code=25",
490                         "EndMessage"
491                 );
492                 lines = fcpServer.collectUntil(is("EndMessage"));
493                 assertThat(lines, matchesFcpMessage(
494                         "TestDDARequest",
495                         "Directory=" + tempFile.getParent(),
496                         "WantReadDirectory=true",
497                         "WantWriteDirectory=false",
498                         "EndMessage"
499                 ));
500                 fcpServer.writeLine(
501                         "TestDDAReply",
502                         "Directory=" + tempFile.getParent(),
503                         "ReadFilename=" + tempFile,
504                         "EndMessage"
505                 );
506                 lines = fcpServer.collectUntil(is("EndMessage"));
507                 assertThat(lines, matchesFcpMessage(
508                         "TestDDAResponse",
509                         "Directory=" + tempFile.getParent(),
510                         "ReadContent=test-content",
511                         "EndMessage"
512                 ));
513                 fcpServer.writeLine(
514                         "TestDDAComplete",
515                         "Directory=" + tempFile.getParent(),
516                         "ReadDirectoryAllowed=true",
517                         "EndMessage"
518                 );
519                 lines = fcpServer.collectUntil(is("EndMessage"));
520                 assertThat(lines,
521                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
522                                 "Filename=" + new File(tempFile.getParent(), "test.dat")));
523         }
524
525         private File createTempFile() throws IOException {
526                 File tempFile = File.createTempFile("test-dda-", ".dat");
527                 tempFile.deleteOnExit();
528                 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
529                 return tempFile;
530         }
531
532         @Test
533         public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
534         throws InterruptedException, ExecutionException, IOException {
535                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
536                 connectNode();
537                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
538                 String identifier = extractIdentifier(lines);
539                 fcpServer.writeLine(
540                         "ProtocolError",
541                         "Identifier=not-the-right-one",
542                         "Code=25",
543                         "EndMessage"
544                 );
545                 fcpServer.writeLine(
546                         "PutSuccessful",
547                         "Identifier=" + identifier,
548                         "URI=KSK@foo.txt",
549                         "EndMessage"
550                 );
551                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
552         }
553
554         @Test
555         public void clientPutAbortsOnProtocolErrorOtherThan25()
556         throws InterruptedException, ExecutionException, IOException {
557                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
558                 connectNode();
559                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
560                 String identifier = extractIdentifier(lines);
561                 fcpServer.writeLine(
562                         "ProtocolError",
563                         "Identifier=" + identifier,
564                         "Code=1",
565                         "EndMessage"
566                 );
567                 assertThat(key.get().isPresent(), is(false));
568         }
569
570         @Test
571         public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
572         InterruptedException {
573                 File tempFile = createTempFile();
574                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
575                 connectNode();
576                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
577                 String identifier = extractIdentifier(lines);
578                 fcpServer.writeLine(
579                         "ProtocolError",
580                         "Identifier=" + identifier,
581                         "Code=25",
582                         "EndMessage"
583                 );
584                 lines = fcpServer.collectUntil(is("EndMessage"));
585                 assertThat(lines, matchesFcpMessage(
586                         "TestDDARequest",
587                         "Directory=" + tempFile.getParent(),
588                         "WantReadDirectory=true",
589                         "WantWriteDirectory=false",
590                         "EndMessage"
591                 ));
592                 fcpServer.writeLine(
593                         "TestDDAReply",
594                         "Directory=/some-other-directory",
595                         "ReadFilename=" + tempFile,
596                         "EndMessage"
597                 );
598                 fcpServer.writeLine(
599                         "TestDDAReply",
600                         "Directory=" + tempFile.getParent(),
601                         "ReadFilename=" + tempFile,
602                         "EndMessage"
603                 );
604                 lines = fcpServer.collectUntil(is("EndMessage"));
605                 assertThat(lines, matchesFcpMessage(
606                         "TestDDAResponse",
607                         "Directory=" + tempFile.getParent(),
608                         "ReadContent=test-content",
609                         "EndMessage"
610                 ));
611         }
612
613         @Test
614         public void clientPutSendsResponseEvenIfFileCanNotBeRead()
615         throws IOException, ExecutionException, InterruptedException {
616                 File tempFile = createTempFile();
617                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
618                 connectNode();
619                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
620                 String identifier = extractIdentifier(lines);
621                 fcpServer.writeLine(
622                         "ProtocolError",
623                         "Identifier=" + identifier,
624                         "Code=25",
625                         "EndMessage"
626                 );
627                 lines = fcpServer.collectUntil(is("EndMessage"));
628                 assertThat(lines, matchesFcpMessage(
629                         "TestDDARequest",
630                         "Directory=" + tempFile.getParent(),
631                         "WantReadDirectory=true",
632                         "WantWriteDirectory=false",
633                         "EndMessage"
634                 ));
635                 fcpServer.writeLine(
636                         "TestDDAReply",
637                         "Directory=" + tempFile.getParent(),
638                         "ReadFilename=" + tempFile + ".foo",
639                         "EndMessage"
640                 );
641                 lines = fcpServer.collectUntil(is("EndMessage"));
642                 assertThat(lines, matchesFcpMessage(
643                         "TestDDAResponse",
644                         "Directory=" + tempFile.getParent(),
645                         "ReadContent=failed-to-read",
646                         "EndMessage"
647                 ));
648         }
649
650         @Test
651         public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
652         throws IOException, ExecutionException, InterruptedException {
653                 File tempFile = createTempFile();
654                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
655                 connectNode();
656                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
657                 String identifier = extractIdentifier(lines);
658                 fcpServer.writeLine(
659                         "TestDDAComplete",
660                         "Directory=/some-other-directory",
661                         "EndMessage"
662                 );
663                 fcpServer.writeLine(
664                         "ProtocolError",
665                         "Identifier=" + identifier,
666                         "Code=25",
667                         "EndMessage"
668                 );
669                 lines = fcpServer.collectUntil(is("EndMessage"));
670                 assertThat(lines, matchesFcpMessage(
671                         "TestDDARequest",
672                         "Directory=" + tempFile.getParent(),
673                         "WantReadDirectory=true",
674                         "WantWriteDirectory=false",
675                         "EndMessage"
676                 ));
677         }
678
679         @Test
680         public void clientPutSendsNotificationsForGeneratedKeys()
681         throws InterruptedException, ExecutionException, IOException {
682                 List<String> generatedKeys = new CopyOnWriteArrayList<>();
683                 Future<Optional<Key>> key = fcpClient.clientPut()
684                         .onKeyGenerated(generatedKeys::add)
685                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
686                         .length(6)
687                         .uri("KSK@foo.txt")
688                         .execute();
689                 connectNode();
690                 List<String> lines = fcpServer.collectUntil(is("Hello"));
691                 String identifier = extractIdentifier(lines);
692                 fcpServer.writeLine(
693                         "URIGenerated",
694                         "Identifier="+identifier,
695                         "URI=KSK@foo.txt",
696                         "EndMessage"
697                 );
698                 fcpServer.writeLine(
699                         "PutSuccessful",
700                         "URI=KSK@foo.txt",
701                         "Identifier=" + identifier,
702                         "EndMessage"
703                 );
704                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
705                 assertThat(generatedKeys, contains("KSK@foo.txt"));
706         }
707
708         @Test
709         public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
710                 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
711                 connectNode();
712                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
713                 assertThat(lines, matchesFcpMessage(
714                         "ListPeers",
715                         "WithVolatile=false",
716                         "WithMetadata=false",
717                         "EndMessage"
718                 ));
719                 String identifier = extractIdentifier(lines);
720                 fcpServer.writeLine(
721                         "Peer",
722                         "Identifier=" + identifier,
723                         "identity=id1",
724                         "EndMessage"
725                 );
726                 fcpServer.writeLine(
727                         "Peer",
728                         "Identifier=" + identifier,
729                         "identity=id2",
730                         "EndMessage"
731                 );
732                 fcpServer.writeLine(
733                         "EndListPeers",
734                         "Identifier=" + identifier,
735                         "EndMessage"
736                 );
737                 assertThat(peers.get(), hasSize(2));
738                 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
739                         containsInAnyOrder("id1", "id2"));
740         }
741
742         @Test
743         public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
744                 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
745                 connectNode();
746                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
747                 assertThat(lines, matchesFcpMessage(
748                         "ListPeers",
749                         "WithVolatile=false",
750                         "WithMetadata=true",
751                         "EndMessage"
752                 ));
753                 String identifier = extractIdentifier(lines);
754                 fcpServer.writeLine(
755                         "Peer",
756                         "Identifier=" + identifier,
757                         "identity=id1",
758                         "metadata.foo=bar1",
759                         "EndMessage"
760                 );
761                 fcpServer.writeLine(
762                         "Peer",
763                         "Identifier=" + identifier,
764                         "identity=id2",
765                         "metadata.foo=bar2",
766                         "EndMessage"
767                 );
768                 fcpServer.writeLine(
769                         "EndListPeers",
770                         "Identifier=" + identifier,
771                         "EndMessage"
772                 );
773                 assertThat(peers.get(), hasSize(2));
774                 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
775                         containsInAnyOrder("bar1", "bar2"));
776         }
777
778         @Test
779         public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
780                 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
781                 connectNode();
782                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
783                 assertThat(lines, matchesFcpMessage(
784                         "ListPeers",
785                         "WithVolatile=true",
786                         "WithMetadata=false",
787                         "EndMessage"
788                 ));
789                 String identifier = extractIdentifier(lines);
790                 fcpServer.writeLine(
791                         "Peer",
792                         "Identifier=" + identifier,
793                         "identity=id1",
794                         "volatile.foo=bar1",
795                         "EndMessage"
796                 );
797                 fcpServer.writeLine(
798                         "Peer",
799                         "Identifier=" + identifier,
800                         "identity=id2",
801                         "volatile.foo=bar2",
802                         "EndMessage"
803                 );
804                 fcpServer.writeLine(
805                         "EndListPeers",
806                         "Identifier=" + identifier,
807                         "EndMessage"
808                 );
809                 assertThat(peers.get(), hasSize(2));
810                 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
811                         containsInAnyOrder("bar1", "bar2"));
812         }
813
814         @Test
815         public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
816                 Future<NodeData> nodeData = fcpClient.getNode().execute();
817                 connectNode();
818                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
819                 String identifier = extractIdentifier(lines);
820                 assertThat(lines, matchesFcpMessage(
821                         "GetNode",
822                         "Identifier=" + identifier,
823                         "GiveOpennetRef=false",
824                         "WithPrivate=false",
825                         "WithVolatile=false",
826                         "EndMessage"
827                 ));
828                 fcpServer.writeLine(
829                         "NodeData",
830                         "Identifier=" + identifier,
831                         "ark.pubURI=SSK@3YEf.../ark",
832                         "ark.number=78",
833                         "auth.negTypes=2",
834                         "version=Fred,0.7,1.0,1466",
835                         "lastGoodVersion=Fred,0.7,1.0,1466",
836                         "EndMessage"
837                 );
838                 assertThat(nodeData.get(), notNullValue());
839         }
840
841         @Test
842         public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
843         throws InterruptedException, ExecutionException, IOException {
844                 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
845                 connectNode();
846                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
847                 String identifier = extractIdentifier(lines);
848                 assertThat(lines, matchesFcpMessage(
849                         "GetNode",
850                         "Identifier=" + identifier,
851                         "GiveOpennetRef=true",
852                         "WithPrivate=false",
853                         "WithVolatile=false",
854                         "EndMessage"
855                 ));
856                 fcpServer.writeLine(
857                         "NodeData",
858                         "Identifier=" + identifier,
859                         "opennet=true",
860                         "ark.pubURI=SSK@3YEf.../ark",
861                         "ark.number=78",
862                         "auth.negTypes=2",
863                         "version=Fred,0.7,1.0,1466",
864                         "lastGoodVersion=Fred,0.7,1.0,1466",
865                         "EndMessage"
866                 );
867                 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
868         }
869
870         @Test
871         public void defaultFcpClientCanGetNodeInformationWithPrivateData()
872         throws InterruptedException, ExecutionException, IOException {
873                 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
874                 connectNode();
875                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
876                 String identifier = extractIdentifier(lines);
877                 assertThat(lines, matchesFcpMessage(
878                         "GetNode",
879                         "Identifier=" + identifier,
880                         "GiveOpennetRef=false",
881                         "WithPrivate=true",
882                         "WithVolatile=false",
883                         "EndMessage"
884                 ));
885                 fcpServer.writeLine(
886                         "NodeData",
887                         "Identifier=" + identifier,
888                         "opennet=false",
889                         "ark.pubURI=SSK@3YEf.../ark",
890                         "ark.number=78",
891                         "auth.negTypes=2",
892                         "version=Fred,0.7,1.0,1466",
893                         "lastGoodVersion=Fred,0.7,1.0,1466",
894                         "ark.privURI=SSK@XdHMiRl",
895                         "EndMessage"
896                 );
897                 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
898         }
899
900         @Test
901         public void defaultFcpClientCanGetNodeInformationWithVolatileData()
902         throws InterruptedException, ExecutionException, IOException {
903                 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
904                 connectNode();
905                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
906                 String identifier = extractIdentifier(lines);
907                 assertThat(lines, matchesFcpMessage(
908                         "GetNode",
909                         "Identifier=" + identifier,
910                         "GiveOpennetRef=false",
911                         "WithPrivate=false",
912                         "WithVolatile=true",
913                         "EndMessage"
914                 ));
915                 fcpServer.writeLine(
916                         "NodeData",
917                         "Identifier=" + identifier,
918                         "opennet=false",
919                         "ark.pubURI=SSK@3YEf.../ark",
920                         "ark.number=78",
921                         "auth.negTypes=2",
922                         "version=Fred,0.7,1.0,1466",
923                         "lastGoodVersion=Fred,0.7,1.0,1466",
924                         "volatile.freeJavaMemory=205706528",
925                         "EndMessage"
926                 );
927                 assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
928         }
929
930         @Test
931         public void defaultFcpClientCanListSinglePeerByIdentity()
932         throws InterruptedException, ExecutionException, IOException {
933                 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
934                 connectNode();
935                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
936                 String identifier = extractIdentifier(lines);
937                 assertThat(lines, matchesFcpMessage(
938                         "ListPeer",
939                         "Identifier=" + identifier,
940                         "NodeIdentifier=id1",
941                         "EndMessage"
942                 ));
943                 fcpServer.writeLine(
944                         "Peer",
945                         "Identifier=" + identifier,
946                         "identity=id1",
947                         "opennet=false",
948                         "ark.pubURI=SSK@3YEf.../ark",
949                         "ark.number=78",
950                         "auth.negTypes=2",
951                         "version=Fred,0.7,1.0,1466",
952                         "lastGoodVersion=Fred,0.7,1.0,1466",
953                         "EndMessage"
954                 );
955                 assertThat(peer.get().get().getIdentity(), is("id1"));
956         }
957
958         @Test
959         public void defaultFcpClientCanListSinglePeerByHostAndPort()
960         throws InterruptedException, ExecutionException, IOException {
961                 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
962                 connectNode();
963                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
964                 String identifier = extractIdentifier(lines);
965                 assertThat(lines, matchesFcpMessage(
966                         "ListPeer",
967                         "Identifier=" + identifier,
968                         "NodeIdentifier=host.free.net:12345",
969                         "EndMessage"
970                 ));
971                 fcpServer.writeLine(
972                         "Peer",
973                         "Identifier=" + identifier,
974                         "identity=id1",
975                         "opennet=false",
976                         "ark.pubURI=SSK@3YEf.../ark",
977                         "ark.number=78",
978                         "auth.negTypes=2",
979                         "version=Fred,0.7,1.0,1466",
980                         "lastGoodVersion=Fred,0.7,1.0,1466",
981                         "EndMessage"
982                 );
983                 assertThat(peer.get().get().getIdentity(), is("id1"));
984         }
985
986         @Test
987         public void defaultFcpClientCanListSinglePeerByName()
988         throws InterruptedException, ExecutionException, IOException {
989                 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
990                 connectNode();
991                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
992                 String identifier = extractIdentifier(lines);
993                 assertThat(lines, matchesFcpMessage(
994                         "ListPeer",
995                         "Identifier=" + identifier,
996                         "NodeIdentifier=FriendNode",
997                         "EndMessage"
998                 ));
999                 fcpServer.writeLine(
1000                         "Peer",
1001                         "Identifier=" + identifier,
1002                         "identity=id1",
1003                         "opennet=false",
1004                         "ark.pubURI=SSK@3YEf.../ark",
1005                         "ark.number=78",
1006                         "auth.negTypes=2",
1007                         "version=Fred,0.7,1.0,1466",
1008                         "lastGoodVersion=Fred,0.7,1.0,1466",
1009                         "EndMessage"
1010                 );
1011                 assertThat(peer.get().get().getIdentity(), is("id1"));
1012         }
1013
1014         @Test
1015         public void defaultFcpClientRecognizesUnknownNodeIdentifiers()
1016         throws InterruptedException, ExecutionException, IOException {
1017                 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
1018                 connectNode();
1019                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1020                 String identifier = extractIdentifier(lines);
1021                 assertThat(lines, matchesFcpMessage(
1022                         "ListPeer",
1023                         "Identifier=" + identifier,
1024                         "NodeIdentifier=id2",
1025                         "EndMessage"
1026                 ));
1027                 fcpServer.writeLine(
1028                         "UnknownNodeIdentifier",
1029                         "Identifier=" + identifier,
1030                         "NodeIdentifier=id2",
1031                         "EndMessage"
1032                 );
1033                 assertThat(peer.get().isPresent(), is(false));
1034         }
1035
1036         @Test
1037         public void defaultFcpClientCanAddPeerFromFile() throws InterruptedException, ExecutionException, IOException {
1038                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
1039                 connectNode();
1040                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1041                 String identifier = extractIdentifier(lines);
1042                 assertThat(lines, matchesFcpMessage(
1043                         "AddPeer",
1044                         "Identifier=" + identifier,
1045                         "File=/tmp/ref.txt",
1046                         "EndMessage"
1047                 ));
1048                 fcpServer.writeLine(
1049                         "Peer",
1050                         "Identifier=" + identifier,
1051                         "identity=id1",
1052                         "opennet=false",
1053                         "ark.pubURI=SSK@3YEf.../ark",
1054                         "ark.number=78",
1055                         "auth.negTypes=2",
1056                         "version=Fred,0.7,1.0,1466",
1057                         "lastGoodVersion=Fred,0.7,1.0,1466",
1058                         "EndMessage"
1059                 );
1060                 assertThat(peer.get().get().getIdentity(), is("id1"));
1061         }
1062
1063         @Test
1064         public void defaultFcpClientCanAddPeerFromURL() throws InterruptedException, ExecutionException, IOException {
1065                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1066                 connectNode();
1067                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1068                 String identifier = extractIdentifier(lines);
1069                 assertThat(lines, matchesFcpMessage(
1070                         "AddPeer",
1071                         "Identifier=" + identifier,
1072                         "URL=http://node.ref/",
1073                         "EndMessage"
1074                 ));
1075                 fcpServer.writeLine(
1076                         "Peer",
1077                         "Identifier=" + identifier,
1078                         "identity=id1",
1079                         "opennet=false",
1080                         "ark.pubURI=SSK@3YEf.../ark",
1081                         "ark.number=78",
1082                         "auth.negTypes=2",
1083                         "version=Fred,0.7,1.0,1466",
1084                         "lastGoodVersion=Fred,0.7,1.0,1466",
1085                         "EndMessage"
1086                 );
1087                 assertThat(peer.get().get().getIdentity(), is("id1"));
1088         }
1089
1090         @Test
1091         public void defaultFcpClientCanAddPeerFromNodeRef() throws InterruptedException, ExecutionException, IOException {
1092                 NodeRef nodeRef = new NodeRef();
1093                 nodeRef.setIdentity("id1");
1094                 nodeRef.setName("name");
1095                 nodeRef.setARK(new ARK("public", "1"));
1096                 nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
1097                 nodeRef.setNegotiationTypes(new int[] { 3, 5 });
1098                 nodeRef.setPhysicalUDP("1.2.3.4:5678");
1099                 nodeRef.setDSAPublicKey("dsa-public");
1100                 nodeRef.setSignature("sig");
1101                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
1102                 connectNode();
1103                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1104                 String identifier = extractIdentifier(lines);
1105                 assertThat(lines, matchesFcpMessage(
1106                         "AddPeer",
1107                         "Identifier=" + identifier,
1108                         "identity=id1",
1109                         "myName=name",
1110                         "ark.pubURI=public",
1111                         "ark.number=1",
1112                         "dsaGroup.g=base",
1113                         "dsaGroup.p=prime",
1114                         "dsaGroup.q=subprime",
1115                         "dsaPubKey.y=dsa-public",
1116                         "physical.udp=1.2.3.4:5678",
1117                         "auth.negTypes=3;5",
1118                         "sig=sig",
1119                         "EndMessage"
1120                 ));
1121                 fcpServer.writeLine(
1122                         "Peer",
1123                         "Identifier=" + identifier,
1124                         "identity=id1",
1125                         "opennet=false",
1126                         "ark.pubURI=SSK@3YEf.../ark",
1127                         "ark.number=78",
1128                         "auth.negTypes=2",
1129                         "version=Fred,0.7,1.0,1466",
1130                         "lastGoodVersion=Fred,0.7,1.0,1466",
1131                         "EndMessage"
1132                 );
1133                 assertThat(peer.get().get().getIdentity(), is("id1"));
1134         }
1135
1136 }