Browse Source

call me THE DESTROYER!!!
first compile-clean but totally fubar version

Sebastian Stenzel 8 years ago
parent
commit
79b825aaad
100 changed files with 46 additions and 7144 deletions
  1. 46 6
      main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java
  2. 0 2
      main/filesystem-api/.gitignore
  3. 0 56
      main/filesystem-api/pom.xml
  4. 0 55
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java
  5. 0 69
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/DeadlockSafeFileOpener.java
  6. 0 15
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/Deleter.java
  7. 0 87
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java
  8. 0 30
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/FileSystem.java
  9. 0 145
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java
  10. 0 131
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/FolderVisitor.java
  11. 0 100
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java
  12. 0 71
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/OpenFiles.java
  13. 0 112
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java
  14. 0 50
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java
  15. 0 63
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/WritableFile.java
  16. 0 82
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java
  17. 0 22
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java
  18. 0 100
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java
  19. 0 78
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java
  20. 0 44
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java
  21. 0 49
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingWritableFile.java
  22. 0 13
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/package-info.java
  23. 0 35
      main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java
  24. 0 59
      main/filesystem-api/src/main/java/org/cryptomator/io/FileContents.java
  25. 0 36
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java
  26. 0 227
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java
  27. 0 70
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/PathResolverTest.java
  28. 0 32
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileSystemTest.java
  29. 0 186
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java
  30. 0 206
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java
  31. 0 64
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java
  32. 0 74
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingWritableFileTest.java
  33. 0 11
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java
  34. 0 20
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java
  35. 0 22
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java
  36. 0 81
      main/filesystem-api/src/test/java/org/cryptomator/io/ByteBuffersTest.java
  37. 0 103
      main/filesystem-api/src/test/java/org/cryptomator/io/FileContentsTest.java
  38. 0 22
      main/filesystem-api/src/test/resources/log4j2.xml
  39. 0 45
      main/filesystem-charsets/pom.xml
  40. 0 32
      main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java
  41. 0 27
      main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java
  42. 0 76
      main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java
  43. 0 16
      main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java
  44. 0 90
      main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java
  45. 0 48
      main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java
  46. 0 149
      main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java
  47. 0 22
      main/filesystem-charsets/src/test/resources/log4j2.xml
  48. 0 1
      main/filesystem-crypto-integration-tests/.gitignore
  49. 0 64
      main/filesystem-crypto-integration-tests/pom.xml
  50. 0 33
      main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoEngineTestModule.java
  51. 0 32
      main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTestComponent.java
  52. 0 298
      main/filesystem-crypto-integration-tests/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemIntegrationTest.java
  53. 0 22
      main/filesystem-crypto-integration-tests/src/test/resources/log4j2.xml
  54. 0 1
      main/filesystem-crypto/.gitignore
  55. 0 93
      main/filesystem-crypto/pom.xml
  56. 0 29
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/AuthenticationFailedException.java
  57. 0 29
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/CryptoException.java
  58. 0 31
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java
  59. 0 49
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java
  60. 0 61
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java
  61. 0 72
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java
  62. 0 44
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java
  63. 0 17
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/InvalidPassphraseException.java
  64. 0 38
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java
  65. 0 71
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/AesKeyWrap.java
  66. 0 15
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java
  67. 0 41
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptoEngineModule.java
  68. 0 197
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java
  69. 0 70
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessor.java
  70. 0 75
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java
  71. 0 180
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java
  72. 0 187
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java
  73. 0 117
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java
  74. 0 149
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java
  75. 0 98
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java
  76. 0 100
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java
  77. 0 50
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java
  78. 0 36
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalAesCtrCipher.java
  79. 0 46
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalMac.java
  80. 0 12
      main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/package-info.java
  81. 0 35
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFile.java
  82. 0 61
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystem.java
  83. 0 29
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystemFactory.java
  84. 0 34
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFolder.java
  85. 0 108
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFile.java
  86. 0 136
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFile.java
  87. 0 63
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextReader.java
  88. 0 51
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextWriter.java
  89. 0 105
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java
  90. 0 16
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java
  91. 0 116
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java
  92. 0 108
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java
  93. 0 29
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java
  94. 0 61
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java
  95. 0 243
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java
  96. 0 122
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java
  97. 0 111
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java
  98. 0 113
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java
  99. 0 112
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java
  100. 0 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/package-info.java

+ 46 - 6
main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java

@@ -2,6 +2,7 @@ package org.cryptomator.common;
 
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
 
 public final class LazyInitializer {
 
@@ -9,7 +10,7 @@ public final class LazyInitializer {
 	}
 
 	/**
-	 * Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
+	 * Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
 	 * 
 	 * @param <T> Type of the value
 	 * @param reference A reference to a maybe not yet initialized value.
@@ -17,18 +18,57 @@ public final class LazyInitializer {
 	 * @return The initialized value
 	 */
 	public static <T> T initializeLazily(AtomicReference<T> reference, Supplier<T> factory) {
+		SupplierThrowingException<T, RuntimeException> factoryThrowingRuntimeExceptions = () -> factory.get();
+		return initializeLazily(reference, factoryThrowingRuntimeExceptions, RuntimeException.class);
+	}
+
+	/**
+	 * Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
+	 * 
+	 * @param <T> Type of the value
+	 * @param <E> Type of the any expected exception that may occur during initialization
+	 * @param reference A reference to a maybe not yet initialized value.
+	 * @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
+	 * @param exceptionType Expected exception type.
+	 * @return The initialized value
+	 * @throws E Exception thrown by the factory function.
+	 */
+	public static <T, E extends Exception> T initializeLazily(AtomicReference<T> reference, SupplierThrowingException<T, E> factory, Class<E> exceptionType) throws E {
 		final T existing = reference.get();
 		if (existing != null) {
 			return existing;
 		} else {
-			return reference.updateAndGet(currentValue -> {
-				if (currentValue == null) {
-					return factory.get();
+			try {
+				return reference.updateAndGet(invokeFactoryIfNull(factory));
+			} catch (InitializationException e) {
+				if (exceptionType.isInstance(e.getCause())) {
+					throw exceptionType.cast(e.getCause());
 				} else {
-					return currentValue;
+					throw e;
 				}
-			});
+			}
 		}
 	}
 
+	private static <T> UnaryOperator<T> invokeFactoryIfNull(SupplierThrowingException<T, ?> factory) throws InitializationException {
+		return currentValue -> {
+			if (currentValue == null) {
+				try {
+					return factory.get();
+				} catch (Throwable e) {
+					throw new InitializationException(e);
+				}
+			} else {
+				return currentValue;
+			}
+		};
+	}
+
+	private static class InitializationException extends RuntimeException {
+
+		public InitializationException(Throwable cause) {
+			super(cause);
+		}
+
+	}
 }

+ 0 - 2
main/filesystem-api/.gitignore

@@ -1,2 +0,0 @@
-/target/
-/target/

+ 0 - 56
main/filesystem-api/pom.xml

@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Copyright (c) 2015 Markus Kreusch
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>1.3.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>filesystem-api</artifactId>
-	<name>Cryptomator filesystem: API</name>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons</artifactId>
-		</dependency>
-		
-		<!-- Guava -->
-		<dependency>
-			<groupId>com.google.guava</groupId>
-			<artifactId>guava</artifactId>
-		</dependency>
-
-		<!-- apache commons -->
-		<dependency>
-			<groupId>commons-io</groupId>
-			<artifactId>commons-io</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-lang3</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-collections4</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-	</dependencies>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 55
main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java

@@ -1,55 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-
-import com.google.common.io.ByteStreams;
-
-class Copier {
-
-	public static void copy(Folder source, Folder destination) {
-		assertFoldersAreNotNested(source, destination);
-
-		destination.delete();
-		destination.create();
-
-		source.files().forEach(sourceFile -> {
-			File destinationFile = destination.file(sourceFile.name());
-			sourceFile.copyTo(destinationFile);
-		});
-
-		source.folders().forEach(sourceFolder -> {
-			Folder destinationFolder = destination.folder(sourceFolder.name());
-			sourceFolder.copyTo(destinationFolder);
-		});
-	}
-
-	public static void copy(File source, File destination) {
-		try (OpenFiles openFiles = DeadlockSafeFileOpener.withReadable(source).andWritable(destination).open()) {
-			ReadableFile readable = openFiles.readable(source);
-			WritableFile writable = openFiles.writable(destination);
-			writable.truncate();
-			ByteStreams.copy(readable, writable);
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	private static void assertFoldersAreNotNested(Folder source, Folder destination) {
-		if (source.isAncestorOf(destination)) {
-			throw new IllegalArgumentException("Can not copy parent to child directory (src: " + source + ", dst: " + destination + ")");
-		}
-		if (destination.isAncestorOf(source)) {
-			throw new IllegalArgumentException("Can not copy child to parent directory (src: " + source + ", dst: " + destination + ")");
-		}
-	}
-
-}

+ 0 - 69
main/filesystem-api/src/main/java/org/cryptomator/filesystem/DeadlockSafeFileOpener.java

@@ -1,69 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem;
-
-import static java.lang.String.format;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.function.Consumer;
-
-public class DeadlockSafeFileOpener {
-
-	public static DeadlockSafeFileOpener withReadable(File file) {
-		return new DeadlockSafeFileOpener().andReadable(file);
-	}
-
-	public static DeadlockSafeFileOpener withWritable(File file) {
-		return new DeadlockSafeFileOpener().andWritable(file);
-	}
-
-	private final SortedMap<File, Consumer<File>> filesWithOperation = new TreeMap<>();
-
-	private final Map<File, ReadableFile> readableFiles = new HashMap<>();
-	private final Map<File, WritableFile> writableFiles = new HashMap<>();
-
-	private DeadlockSafeFileOpener() {
-	}
-
-	public DeadlockSafeFileOpener andReadable(File file) {
-		if (filesWithOperation.put(file, this::openReadable) != null) {
-			throw new IllegalArgumentException(format("File %s already marked for opening", file));
-		}
-		return this;
-	}
-
-	public DeadlockSafeFileOpener andWritable(File file) {
-		if (filesWithOperation.put(file, this::openWritable) != null) {
-			throw new IllegalArgumentException(format("File %s already marked for opening", file));
-		}
-		return this;
-	}
-
-	private void openReadable(File file) {
-		readableFiles.put(file, file.openReadable());
-	}
-
-	private void openWritable(File file) {
-		writableFiles.put(file, file.openWritable());
-	}
-
-	public OpenFiles open() {
-		try {
-			filesWithOperation.forEach((file, openAction) -> openAction.accept(file));
-		} catch (RuntimeException e) {
-			OpenFiles.cleanup(readableFiles.values(), writableFiles.values());
-			throw e;
-		}
-		return new OpenFiles(readableFiles, writableFiles);
-	}
-
-}

+ 0 - 15
main/filesystem-api/src/main/java/org/cryptomator/filesystem/Deleter.java

@@ -1,15 +0,0 @@
-package org.cryptomator.filesystem;
-
-public class Deleter {
-
-	/**
-	 * Deletes all and only the content of a given {@link Folder} but <b>not</b> the folder itself.
-	 */
-	public static void deleteContent(Folder folder) {
-		if (folder.exists()) {
-			folder.folders().forEach(Folder::delete);
-			folder.files().forEach(File::delete);
-		}
-	}
-
-}

+ 0 - 87
main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java

@@ -1,87 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Markus Kreusch
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- ******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-
-/**
- * A {@link File} in a {@link FileSystem}.
- * 
- * @author Markus Kreusch
- */
-public interface File extends Node, Comparable<File> {
-
-	static final int EOF = -1;
-
-	/**
-	 * @return The current size of the file. This value is a snapshot and might have been changed by concurrent modifications.
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs
-	 */
-	long size() throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Opens this file for reading.
-	 * <p>
-	 * An implementation guarantees, that per {@link FileSystem} and
-	 * {@code File} while a {@code ReadableFile} is open no {@link WritableFile}
-	 * can be open and vice versa. A {@link ReadableFile} is open when returned
-	 * from this method and not yet closed using {@link ReadableFile#close()}.
-	 * <br>
-	 * A limitation to the number of {@code ReadableFiles} is in general not
-	 * required but may be set by a specific implementation.
-	 * <p>
-	 * If a {@link WritableFile} for this {@code File} is open the invocation of
-	 * this method will block regarding the specified timeout.<br>
-	 * In addition implementations may block to lock the required IO resources
-	 * to read the file.
-	 * 
-	 * @return a {@link ReadableFile} to work with
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs while opening the file, the
-	 *             file does not exist or is a directory
-	 */
-	ReadableFile openReadable() throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Opens this file for writing.
-	 * <p>
-	 * If the file does not exist a new empty file is created.
-	 * <p>
-	 * An implementation guarantees, that per {@link FileSystem} and
-	 * {@code File} only one {@link WritableFile} is open at a time. A
-	 * {@code WritableFile} is open when returned from this method and not yet
-	 * closed using {@link WritableFile#close()} or
-	 * {@link WritableFile#delete()}.<br>
-	 * In addition while a {@code WritableFile} is open no {@link ReadableFile}
-	 * can be open and vice versa.
-	 * <p>
-	 * If a {@code Readable-} or {@code WritableFile} for this {@code File} is
-	 * open the invocation of this method will block regarding the specified
-	 * timeout.<br>
-	 * In addition implementations may block to lock the required IO resources
-	 * to read the file.
-	 * 
-	 * @return a {@link WritableFile} to work with
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs while opening the file or
-	 *             the file is a directory
-	 */
-	WritableFile openWritable() throws UncheckedIOException;
-
-	default void copyTo(File destination) {
-		Copier.copy(this, destination);
-	}
-
-	/**
-	 * Moves this file including content to a new location specified by <code>destination</code>.
-	 */
-	void moveTo(File destination) throws UncheckedIOException;
-
-}

+ 0 - 30
main/filesystem-api/src/main/java/org/cryptomator/filesystem/FileSystem.java

@@ -1,30 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Markus Kreusch
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- ******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.util.Optional;
-
-/**
- * The root folder of a file system.
- * 
- * @author Markus Kreusch
- */
-public interface FileSystem extends Folder {
-
-	/**
-	 * @return an empty {@link Optional} because a {@link FileSystem} represents
-	 *         the root {@link Folder} and thus does not have a parent
-	 */
-	@Override
-	default Optional<? extends Folder> parent() {
-		return Optional.empty();
-	}
-
-	Optional<Long> quotaUsedBytes();
-
-	Optional<Long> quotaAvailableBytes();
-
-}

+ 0 - 145
main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java

@@ -1,145 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Markus Kreusch
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- ******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.stream.Stream;
-
-/**
- * A {@link Folder} in a {@link FileSystem}.
- * 
- * @author Markus Kreusch
- */
-public interface Folder extends Node {
-
-	/**
-	 * <p>
-	 * Creates a {@link Stream} over all child nodes of this {@code Folder}.
-	 * <p>
-	 * <b>Note:</b> The {@link Stream} may be lazily populated and thus
-	 * {@link IOException IOExceptions} may occurs after this method returned.
-	 * In this case implementors should throw a {@link UncheckedIOException}
-	 * from any method that produces an {@link IOException}. Thus users should
-	 * expect {@link UncheckedIOException UncheckedIOExceptions} when invoking
-	 * methods on the returned {@code Stream}.
-	 * 
-	 * @return the created {@code Stream}
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs while initializing the
-	 *             stream or the {@code Folder} does not exist
-	 */
-	Stream<? extends Node> children() throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Returns the child {@link Node} in this directory of type {@link File}
-	 * with the specified name.
-	 * <p>
-	 * This operation always returns a {@link File} without checking if the file
-	 * exists or is a {@link Folder} instead.
-	 */
-	File file(String name) throws UncheckedIOException;
-
-	/**
-	 * Returns a file by resolving a path relative to this folder.
-	 * 
-	 * @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not. Path must not be empty.
-	 * @return File with the given path relative to this folder
-	 * @throws IllegalArgumentException
-	 *             if relativePath is empty
-	 */
-	default File resolveFile(String relativePath) throws UncheckedIOException, IllegalArgumentException {
-		return PathResolver.resolveFile(this, relativePath);
-	}
-
-	/**
-	 * <p>
-	 * Returns the child {@link Node} in this directory of type {@link Folder}
-	 * with the specified name.
-	 * <p>
-	 * This operation always returns a {@link Folder} without checking if the
-	 * folder exists or is a {@link File} instead.
-	 */
-	Folder folder(String name) throws UncheckedIOException;
-
-	/**
-	 * Returns a folder by resolving a path relative to this folder.
-	 * 
-	 * @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not. Path may be empty.
-	 * @return Folder with the given path relative to this folder. Returns <code>this</code> if path is empty.
-	 */
-	default Folder resolveFolder(String relativePath) throws UncheckedIOException {
-		return PathResolver.resolveFolder(this, relativePath);
-	}
-
-	/**
-	 * Creates the directory including all parent directories, if it doesn't
-	 * exist yet. No effect, if folder already exists.
-	 * 
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs while creating the folder or
-	 *             one of its parents
-	 */
-	void create() throws UncheckedIOException;
-
-	/**
-	 * Recusively copies this directory and all its contents to (not into) the
-	 * given destination, creating nonexisting parent directories. If the target
-	 * exists it is deleted before performing the copy.
-	 * 
-	 * @param target
-	 *            Destination folder. Must not be a descendant of this folder.
-	 */
-	default void copyTo(Folder target) throws UncheckedIOException {
-		Copier.copy(this, target);
-	}
-
-	/**
-	 * Moves this directory and its contents to the given destination. If the
-	 * target exists it is deleted before performing the move.
-	 */
-	void moveTo(Folder target);
-
-	/**
-	 * @return the result of {@link #children()} filtered to contain only
-	 *         {@link File Files}
-	 */
-	default Stream<? extends File> files() throws UncheckedIOException {
-		return children() //
-				.filter(File.class::isInstance) //
-				.map(File.class::cast);
-	}
-
-	/**
-	 * @return the result of {@link #children()} filtered to contain only
-	 *         {@link Folder Folders}
-	 */
-	default Stream<? extends Folder> folders() throws UncheckedIOException {
-		return children() //
-				.filter(Folder.class::isInstance) //
-				.map(Folder.class::cast);
-	}
-
-	/**
-	 * Recursively checks whether this folder or any subfolder contains the
-	 * given node.
-	 * 
-	 * @param node
-	 *            Potential child, grandchild, ...
-	 * @return <code>true</code> if this folder is an ancestor of the node.
-	 */
-	default boolean isAncestorOf(Node node) {
-		if (!node.parent().isPresent()) {
-			return false;
-		} else if (node.parent().get().equals(this)) {
-			return true;
-		} else {
-			return this.isAncestorOf(node.parent().get());
-		}
-	}
-
-}

+ 0 - 131
main/filesystem-api/src/main/java/org/cryptomator/filesystem/FolderVisitor.java

@@ -1,131 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem;
-
-import static java.lang.String.format;
-
-import java.util.function.Consumer;
-
-public class FolderVisitor {
-
-	private final Consumer<Folder> beforeFolderVisitor;
-	private final Consumer<Folder> afterFolderVisitor;
-	private final Consumer<File> fileVisitor;
-	private final Consumer<Node> nodeVisitor;
-	private final int maxDepth;
-
-	public FolderVisitor(FolderVisitorBuilder builder) {
-		this.beforeFolderVisitor = builder.beforeFolderVisitor;
-		this.afterFolderVisitor = builder.afterFolderVisitor;
-		this.fileVisitor = builder.fileVisitor;
-		this.nodeVisitor = builder.nodeVisitor;
-		this.maxDepth = builder.maxDepth;
-	}
-
-	public static FolderVisitorBuilder folderVisitor() {
-		return new FolderVisitorBuilder();
-	}
-
-	public FolderVisitor visit(Folder folder) {
-		return visit(folder, 0);
-	}
-
-	public FolderVisitor visit(File file) {
-		return visit(file, 0);
-	}
-
-	private FolderVisitor visit(Folder folder, int depth) {
-		beforeFolderVisitor.accept(folder);
-		nodeVisitor.accept(folder);
-		final int childDepth = depth + 1;
-		if (childDepth <= maxDepth) {
-			folder.folders().forEach(childFolder -> visit(childFolder, childDepth));
-			folder.files().forEach(childFile -> visit(childFile, childDepth));
-		}
-		afterFolderVisitor.accept(folder);
-		return this;
-	}
-
-	private FolderVisitor visit(File file, int depth) {
-		nodeVisitor.accept(file);
-		fileVisitor.accept(file);
-		return this;
-	}
-
-	public static class FolderVisitorBuilder {
-
-		private Consumer<Folder> beforeFolderVisitor = noOp();
-		private Consumer<Folder> afterFolderVisitor = noOp();
-		private Consumer<File> fileVisitor = noOp();
-		private Consumer<Node> nodeVisitor = noOp();
-		private int maxDepth = Integer.MAX_VALUE;
-
-		private FolderVisitorBuilder() {
-		}
-
-		public FolderVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
-			if (beforeFolderVisitor == null) {
-				throw new IllegalArgumentException("Vistior may not be null");
-			}
-			this.beforeFolderVisitor = beforeFolderVisitor;
-			return this;
-		}
-
-		public FolderVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
-			if (afterFolderVisitor == null) {
-				throw new IllegalArgumentException("Vistior may not be null");
-			}
-			this.afterFolderVisitor = afterFolderVisitor;
-			return this;
-		}
-
-		public FolderVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
-			if (fileVisitor == null) {
-				throw new IllegalArgumentException("Vistior may not be null");
-			}
-			this.fileVisitor = fileVisitor;
-			return this;
-		}
-
-		public FolderVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
-			if (nodeVisitor == null) {
-				throw new IllegalArgumentException("Vistior may not be null");
-			}
-			this.nodeVisitor = nodeVisitor;
-			return this;
-		}
-
-		public FolderVisitorBuilder withMaxDepth(int maxDepth) {
-			if (maxDepth < 0) {
-				throw new IllegalArgumentException(format("maxDepth must not be smaller 0 but was %d", maxDepth));
-			}
-			this.maxDepth = maxDepth;
-			return this;
-		}
-
-		public FolderVisitor visit(Folder folder) {
-			return build().visit(folder);
-		}
-
-		public FolderVisitor visit(File file) {
-			return build().visit(file);
-		}
-
-		public FolderVisitor build() {
-			return new FolderVisitor(this);
-		}
-
-		private static <T> Consumer<T> noOp() {
-			return ignoredParameter -> {
-			};
-		}
-
-	}
-
-}

+ 0 - 100
main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java

@@ -1,100 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Markus Kreusch
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- ******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.time.Instant;
-import java.util.Optional;
-
-/**
- * Represents a node, namely a {@link File} or {@link Folder}, in a
- * {@link FileSystem}.
- * <p>
- * A node's identity (i.e. {@link #hashCode()} and {@link #equals(Object)}) depends on its parent node and its name (forming the node's path).
- * These properties are meant to be immutable. This means that e.g. moving a node doesn't modify the node's identity but rather transfers properties to the destination node.
- * 
- * @author Markus Kreusch
- * @see Folder
- * @see File
- */
-public interface Node {
-
-	String name() throws UncheckedIOException;
-
-	/**
-	 * @return Optional parent folder. No parent is present for the root node (see {@link FileSystem#parent()}).
-	 */
-	Optional<? extends Folder> parent() throws UncheckedIOException;
-
-	/**
-	 * @return <code>true</code> if the node exists.
-	 */
-	boolean exists() throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Deletes the node if it exists.
-	 * <p>
-	 * Does nothing if the node does not exist.
-	 */
-	void delete() throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Determines the last modified date of this node.
-	 * 
-	 * @returns the last modified date of the file
-	 */
-	Instant lastModified() throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Sets the last modified date of the file.
-	 * 
-	 * @param lastModified the time to set as creation time
-	 */
-	void setLastModified(Instant lastModified) throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Determines the creation time of this node.
-	 * <p>
-	 * Note: Getting the creation time may not be supported by all {@link FileSystem FileSystems}.
-	 * 
-	 * @returns the creation time of the file or {@link Optional#empty()} if not supported
-	 */
-	default Optional<Instant> creationTime() throws UncheckedIOException {
-		return Optional.empty();
-	}
-
-	/**
-	 * <p>
-	 * Sets the creation time of this node.
-	 * <p>
-	 * Setting the creation time may not be supported by all {@link FileSystem FileSystems}. If the {@code FileSystem} this {@code Node} belongs to does not support the
-	 * setting the creation time the behavior of this method is unspecified.
-	 * 
-	 * @param creationTime the time to set as creation time
-	 */
-	default void setCreationTime(Instant creationTime) throws UncheckedIOException {
-		throw new UncheckedIOException(new IOException("CreationTime not supported"));
-	}
-
-	/**
-	 * @return the {@link FileSystem} this Node belongs to
-	 */
-	default FileSystem fileSystem() {
-		return parent() //
-				.map(Node::fileSystem) //
-				.orElseGet(() -> (FileSystem) this);
-	}
-
-	default boolean belongsToSameFilesystem(Node other) {
-		return fileSystem() == other.fileSystem();
-	}
-
-}

+ 0 - 71
main/filesystem-api/src/main/java/org/cryptomator/filesystem/OpenFiles.java

@@ -1,71 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.UncheckedIOException;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.stream.Stream;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class OpenFiles implements AutoCloseable {
-
-	private final static Logger LOG = LoggerFactory.getLogger(OpenFiles.class);
-
-	private final Map<File, ReadableFile> readableFiles;
-	private final Map<File, WritableFile> writableFiles;
-
-	public OpenFiles(Map<File, ReadableFile> readableFiles, Map<File, WritableFile> writableFiles) {
-		this.readableFiles = readableFiles;
-		this.writableFiles = writableFiles;
-	}
-
-	@Override
-	public void close() throws UncheckedIOException {
-		OpenFiles.cleanup(readableFiles.values(), writableFiles.values());
-	}
-
-	public ReadableFile readable(File file) {
-		return readableFiles.computeIfAbsent(file, fileNotOpenForReading -> {
-			throw new IllegalArgumentException(String.format("File %s is not open for reading", fileNotOpenForReading));
-		});
-	}
-
-	public WritableFile writable(File file) {
-		return writableFiles.computeIfAbsent(file, fileNotOpenForWriting -> {
-			throw new IllegalArgumentException(String.format("File %s is not open for writing", fileNotOpenForWriting));
-		});
-	}
-
-	static void cleanup(Collection<ReadableFile> readableFiles, Collection<WritableFile> writableFiles) {
-		Iterator<? extends AutoCloseable> iterator = Stream.concat(readableFiles.stream(), writableFiles.stream()).iterator();
-		UncheckedIOException firstException = null;
-		while (iterator.hasNext()) {
-			AutoCloseable openFile = iterator.next();
-			try {
-				openFile.close();
-			} catch (UncheckedIOException e) {
-				if (firstException == null) {
-					firstException = e;
-				} else {
-					firstException.addSuppressed(e);
-				}
-			} catch (Exception e) {
-				LOG.error("Unexpected exception during close on " + openFile.getClass().getSimpleName(), e);
-			}
-		}
-		if (firstException != null) {
-			throw firstException;
-		}
-	}
-
-}

+ 0 - 112
main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java

@@ -1,112 +0,0 @@
-package org.cryptomator.filesystem;
-
-import java.io.FileNotFoundException;
-import java.io.UncheckedIOException;
-import java.util.Arrays;
-import java.util.Iterator;
-
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.StringUtils;
-
-final class PathResolver {
-
-	private static final String DOT = ".";
-	private static final String DOTDOT = "..";
-
-	private PathResolver() {
-	}
-
-	/**
-	 * Resolves a relative path (separated by '/') to a folder, e.g.
-	 * <!-- @formatter:off -->
-	 * <table>
-	 * <thead>
-	 * <tr>
-	 * <th>dir</th>
-	 * <th>path</th>
-	 * <th>result</th>
-	 * </tr>
-	 * </thead>
-	 * <tbody>
-	 * <tr>
-	 * <td>/foo/bar</td>
-	 * <td>foo/bar</td>
-	 * <td>/foo/bar/foo/bar</td>
-	 * </tr>
-	 * <tr>
-	 * <td>/foo/bar</td>
-	 * <td>../baz</td>
-	 * <td>/foo/baz</td>
-	 * </tr>
-	 * <tr>
-	 * <td>/foo/bar</td>
-	 * <td>./foo/..</td>
-	 * <td>/foo/bar</td>
-	 * </tr>
-	 * <tr>
-	 * <td>/foo/bar</td>
-	 * <td>/</td>
-	 * <td>/foo/bar</td>
-	 * </tr>
-	 * <tr>
-	 * <td>/foo/bar</td>
-	 * <td></td>
-	 * <td>/foo/bar</td>
-	 * </tr>
-	 * <tr>
-	 * <td>/foo/bar</td>
-	 * <td>../../..</td>
-	 * <td>Exception</td>
-	 * </tr>
-	 * </tbody>
-	 * </table>
-	 * 
-	 * @param dir The directory from which to resolve the path.
-	 * @param relativePath The path relative to a given directory.
-	 * @return The folder with the given path relative to the given dir.
-	 */
-	public static Folder resolveFolder(Folder dir, String relativePath) {
-		final String[] fragments = StringUtils.split(relativePath, '/');
-		if (ArrayUtils.isEmpty(fragments)) {
-			return dir;
-		}
-		return resolveFolder(dir, Arrays.stream(fragments).iterator());
-	}
-
-	/**
-	 * Resolves a relative path (separated by '/') to a file. Besides returning a File, this method is identical to {@link #resolveFile(Folder, String)}.
-	 * 
-	 * @param dir The directory from which to resolve the path.
-	 * @param relativePath The path relative to a given directory.
-	 * @return The file with the given path relative to the given dir.
-	 * @throws IllegalArgumentException
-	 *             if relativePath is empty, as this path would resolve to the directory itself, which obviously can't be a file.
-	 */
-	public static File resolveFile(Folder dir, String relativePath) {
-		final String[] fragments = StringUtils.split(relativePath, '/');
-		if (ArrayUtils.isEmpty(fragments)) {
-			throw new IllegalArgumentException("Empty relativePath");
-		}
-		final Folder folder = resolveFolder(dir, Arrays.stream(fragments).limit(fragments.length - 1).iterator());
-		final String filename = fragments[fragments.length - 1];
-		return folder.file(filename);
-	}
-
-	private static Folder resolveFolder(Folder dir, Iterator<String> remainingPathFragments) {
-		if (!remainingPathFragments.hasNext()) {
-			return dir;
-		}
-		final String fragment = remainingPathFragments.next();
-		assert fragment.length() > 0 : "iterator must not contain empty fragments";
-		if (DOT.equals(fragment)) {
-			return resolveFolder(dir, remainingPathFragments);
-		} else if (DOTDOT.equals(fragment) && dir.parent().isPresent()) {
-			return resolveFolder(dir.parent().get(), remainingPathFragments);
-		} else if (DOTDOT.equals(fragment) && !dir.parent().isPresent()) {
-			throw new UncheckedIOException(new FileNotFoundException("Unresolvable path"));
-		} else {
-			return resolveFolder(dir.folder(fragment), remainingPathFragments);
-		}
-	}
-
-}

+ 0 - 50
main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java

@@ -1,50 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Markus Kreusch
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- ******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ReadableByteChannel;
-
-public interface ReadableFile extends ReadableByteChannel {
-
-	/**
-	 * <p>
-	 * Tries to fill the remaining space in the given byte buffer with data from
-	 * this readable bytes from the current position.
-	 * <p>
-	 * May read less bytes if the end of this readable bytes has been reached.
-	 * 
-	 * @param target
-	 *            the byte buffer to fill
-	 * @return the number of bytes actually read, or {@code -1} if the end of
-	 *         file has been reached
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs while reading from this
-	 *             {@code ReadableBytes}
-	 */
-	@Override
-	int read(ByteBuffer target) throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Fast-forwards or rewinds the file to the specified position.
-	 * <p>
-	 * Consecutive reads on the file will begin at the new position.
-	 * 
-	 * @param position
-	 *            the position to set the file to
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs
-	 * 
-	 */
-	void position(long position) throws UncheckedIOException;
-
-	@Override
-	void close() throws UncheckedIOException;
-
-}

+ 0 - 63
main/filesystem-api/src/main/java/org/cryptomator/filesystem/WritableFile.java

@@ -1,63 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Markus Kreusch
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- ******************************************************************************/
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.WritableByteChannel;
-
-public interface WritableFile extends WritableByteChannel {
-
-	void truncate() throws UncheckedIOException;
-
-	/**
-	 * Writes the data in the given byte buffer to this readable bytes at the
-	 * current position.
-	 * 
-	 * @param source
-	 *            the byte buffer to use
-	 * @return the number of bytes written, always equal to
-	 *         {@code source.remaining()}
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs while writing
-	 */
-	@Override
-	int write(ByteBuffer source) throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Fast-forwards or rewinds the file to the specified position.
-	 * <p>
-	 * Consecutive writes on the file will begin at the new position.
-	 * <p>
-	 * If the position is set to a value greater than the current end of file
-	 * consecutive writes will write data to the given position. The value of
-	 * all bytes between this position and the previous end of file will be
-	 * unspecified.
-	 * 
-	 * @param position
-	 *            the position to set the file to
-	 * @throws UncheckedIOException
-	 *             if an {@link IOException} occurs
-	 */
-	void position(long position) throws UncheckedIOException;
-
-	/**
-	 * <p>
-	 * Closes this {@code WritableFile} which finally commits all operations
-	 * performed on it to the underlying file system.
-	 * <p>
-	 * After a {@code WritableFile} has been closed all other operations will
-	 * throw an {@link UncheckedIOException}.
-	 * <p>
-	 * Invoking this method on a {@link WritableFile} which has already been
-	 * closed does nothing.
-	 */
-	@Override
-	void close() throws UncheckedIOException;
-
-}

+ 0 - 82
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java

@@ -1,82 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.io.UncheckedIOException;
-import java.util.Optional;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
-
-public abstract class DelegatingFile<D extends DelegatingFolder<D, ?>> extends DelegatingNode<File> implements File {
-
-	private final D parent;
-
-	public DelegatingFile(D parent, File delegate) {
-		super(delegate);
-		this.parent = parent;
-	}
-
-	@Override
-	public Optional<D> parent() throws UncheckedIOException {
-		return Optional.of(parent);
-	}
-
-	@Override
-	public long size() throws UncheckedIOException {
-		return delegate.size();
-	}
-
-	@Override
-	public ReadableFile openReadable() throws UncheckedIOException {
-		return delegate.openReadable();
-	}
-
-	@Override
-	public WritableFile openWritable() throws UncheckedIOException {
-		return delegate.openWritable();
-	}
-
-	@Override
-	public void copyTo(File destination) {
-		if (getClass().equals(destination.getClass())) {
-			final File delegateDest = ((DelegatingFile<?>) destination).delegate;
-			delegate.copyTo(delegateDest);
-		} else {
-			delegate.copyTo(destination);
-		}
-	}
-
-	@Override
-	public void moveTo(File destination) {
-		if (getClass().equals(destination.getClass())) {
-			final File delegateDest = ((DelegatingFile<?>) destination).delegate;
-			delegate.moveTo(delegateDest);
-		} else {
-			throw new IllegalArgumentException("Can only move DelegatingFile to other DelegatingFile.");
-		}
-	}
-
-	@Override
-	public void delete() throws UncheckedIOException {
-		delegate.delete();
-	}
-
-	@Override
-	public int compareTo(File o) {
-		if (getClass().equals(o.getClass())) {
-			final File delegateOther = ((DelegatingFile<?>) o).delegate;
-			return delegate.compareTo(delegateOther);
-		} else {
-			return delegate.compareTo(o);
-		}
-	}
-
-}

+ 0 - 22
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java

@@ -1,22 +0,0 @@
-package org.cryptomator.filesystem.delegating;
-
-import java.util.Optional;
-
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-
-public interface DelegatingFileSystem extends FileSystem {
-
-	Folder getDelegate();
-
-	@Override
-	default Optional<Long> quotaUsedBytes() {
-		return getDelegate().fileSystem().quotaUsedBytes();
-	}
-
-	@Override
-	default Optional<Long> quotaAvailableBytes() {
-		return getDelegate().fileSystem().quotaAvailableBytes();
-	}
-
-}

+ 0 - 100
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java

@@ -1,100 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.io.UncheckedIOException;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import org.cryptomator.common.WeakValuedCache;
-import org.cryptomator.common.streams.AutoClosingStream;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.Node;
-
-public abstract class DelegatingFolder<D extends DelegatingFolder<D, F>, F extends DelegatingFile<D>> extends DelegatingNode<Folder>implements Folder {
-
-	private final D parent;
-	private final WeakValuedCache<Folder, D> folders = WeakValuedCache.usingLoader(this::newFolder);
-	private final WeakValuedCache<File, F> files = WeakValuedCache.usingLoader(this::newFile);
-
-	public DelegatingFolder(D parent, Folder delegate) {
-		super(delegate);
-		this.parent = parent;
-	}
-
-	@Override
-	public Optional<D> parent() throws UncheckedIOException {
-		return Optional.ofNullable(parent);
-	}
-
-	@Override
-	public Stream<? extends Node> children() throws UncheckedIOException {
-		return AutoClosingStream.from(Stream.concat(folders(), files()));
-	}
-
-	@Override
-	public Stream<D> folders() {
-		return delegate.folders().map(folders::get);
-	}
-
-	@Override
-	public Stream<F> files() throws UncheckedIOException {
-		return delegate.files().map(files::get);
-	}
-
-	@Override
-	public F file(String name) throws UncheckedIOException {
-		return files.get(delegate.file(name));
-	}
-
-	protected abstract F newFile(File delegate);
-
-	@Override
-	public D folder(String name) throws UncheckedIOException {
-		return folders.get(delegate.folder(name));
-	}
-
-	protected abstract D newFolder(Folder delegate);
-
-	@Override
-	public void create() throws UncheckedIOException {
-		if (exists()) {
-			return;
-		}
-		parent().ifPresent(p -> p.create());
-		delegate.create();
-	}
-
-	@Override
-	public void delete() {
-		delegate.delete();
-	}
-
-	@Override
-	public void copyTo(Folder destination) throws UncheckedIOException {
-		if (destination instanceof DelegatingFolder) {
-			final Folder delegateDest = ((DelegatingFolder<?, ?>) destination).delegate;
-			delegate.copyTo(delegateDest);
-		} else {
-			throw new IllegalArgumentException("Can only copy DelegatingFolder to other DelegatingFolder.");
-		}
-	}
-
-	@Override
-	public void moveTo(Folder destination) {
-		if (getClass().equals(destination.getClass())) {
-			final Folder delegateDest = ((DelegatingFolder<?, ?>) destination).delegate;
-			delegate.moveTo(delegateDest);
-		} else {
-			throw new IllegalArgumentException("Can only move DelegatingFolder to other DelegatingFolder.");
-		}
-	}
-
-}

+ 0 - 78
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java

@@ -1,78 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.io.UncheckedIOException;
-import java.time.Instant;
-import java.util.Optional;
-
-import org.cryptomator.filesystem.Node;
-
-public abstract class DelegatingNode<T extends Node> implements Node {
-
-	protected final T delegate;
-
-	public DelegatingNode(T delegate) {
-		if (delegate == null) {
-			throw new IllegalArgumentException("Delegate must not be null");
-		}
-		this.delegate = delegate;
-	}
-
-	@Override
-	public String name() throws UncheckedIOException {
-		return delegate.name();
-	}
-
-	@Override
-	public boolean exists() throws UncheckedIOException {
-		return delegate.exists();
-	}
-
-	@Override
-	public Instant lastModified() throws UncheckedIOException {
-		return delegate.lastModified();
-	}
-
-	@Override
-	public void setLastModified(Instant instant) throws UncheckedIOException {
-		delegate.setLastModified(instant);
-	}
-
-	@Override
-	public Optional<Instant> creationTime() throws UncheckedIOException {
-		return delegate.creationTime();
-	}
-
-	@Override
-	public void setCreationTime(Instant instant) throws UncheckedIOException {
-		delegate.setCreationTime(instant);
-	}
-
-	@Override
-	public int hashCode() {
-		return delegate.hashCode();
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (obj instanceof DelegatingNode) {
-			DelegatingNode<?> other = (DelegatingNode<?>) obj;
-			return this.delegate.equals(other.delegate);
-		} else {
-			return false;
-		}
-	}
-
-	@Override
-	public String toString() {
-		return "Delegate[" + delegate + "]";
-	}
-
-}

+ 0 - 44
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java

@@ -1,44 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-
-import org.cryptomator.filesystem.ReadableFile;
-
-public class DelegatingReadableFile implements ReadableFile {
-
-	private final ReadableFile delegate;
-
-	public DelegatingReadableFile(ReadableFile delegate) {
-		this.delegate = delegate;
-	}
-
-	@Override
-	public boolean isOpen() {
-		return delegate.isOpen();
-	}
-
-	@Override
-	public int read(ByteBuffer target) throws UncheckedIOException {
-		return delegate.read(target);
-	}
-
-	@Override
-	public void position(long position) throws UncheckedIOException {
-		delegate.position(position);
-	}
-
-	@Override
-	public void close() throws UncheckedIOException {
-		delegate.close();
-	}
-
-}

+ 0 - 49
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingWritableFile.java

@@ -1,49 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-
-import org.cryptomator.filesystem.WritableFile;
-
-public class DelegatingWritableFile implements WritableFile {
-
-	final WritableFile delegate;
-
-	public DelegatingWritableFile(WritableFile delegate) {
-		this.delegate = delegate;
-	}
-
-	@Override
-	public boolean isOpen() {
-		return delegate.isOpen();
-	}
-
-	@Override
-	public void truncate() throws UncheckedIOException {
-		delegate.truncate();
-	}
-
-	@Override
-	public int write(ByteBuffer source) throws UncheckedIOException {
-		return delegate.write(source);
-	}
-
-	@Override
-	public void position(long position) throws UncheckedIOException {
-		delegate.position(position);
-	}
-
-	@Override
-	public void close() throws UncheckedIOException {
-		delegate.close();
-	}
-
-}

+ 0 - 13
main/filesystem-api/src/main/java/org/cryptomator/filesystem/package-info.java

@@ -1,13 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-/**
- * Defines a file system abstraction to allow access to real and virtual file
- * systems through a common API.
- */
-package org.cryptomator.filesystem;

+ 0 - 35
main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java

@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.io;
-
-import java.nio.ByteBuffer;
-
-public final class ByteBuffers {
-
-	private ByteBuffers() {
-	}
-
-	/**
-	 * Copies as many bytes as possible from the given source to the destination buffer.
-	 * The position of both buffers will be incremented by as many bytes as have been copied.
-	 * 
-	 * @param source ByteBuffer from which bytes are read
-	 * @param destination ByteBuffer into which bytes are written
-	 * @return number of bytes copied, i.e. {@link ByteBuffer#remaining() source.remaining()} or {@link ByteBuffer#remaining() destination.remaining()}, whatever is less.
-	 */
-	public static int copy(ByteBuffer source, ByteBuffer destination) {
-		final int numBytes = Math.min(source.remaining(), destination.remaining());
-		final ByteBuffer tmp = source.asReadOnlyBuffer();
-		tmp.limit(tmp.position() + numBytes);
-		destination.put(tmp);
-		source.position(tmp.position()); // until now only tmp pos has been incremented, so we need to adjust the position
-		return numBytes;
-	}
-
-}

+ 0 - 59
main/filesystem-api/src/main/java/org/cryptomator/io/FileContents.java

@@ -1,59 +0,0 @@
-package org.cryptomator.io;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.UncheckedIOException;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-import org.apache.commons.io.IOUtils;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.WritableFile;
-
-public final class FileContents {
-
-	public static final FileContents UTF_8 = FileContents.withCharset(StandardCharsets.UTF_8);
-
-	private final Charset charset;
-
-	private FileContents(Charset charset) {
-		this.charset = charset;
-	}
-
-	/**
-	 * Reads the whole content from the given file.
-	 * 
-	 * @param file File whose content should be read.
-	 * @return The file's content interpreted in this FileContents' charset.
-	 */
-	public String readContents(File file) {
-		try ( //
-				ReadableByteChannel channel = file.openReadable(); //
-				Reader reader = Channels.newReader(channel, charset.newDecoder(), -1)) {
-			return IOUtils.toString(reader);
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	/**
-	 * Writes the string into the file encoded with this FileContents' charset.
-	 * This methods replaces any previously existing content, i.e. the string will be the sole content.
-	 * 
-	 * @param file File whose content should be written.
-	 * @param content The new content.
-	 */
-	public void writeContents(File file, String content) {
-		try (WritableFile writable = file.openWritable()) {
-			writable.truncate();
-			writable.write(charset.encode(content));
-		}
-	}
-
-	public static FileContents withCharset(Charset charset) {
-		return new FileContents(charset);
-	}
-
-}

+ 0 - 36
main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java

@@ -1,36 +0,0 @@
-package org.cryptomator.filesystem;
-
-import java.nio.ByteBuffer;
-
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeDiagnosingMatcher;
-
-public class ByteBufferMatcher {
-
-	public static Matcher<ByteBuffer> byteBufferFilledWith(int value) {
-		if (((byte) value) != value) {
-			throw new IllegalArgumentException("Invalid byte value");
-		}
-		return new TypeSafeDiagnosingMatcher<ByteBuffer>(ByteBuffer.class) {
-
-			@Override
-			public void describeTo(Description description) {
-				description.appendText("a byte buffer filled with " + value);
-			}
-
-			@Override
-			protected boolean matchesSafely(ByteBuffer item, Description mismatchDescription) {
-				while (item.hasRemaining()) {
-					byte currentValue = item.get();
-					if (currentValue != value) {
-						mismatchDescription.appendText("a byte buffer containing also " + currentValue);
-						return false;
-					}
-				}
-				return true;
-			}
-		};
-	}
-
-}

+ 0 - 227
main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java

@@ -1,227 +0,0 @@
-package org.cryptomator.filesystem;
-
-import static java.util.Arrays.asList;
-import static org.cryptomator.common.test.matcher.ContainsMatcher.contains;
-import static org.cryptomator.common.test.mockito.Answers.collectParameters;
-import static org.cryptomator.common.test.mockito.Answers.consecutiveAnswers;
-import static org.cryptomator.common.test.mockito.Answers.value;
-import static org.cryptomator.filesystem.File.EOF;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.stream.Stream;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.mockito.stubbing.Answer;
-
-import de.bechte.junit.runners.context.HierarchicalContextRunner;
-
-@RunWith(HierarchicalContextRunner.class)
-public class CopierTest {
-
-	@Rule
-	public ExpectedException thrown = ExpectedException.none();
-
-	@Rule
-	public MockitoRule mockitoRule = MockitoJUnit.rule();
-
-	public class CopyFiles {
-
-		@Mock
-		private File source;
-
-		@Mock
-		private File destination;
-
-		@Mock
-		private ReadableFile readable;
-
-		@Mock
-		private WritableFile writable;
-
-		@Before
-		public void setUp() {
-			when(source.openReadable()).thenReturn(readable);
-			when(destination.openWritable()).thenReturn(writable);
-		}
-
-		@Test
-		public void testCopyFileOpensFilesInSortedOrderIfSourceIsSmallerDestination() {
-			mockCompareToWithOrder(source, destination);
-			when(readable.read(any())).thenReturn(EOF);
-
-			Copier.copy(source, destination);
-
-			InOrder inOrder = inOrder(source, destination);
-			inOrder.verify(source).openReadable();
-			inOrder.verify(destination).openWritable();
-		}
-
-		@Test
-		public void testCopyFileOpensFilesInSortedOrderIfDestinationIsSmallerSource() {
-			mockCompareToWithOrder(destination, source);
-			when(readable.read(any())).thenReturn(EOF);
-
-			Copier.copy(source, destination);
-
-			InOrder inOrder = inOrder(source, destination);
-			inOrder.verify(destination).openWritable();
-			inOrder.verify(source).openReadable();
-		}
-
-		@Test
-		public void testCopyFileReadsAndWritesReadableSourceAndWritableDestintationUntilEof() {
-			int irrelevantValue = 0;
-			Collection<byte[]> written = new ArrayList<>();
-			mockCompareToWithOrder(source, destination);
-			byte[] read1 = {1, 48, 32, 33, 22};
-			byte[] read2 = {4, 3, 1, -2, -8};
-			when(readable.read(any())).then(consecutiveAnswers(fillBufferWith(read1), fillBufferWith(read2), value(EOF)));
-			when(writable.write(any())).then(collectParameters(value(irrelevantValue), (ByteBuffer buffer) -> {
-				byte[] data = new byte[buffer.remaining()];
-				buffer.get(data);
-				written.add(data);
-			}));
-
-			Copier.copy(source, destination);
-
-			InOrder inOrder = inOrder(readable, writable);
-			inOrder.verify(writable).truncate();
-			inOrder.verify(readable).read(any());
-			inOrder.verify(writable).write(any());
-			inOrder.verify(readable).read(any());
-			inOrder.verify(writable).write(any());
-			inOrder.verify(readable).read(any());
-			inOrder.verify(readable).close();
-			inOrder.verify(writable).close();
-
-			assertThat(written, contains(is(read1), is(read2)));
-		}
-
-		private Answer<Integer> fillBufferWith(byte[] data) {
-			return new Answer<Integer>() {
-				@Override
-				public Integer answer(InvocationOnMock invocation) throws Throwable {
-					ByteBuffer buffer = invocation.getArgumentAt(0, ByteBuffer.class);
-					for (byte value : data) {
-						buffer.put(value);
-					}
-					return data.length;
-				}
-
-			};
-		}
-
-		private void mockCompareToWithOrder(File first, File last) {
-			when(first.compareTo(last)).thenReturn(-1);
-			when(last.compareTo(first)).thenReturn(1);
-		}
-
-	}
-
-	public class CopyFolders {
-
-		@Mock
-		private Folder source;
-
-		@Mock
-		private Folder destination;
-
-		@Test
-		public void testCopyFolderDeletesAndCreatesDestinationBeforeIteratingOverTheFilesAndFoldersInSource() {
-			when(source.files()).thenReturn(Stream.empty());
-			when(source.folders()).thenReturn(Stream.empty());
-
-			Copier.copy(source, destination);
-
-			InOrder inOrder = inOrder(source, destination);
-			inOrder.verify(destination).delete();
-			inOrder.verify(destination).create();
-			inOrder.verify(source).files();
-			inOrder.verify(source).folders();
-		}
-
-		@Test
-		@SuppressWarnings({"unchecked", "rawtypes"})
-		public void testCopyFolderInvokesCopyToOnAllFilesInSourceWithFileWithSameNameFromDestination() {
-			String filename1 = "nameOfFile1";
-			String filename2 = "nameOfFile2";
-			File file1 = mock(File.class);
-			File file2 = mock(File.class);
-			File destinationFile1 = mock(File.class);
-			File destinationFile2 = mock(File.class);
-			when(source.files()).thenReturn((Stream) asList(file1, file2).stream());
-			when(source.folders()).thenReturn(Stream.empty());
-			when(destination.file(filename1)).thenReturn(destinationFile1);
-			when(destination.file(filename2)).thenReturn(destinationFile2);
-			when(file1.name()).thenReturn(filename1);
-			when(file2.name()).thenReturn(filename2);
-
-			Copier.copy(source, destination);
-
-			verify(file1).copyTo(destinationFile1);
-			verify(file2).copyTo(destinationFile2);
-		}
-
-		@Test
-		@SuppressWarnings({"unchecked", "rawtypes"})
-		public void testCopyFolderInvokesCopyToOnAllFoldersInSourceWithFolderWithSameNameFromDestination() {
-			String folderName1 = "nameOfFolder1";
-			String folderName2 = "nameOfFolder2";
-			Folder folder1 = mock(Folder.class);
-			Folder folder2 = mock(Folder.class);
-			Folder destinationfolder1 = mock(Folder.class);
-			Folder destinationfolder2 = mock(Folder.class);
-			when(source.folders()).thenReturn((Stream) asList(folder1, folder2).stream());
-			when(source.files()).thenReturn(Stream.empty());
-			when(destination.folder(folderName1)).thenReturn(destinationfolder1);
-			when(destination.folder(folderName2)).thenReturn(destinationfolder2);
-			when(folder1.name()).thenReturn(folderName1);
-			when(folder2.name()).thenReturn(folderName2);
-
-			Copier.copy(source, destination);
-
-			verify(folder1).copyTo(destinationfolder1);
-			verify(folder2).copyTo(destinationfolder2);
-		}
-
-		@Test
-		public void testCopyFolderFailsWithIllegalArgumentExceptionIfSourceIsNestedInDestination() {
-			when(source.isAncestorOf(destination)).thenReturn(false);
-			when(destination.isAncestorOf(source)).thenReturn(true);
-
-			thrown.expect(IllegalArgumentException.class);
-
-			Copier.copy(source, destination);
-		}
-
-		@Test
-		public void testCopyFolderFailsWithIllegalArgumentExceptionIfDestinationIsNestedInSource() {
-			when(source.isAncestorOf(destination)).thenReturn(true);
-			when(destination.isAncestorOf(source)).thenReturn(false);
-
-			thrown.expect(IllegalArgumentException.class);
-
-			Copier.copy(source, destination);
-		}
-
-	}
-
-}

+ 0 - 70
main/filesystem-api/src/test/java/org/cryptomator/filesystem/PathResolverTest.java

@@ -1,70 +0,0 @@
-package org.cryptomator.filesystem;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.Optional;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class PathResolverTest {
-
-	private final Folder root = Mockito.mock(Folder.class);
-	private final Folder foo = Mockito.mock(Folder.class);
-	private final Folder bar = Mockito.mock(Folder.class);
-	private final File baz = Mockito.mock(File.class);
-
-	@Before
-	public void configureMocks() throws IOException {
-		Mockito.doReturn(Optional.empty()).when(root).parent();
-		Mockito.doReturn(Optional.of(root)).when(foo).parent();
-		Mockito.doReturn(Optional.of(foo)).when(bar).parent();
-
-		Mockito.doReturn(foo).when(root).folder("foo");
-		Mockito.doReturn(bar).when(foo).folder("bar");
-		Mockito.doReturn(baz).when(bar).file("baz");
-	}
-
-	@Test
-	public void testResolveSameFolder() {
-		Assert.assertEquals(foo, PathResolver.resolveFolder(foo, ""));
-		Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "/"));
-		Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "///"));
-	}
-
-	@Test
-	public void testResolveChildFolder() {
-		Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/bar"));
-		Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/./bar"));
-		Assert.assertEquals(bar, PathResolver.resolveFolder(root, "./foo/././bar"));
-	}
-
-	@Test
-	public void testResolveParentFolder() {
-		Assert.assertEquals(foo, PathResolver.resolveFolder(bar, ".."));
-		Assert.assertEquals(root, PathResolver.resolveFolder(bar, "../.."));
-	}
-
-	@Test
-	public void testResolveSiblingFolder() {
-		Assert.assertEquals(foo, PathResolver.resolveFolder(bar, "../../foo"));
-	}
-
-	@Test(expected = UncheckedIOException.class)
-	public void testResolveUnresolvableFolder() {
-		PathResolver.resolveFolder(root, "..");
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void testResolveFileWithEmptyPath() {
-		PathResolver.resolveFile(root, "");
-	}
-
-	@Test
-	public void testResolveFile() {
-		Assert.assertEquals(baz, PathResolver.resolveFile(foo, "../foo/bar/./baz"));
-	}
-
-}

+ 0 - 32
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileSystemTest.java

@@ -1,32 +0,0 @@
-package org.cryptomator.filesystem.delegating;
-
-import java.util.Optional;
-
-import org.cryptomator.filesystem.FileSystem;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class DelegatingFileSystemTest {
-
-	@Test
-	public void testQuotaAvailableBytes() {
-		FileSystem mockFs = Mockito.mock(FileSystem.class);
-		Mockito.when(mockFs.fileSystem()).thenReturn(mockFs);
-		Mockito.when(mockFs.quotaAvailableBytes()).thenReturn(Optional.of(42l));
-
-		DelegatingFileSystem delegatingFs = TestDelegatingFileSystem.withRoot(mockFs);
-		Assert.assertEquals(mockFs.quotaAvailableBytes(), delegatingFs.quotaAvailableBytes());
-	}
-
-	@Test
-	public void testQuotaUsedBytes() {
-		FileSystem mockFs = Mockito.mock(FileSystem.class);
-		Mockito.when(mockFs.fileSystem()).thenReturn(mockFs);
-		Mockito.when(mockFs.quotaUsedBytes()).thenReturn(Optional.of(23l));
-
-		DelegatingFileSystem delegatingFs = TestDelegatingFileSystem.withRoot(mockFs);
-		Assert.assertEquals(mockFs.quotaUsedBytes(), delegatingFs.quotaUsedBytes());
-	}
-
-}

+ 0 - 186
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java

@@ -1,186 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.time.Instant;
-import java.util.Optional;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class DelegatingFileTest {
-
-	@Test
-	public void testName() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Mockito.when(mockFile.name()).thenReturn("Test");
-		Assert.assertEquals(mockFile.name(), delegatingFile.name());
-	}
-
-	@Test
-	public void testSize() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Mockito.when(mockFile.size()).thenReturn(42l);
-		Assert.assertEquals(42l, delegatingFile.size());
-		Mockito.verify(mockFile).size();
-	}
-
-	@Test
-	public void testParent() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		File mockFile = Mockito.mock(File.class);
-
-		TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(delegatingParent, mockFile);
-		Assert.assertEquals(delegatingParent, delegatingFile.parent().get());
-	}
-
-	@Test
-	public void testExists() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Mockito.when(mockFile.exists()).thenReturn(true);
-		Assert.assertTrue(delegatingFile.exists());
-
-		Mockito.when(mockFile.exists()).thenReturn(false);
-		Assert.assertFalse(delegatingFile.exists());
-	}
-
-	@Test
-	public void testLastModified() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Instant now = Instant.now();
-		Mockito.when(mockFile.lastModified()).thenReturn(now);
-		Assert.assertEquals(now, delegatingFile.lastModified());
-	}
-
-	@Test
-	public void testSetLastModified() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Instant now = Instant.now();
-		delegatingFile.setLastModified(now);
-		Mockito.verify(mockFile).setLastModified(now);
-	}
-
-	@Test
-	public void testCreationTime() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Instant now = Instant.now();
-		Mockito.when(mockFile.creationTime()).thenReturn(Optional.of(now));
-		Assert.assertEquals(now, delegatingFile.creationTime().get());
-	}
-
-	@Test
-	public void testSetCreationTime() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		Instant now = Instant.now();
-		delegatingFile.setCreationTime(now);
-		Mockito.verify(mockFile).setCreationTime(now);
-	}
-
-	@Test
-	public void testOpenReadable() {
-		File mockFile = Mockito.mock(File.class);
-		ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
-
-		Mockito.when(mockFile.openReadable()).thenReturn(mockReadableFile);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-		Assert.assertNotNull(delegatingFile.openReadable());
-	}
-
-	@Test
-	public void testOpenWritable() {
-		File mockFile = Mockito.mock(File.class);
-		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
-
-		Mockito.when(mockFile.openWritable()).thenReturn(mockWritableFile);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-		Assert.assertNotNull(delegatingFile.openWritable());
-	}
-
-	@Test
-	public void testMoveTo() {
-		File mockFile1 = Mockito.mock(File.class);
-		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
-		DelegatingFile<?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
-
-		delegatingFile1.moveTo(delegatingFile2);
-		Mockito.verify(mockFile1).moveTo(mockFile2);
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void testMoveToDestinationFromDifferentLayer() {
-		File mockFile1 = Mockito.mock(File.class);
-		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
-
-		delegatingFile1.moveTo(mockFile2);
-	}
-
-	@Test
-	public void testCopyTo() {
-		File mockFile1 = Mockito.mock(File.class);
-		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
-		DelegatingFile<?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
-
-		delegatingFile1.copyTo(delegatingFile2);
-		Mockito.verify(mockFile1).copyTo(mockFile2);
-	}
-
-	@Test
-	public void testCopyToDestinationFromDifferentLayer() {
-		File mockFile1 = Mockito.mock(File.class);
-		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
-
-		delegatingFile1.copyTo(mockFile2);
-		Mockito.verify(mockFile1).copyTo(mockFile2);
-	}
-
-	@Test
-	public void testDelete() {
-		File mockFile = Mockito.mock(File.class);
-		DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
-
-		delegatingFile.delete();
-		Mockito.verify(mockFile).delete();
-	}
-
-	@Test
-	public void testCompareTo() {
-		File mockFile1 = Mockito.mock(File.class);
-		File mockFile2 = Mockito.mock(File.class);
-
-		Mockito.when(mockFile1.compareTo(mockFile2)).thenReturn(-1);
-		DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
-		DelegatingFile<?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
-		Assert.assertEquals(-1, delegatingFile1.compareTo(delegatingFile2));
-	}
-
-}

+ 0 - 206
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java

@@ -1,206 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.Node;
-import org.hamcrest.Matchers;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class DelegatingFolderTest {
-
-	@Test
-	public void testName() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		Mockito.when(mockFolder.name()).thenReturn("Test");
-		Assert.assertEquals(mockFolder.name(), delegatingFolder.name());
-	}
-
-	@Test
-	public void testParent() {
-		Folder mockFolder1 = Mockito.mock(Folder.class);
-		Folder mockFolder2 = Mockito.mock(Folder.class);
-
-		TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder1);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(delegatingParent, mockFolder2);
-		Assert.assertEquals(delegatingParent, delegatingFolder.parent().get());
-	}
-
-	@Test
-	public void testExists() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		Mockito.when(mockFolder.exists()).thenReturn(true);
-		Assert.assertTrue(delegatingFolder.exists());
-
-		Mockito.when(mockFolder.exists()).thenReturn(false);
-		Assert.assertFalse(delegatingFolder.exists());
-	}
-
-	@Test
-	public void testLastModified() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		Instant now = Instant.now();
-		Mockito.when(mockFolder.lastModified()).thenReturn(now);
-		Assert.assertEquals(now, delegatingFolder.lastModified());
-	}
-
-	@Test
-	public void testSetLastModified() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		Instant now = Instant.now();
-		delegatingFolder.setLastModified(now);
-		Mockito.verify(mockFolder).setLastModified(now);
-	}
-
-	@Test
-	public void testCreationTime() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		Instant now = Instant.now();
-		Mockito.when(mockFolder.creationTime()).thenReturn(Optional.of(now));
-		Assert.assertEquals(now, delegatingFolder.creationTime().get());
-	}
-
-	@Test
-	public void testSetCreationTime() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		Instant now = Instant.now();
-		delegatingFolder.setCreationTime(now);
-		Mockito.verify(mockFolder).setCreationTime(now);
-	}
-
-	@Test
-	public void testChildren() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		TestDelegatingFileSystem delegatingFolder = TestDelegatingFileSystem.withRoot(mockFolder);
-
-		Folder subFolder1 = Mockito.mock(Folder.class);
-		TestDelegatingFolder delegatingSubFolder1 = new TestDelegatingFolder(delegatingFolder, subFolder1);
-		File subFile1 = Mockito.mock(File.class);
-		TestDelegatingFile delegatingSubFile1 = new TestDelegatingFile(delegatingFolder, subFile1);
-
-		/* folders */
-		Mockito.when(mockFolder.folder("subFolder1")).thenReturn(subFolder1);
-		Assert.assertEquals(delegatingSubFolder1, delegatingFolder.folder("subFolder1"));
-
-		Mockito.<Stream<? extends Folder>>when(mockFolder.folders()).thenAnswer((invocation) -> {
-			return Arrays.stream(new Folder[] {subFolder1});
-		});
-		List<TestDelegatingFolder> subFolders = delegatingFolder.folders().collect(Collectors.toList());
-		Assert.assertThat(subFolders, Matchers.containsInAnyOrder(delegatingSubFolder1));
-
-		/* files */
-		Mockito.when(mockFolder.file("subFile1")).thenReturn(subFile1);
-		Assert.assertEquals(delegatingSubFile1, delegatingFolder.file("subFile1"));
-
-		Mockito.<Stream<? extends File>>when(mockFolder.files()).thenAnswer((invocation) -> {
-			return Arrays.stream(new File[] {subFile1});
-		});
-		List<TestDelegatingFile> subFiles = delegatingFolder.files().collect(Collectors.toList());
-		Assert.assertThat(subFiles, Matchers.containsInAnyOrder(delegatingSubFile1));
-
-		/* files and folders */
-		List<Node> children = delegatingFolder.children().collect(Collectors.toList());
-		DelegatingNode<?>[] expectedChildren = new DelegatingNode[] {delegatingSubFolder1, delegatingSubFile1};
-		Assert.assertThat(children, Matchers.containsInAnyOrder(expectedChildren));
-
-	}
-
-	@Test
-	public void testMoveTo() {
-		Folder mockFolder1 = Mockito.mock(Folder.class);
-		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
-		DelegatingFolder<?, ?> delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2);
-
-		delegatingFolder1.moveTo(delegatingFolder2);
-		Mockito.verify(mockFolder1).moveTo(mockFolder2);
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void testMoveToDestinationFromDifferentLayer() {
-		Folder mockFolder1 = Mockito.mock(Folder.class);
-		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
-
-		delegatingFolder1.moveTo(mockFolder2);
-	}
-
-	@Test
-	public void testCopyTo() {
-		Folder mockFolder1 = Mockito.mock(Folder.class);
-		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
-		DelegatingFolder<?, ?> delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2);
-
-		delegatingFolder1.copyTo(delegatingFolder2);
-		Mockito.verify(mockFolder1).copyTo(mockFolder2);
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void testCopyToDestinationFromDifferentLayer() {
-		Folder mockFolder1 = Mockito.mock(Folder.class);
-		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
-
-		delegatingFolder1.copyTo(mockFolder2);
-	}
-
-	@Test
-	public void testCreate() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		delegatingFolder.create();
-		Mockito.verify(mockFolder).create();
-	}
-
-	@Test
-	public void testDelete() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-
-		delegatingFolder.delete();
-		Mockito.verify(mockFolder).delete();
-	}
-
-	@Test
-	public void testSubresourcesAreSameInstance() {
-		Folder mockFolder = Mockito.mock(Folder.class);
-		Folder mockSubFolder = Mockito.mock(Folder.class);
-		File mockSubFile = Mockito.mock(File.class);
-		Mockito.when(mockFolder.folder("mockSubFolder")).thenReturn(mockSubFolder);
-		Mockito.when(mockFolder.file("mockSubFile")).thenReturn(mockSubFile);
-
-		DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
-		Assert.assertSame(delegatingFolder.folder("mockSubFolder"), delegatingFolder.folder("mockSubFolder"));
-		Assert.assertSame(delegatingFolder.file("mockSubFile"), delegatingFolder.file("mockSubFile"));
-	}
-}

+ 0 - 64
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java

@@ -1,64 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.nio.ByteBuffer;
-
-import org.cryptomator.filesystem.ReadableFile;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class DelegatingReadableFileTest {
-
-	@Test
-	public void testIsOpen() {
-		ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
-
-		Mockito.when(mockReadableFile.isOpen()).thenReturn(true);
-		Assert.assertTrue(delegatingReadableFile.isOpen());
-
-		Mockito.when(mockReadableFile.isOpen()).thenReturn(false);
-		Assert.assertFalse(delegatingReadableFile.isOpen());
-	}
-
-	@Test
-	public void testRead() {
-		ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
-
-		ByteBuffer buf = ByteBuffer.allocate(4);
-		Mockito.when(mockReadableFile.read(buf)).thenReturn(4);
-		Assert.assertEquals(4, delegatingReadableFile.read(buf));
-		Mockito.verify(mockReadableFile).read(buf);
-	}
-
-	@Test
-	public void testPosition() {
-		ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
-
-		delegatingReadableFile.position(42);
-		Mockito.verify(mockReadableFile).position(42);
-	}
-
-	@Test
-	public void testClose() {
-		ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
-		DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
-
-		delegatingReadableFile.close();
-		Mockito.verify(mockReadableFile).close();
-	}
-
-}

+ 0 - 74
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingWritableFileTest.java

@@ -1,74 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.nio.ByteBuffer;
-
-import org.cryptomator.filesystem.WritableFile;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class DelegatingWritableFileTest {
-
-	@Test
-	public void testIsOpen() {
-		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
-
-		Mockito.when(mockWritableFile.isOpen()).thenReturn(true);
-		Assert.assertTrue(delegatingWritableFile.isOpen());
-
-		Mockito.when(mockWritableFile.isOpen()).thenReturn(false);
-		Assert.assertFalse(delegatingWritableFile.isOpen());
-	}
-
-	@Test
-	public void testTruncate() {
-		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
-
-		delegatingWritableFile.truncate();
-		Mockito.verify(mockWritableFile).truncate();
-	}
-
-	@Test
-	public void testWrite() {
-		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
-
-		ByteBuffer buf = ByteBuffer.allocate(4);
-		Mockito.when(mockWritableFile.write(buf)).thenReturn(4);
-		Assert.assertEquals(4, delegatingWritableFile.write(buf));
-		Mockito.verify(mockWritableFile).write(buf);
-	}
-
-	@Test
-	public void testPosition() {
-		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
-		@SuppressWarnings("resource")
-		DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
-
-		delegatingWritableFile.position(42);
-		Mockito.verify(mockWritableFile).position(42);
-	}
-
-	@Test
-	public void testClose() {
-		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
-		DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
-
-		delegatingWritableFile.close();
-		Mockito.verify(mockWritableFile).close();
-	}
-
-}

+ 0 - 11
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java

@@ -1,11 +0,0 @@
-package org.cryptomator.filesystem.delegating;
-
-import org.cryptomator.filesystem.File;
-
-class TestDelegatingFile extends DelegatingFile<TestDelegatingFolder> {
-
-	public TestDelegatingFile(TestDelegatingFolder parent, File delegate) {
-		super(parent, delegate);
-	}
-
-}

+ 0 - 20
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java

@@ -1,20 +0,0 @@
-package org.cryptomator.filesystem.delegating;
-
-import org.cryptomator.filesystem.Folder;
-
-class TestDelegatingFileSystem extends TestDelegatingFolder implements DelegatingFileSystem {
-
-	private TestDelegatingFileSystem(Folder delegate) {
-		super(null, delegate);
-	}
-
-	public static TestDelegatingFileSystem withRoot(Folder delegate) {
-		return new TestDelegatingFileSystem(delegate);
-	}
-
-	@Override
-	public Folder getDelegate() {
-		return delegate;
-	}
-
-}

+ 0 - 22
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java

@@ -1,22 +0,0 @@
-package org.cryptomator.filesystem.delegating;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-
-class TestDelegatingFolder extends DelegatingFolder<TestDelegatingFolder, TestDelegatingFile> {
-
-	public TestDelegatingFolder(TestDelegatingFolder parent, Folder delegate) {
-		super(parent, delegate);
-	}
-
-	@Override
-	protected TestDelegatingFile newFile(File delegate) {
-		return new TestDelegatingFile(this, delegate);
-	}
-
-	@Override
-	protected TestDelegatingFolder newFolder(Folder delegate) {
-		return new TestDelegatingFolder(this, delegate);
-	}
-
-}

+ 0 - 81
main/filesystem-api/src/test/java/org/cryptomator/io/ByteBuffersTest.java

@@ -1,81 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.io;
-
-import java.nio.ByteBuffer;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ByteBuffersTest {
-
-	@Test
-	public void testCopyOfEmptySource() {
-		final ByteBuffer src = ByteBuffer.allocate(0);
-		final ByteBuffer dst = ByteBuffer.allocate(5);
-		dst.put(new byte[3]);
-		Assert.assertEquals(0, src.position());
-		Assert.assertEquals(0, src.remaining());
-		Assert.assertEquals(3, dst.position());
-		Assert.assertEquals(2, dst.remaining());
-		ByteBuffers.copy(src, dst);
-		Assert.assertEquals(0, src.position());
-		Assert.assertEquals(0, src.remaining());
-		Assert.assertEquals(3, dst.position());
-		Assert.assertEquals(2, dst.remaining());
-	}
-
-	@Test
-	public void testCopyToEmptyDestination() {
-		final ByteBuffer src = ByteBuffer.wrap(new byte[4]);
-		final ByteBuffer dst = ByteBuffer.allocate(0);
-		src.put(new byte[2]);
-		Assert.assertEquals(2, src.position());
-		Assert.assertEquals(2, src.remaining());
-		Assert.assertEquals(0, dst.position());
-		Assert.assertEquals(0, dst.remaining());
-		ByteBuffers.copy(src, dst);
-		Assert.assertEquals(2, src.position());
-		Assert.assertEquals(2, src.remaining());
-		Assert.assertEquals(0, dst.position());
-		Assert.assertEquals(0, dst.remaining());
-	}
-
-	@Test
-	public void testCopyToBiggerDestination() {
-		final ByteBuffer src = ByteBuffer.wrap(new byte[2]);
-		final ByteBuffer dst = ByteBuffer.allocate(10);
-		dst.put(new byte[3]);
-		Assert.assertEquals(0, src.position());
-		Assert.assertEquals(2, src.remaining());
-		Assert.assertEquals(3, dst.position());
-		Assert.assertEquals(7, dst.remaining());
-		ByteBuffers.copy(src, dst);
-		Assert.assertEquals(2, src.position());
-		Assert.assertEquals(0, src.remaining());
-		Assert.assertEquals(5, dst.position());
-		Assert.assertEquals(5, dst.remaining());
-	}
-
-	@Test
-	public void testCopyToSmallerDestination() {
-		final ByteBuffer src = ByteBuffer.wrap(new byte[5]);
-		final ByteBuffer dst = ByteBuffer.allocate(2);
-		Assert.assertEquals(0, src.position());
-		Assert.assertEquals(5, src.remaining());
-		Assert.assertEquals(0, dst.position());
-		Assert.assertEquals(2, dst.remaining());
-		ByteBuffers.copy(src, dst);
-		Assert.assertEquals(2, src.position());
-		Assert.assertEquals(3, src.remaining());
-		Assert.assertEquals(2, dst.position());
-		Assert.assertEquals(0, dst.remaining());
-	}
-
-}

+ 0 - 103
main/filesystem-api/src/test/java/org/cryptomator/io/FileContentsTest.java

@@ -1,103 +0,0 @@
-package org.cryptomator.io;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-@RunWith(Theories.class)
-public class FileContentsTest {
-
-	@DataPoints
-	public static final Iterable<Charset> CHARSETS = Arrays.asList(StandardCharsets.UTF_8, StandardCharsets.US_ASCII, StandardCharsets.UTF_16);
-
-	@DataPoints
-	public static final Iterable<String> TEST_CONTENTS = Arrays.asList("hello world", "hellö wörld", "");
-
-	@Theory
-	public void testReadAll(Charset charset, String testString) {
-		Assume.assumeTrue(charset.newEncoder().canEncode(testString));
-
-		ByteBuffer testContent = ByteBuffer.wrap(testString.getBytes(charset));
-		File file = Mockito.mock(File.class);
-		ReadableFile readable = Mockito.mock(ReadableFile.class);
-		Mockito.when(file.openReadable()).thenReturn(readable);
-		Mockito.when(readable.read(Mockito.any(ByteBuffer.class))).then(invocation -> {
-			ByteBuffer target = invocation.getArgumentAt(0, ByteBuffer.class);
-			if (testContent.hasRemaining()) {
-				return ByteBuffers.copy(testContent, target);
-			} else {
-				return -1;
-			}
-		});
-
-		String contentsRead = FileContents.withCharset(charset).readContents(file);
-		Assert.assertEquals(testString, contentsRead);
-	}
-
-	@Theory
-	public void testWriteAll(Charset charset, String testString) {
-		Assume.assumeTrue(charset.newEncoder().canEncode(testString));
-
-		ByteBuffer testContent = ByteBuffer.allocate(100);
-		File file = Mockito.mock(File.class);
-		WritableFile writable = Mockito.mock(WritableFile.class);
-		Mockito.when(file.openWritable()).thenReturn(writable);
-		Mockito.doAnswer(invocation -> {
-			testContent.clear();
-			return null;
-		}).when(writable).truncate();
-		Mockito.when(writable.write(Mockito.any(ByteBuffer.class))).then(invocation -> {
-			ByteBuffer source = invocation.getArgumentAt(0, ByteBuffer.class);
-			if (testContent.hasRemaining()) {
-				return ByteBuffers.copy(source, testContent);
-			} else {
-				return -1;
-			}
-		});
-
-		FileContents.withCharset(charset).writeContents(file, testString);
-		Assert.assertArrayEquals(testString.getBytes(charset), Arrays.copyOf(testContent.array(), testContent.position()));
-	}
-
-	@Test(expected = UncheckedIOException.class)
-	public void testIOExceptionDuringRead() {
-		File file = Mockito.mock(File.class);
-		Mockito.when(file.openReadable()).thenAnswer(invocation -> {
-			throw new IOException("failed");
-		});
-
-		FileContents.UTF_8.readContents(file);
-	}
-
-	@Test(expected = UncheckedIOException.class)
-	public void testUncheckedIOExceptionDuringRead() {
-		File file = Mockito.mock(File.class);
-		Mockito.when(file.openReadable()).thenThrow(new UncheckedIOException(new IOException("failed")));
-
-		FileContents.UTF_8.readContents(file);
-	}
-
-	@Test(expected = UncheckedIOException.class)
-	public void testUncheckedIOExceptionDuringWrite() {
-		File file = Mockito.mock(File.class);
-		Mockito.when(file.openWritable()).thenThrow(new UncheckedIOException(new IOException("failed")));
-
-		FileContents.UTF_8.writeContents(file, "hello world");
-	}
-
-}

+ 0 - 22
main/filesystem-api/src/test/resources/log4j2.xml

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<Configuration status="WARN">
-
-	<Appenders>
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
-		</Console>
-		<Console name="StdErr" target="SYSTEM_ERR">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
-		</Console>
-	</Appenders>
-
-	<Loggers>
-		<Root level="DEBUG">
-			<AppenderRef ref="Console" />
-			<AppenderRef ref="StdErr" />
-		</Root>
-	</Loggers>
-
-</Configuration>

+ 0 - 45
main/filesystem-charsets/pom.xml

@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Copyright (c) 2015 Sebastian Stenzel
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
-  
-  Contributors:
-      Sebastian Stenzel - initial API and implementation
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>1.3.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>filesystem-charsets</artifactId>
-	<name>Cryptomator filesystem: Charset compatibility layer</name>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-api</artifactId>
-		</dependency>
-
-		<!-- Tests -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-inmemory</artifactId>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 32
main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java

@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.charsets;
-
-import java.io.UncheckedIOException;
-import java.text.Normalizer;
-import java.text.Normalizer.Form;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.delegating.DelegatingFile;
-
-class NormalizedNameFile extends DelegatingFile<NormalizedNameFolder> {
-
-	private final Form displayForm;
-
-	public NormalizedNameFile(NormalizedNameFolder parent, File delegate, Form displayForm) {
-		super(parent, delegate);
-		this.displayForm = displayForm;
-	}
-
-	@Override
-	public String name() throws UncheckedIOException {
-		return Normalizer.normalize(super.name(), displayForm);
-	}
-
-}

+ 0 - 27
main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java

@@ -1,27 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.charsets;
-
-import java.text.Normalizer.Form;
-
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.delegating.DelegatingFileSystem;
-
-public class NormalizedNameFileSystem extends NormalizedNameFolder implements DelegatingFileSystem {
-
-	public NormalizedNameFileSystem(Folder delegate, Form displayForm) {
-		super(null, delegate, displayForm);
-	}
-
-	@Override
-	public Folder getDelegate() {
-		return delegate;
-	}
-
-}

+ 0 - 76
main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java

@@ -1,76 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.charsets;
-
-import java.io.UncheckedIOException;
-import java.text.Normalizer;
-import java.text.Normalizer.Form;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.delegating.DelegatingFolder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class NormalizedNameFolder extends DelegatingFolder<NormalizedNameFolder, NormalizedNameFile> {
-
-	private static final Logger LOG = LoggerFactory.getLogger(NormalizedNameFolder.class);
-	private final Form displayForm;
-
-	public NormalizedNameFolder(NormalizedNameFolder parent, Folder delegate, Form displayForm) {
-		super(parent, delegate);
-		this.displayForm = displayForm;
-	}
-
-	@Override
-	public String name() throws UncheckedIOException {
-		return Normalizer.normalize(super.name(), displayForm);
-	}
-
-	@Override
-	public NormalizedNameFile file(String name) throws UncheckedIOException {
-		String nfcName = Normalizer.normalize(name, Form.NFC);
-		String nfdName = Normalizer.normalize(name, Form.NFD);
-		NormalizedNameFile nfcFile = super.file(nfcName);
-		NormalizedNameFile nfdFile = super.file(nfdName);
-		if (!nfcName.equals(nfdName) && nfcFile.exists() && nfdFile.exists()) {
-			LOG.debug("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
-		} else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) {
-			LOG.debug("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
-			nfdFile.moveTo(nfcFile);
-		}
-		return nfcFile;
-	}
-
-	@Override
-	protected NormalizedNameFile newFile(File delegate) {
-		return new NormalizedNameFile(this, delegate, displayForm);
-	}
-
-	@Override
-	public NormalizedNameFolder folder(String name) throws UncheckedIOException {
-		String nfcName = Normalizer.normalize(name, Form.NFC);
-		String nfdName = Normalizer.normalize(name, Form.NFD);
-		NormalizedNameFolder nfcFolder = super.folder(nfcName);
-		NormalizedNameFolder nfdFolder = super.folder(nfdName);
-		if (!nfcName.equals(nfdName) && nfcFolder.exists() && nfdFolder.exists()) {
-			LOG.debug("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
-		} else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) {
-			LOG.debug("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
-			nfdFolder.moveTo(nfcFolder);
-		}
-		return nfcFolder;
-	}
-
-	@Override
-	protected NormalizedNameFolder newFolder(Folder delegate) {
-		return new NormalizedNameFolder(this, delegate, displayForm);
-	}
-
-}

+ 0 - 16
main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java

@@ -1,16 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-/**
- * Makes sure, the filesystems wrapped by this filesystem work only on UTF-8 encoded file paths using Normalization Form C.
- * Filesystems wrapping this file system, on the other hand, will get filenames reported in a specified Normalization Form.
- * It is recommended to use NFD for OS X and NFC for other operating systems.
- * When looking for a file or folder with a name given in either form, both possibilities are considered
- * and files/folders stored in NFD are automatically migrated to NFC.
- */
-package org.cryptomator.filesystem.charsets;

+ 0 - 90
main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java

@@ -1,90 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.charsets;
-
-import java.nio.ByteBuffer;
-import java.text.Normalizer.Form;
-
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.WritableFile;
-import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class NormalizedNameFileSystemTest {
-
-	@Test
-	public void testFileMigration() {
-		FileSystem inMemoryFs = new InMemoryFileSystem();
-		try (WritableFile writable = inMemoryFs.file("\u006F\u0302").openWritable()) {
-			writable.write(ByteBuffer.allocate(0));
-		}
-		FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
-		Assert.assertTrue(normalizationFs.file("\u00F4").exists());
-		Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
-		Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
-		Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
-	}
-
-	@Test
-	public void testNoFileMigration() {
-		FileSystem inMemoryFs = new InMemoryFileSystem();
-		try (WritableFile writable = inMemoryFs.file("\u00F4").openWritable()) {
-			writable.write(ByteBuffer.allocate(0));
-		}
-		FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
-		Assert.assertTrue(normalizationFs.file("\u00F4").exists());
-		Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
-		Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
-		Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
-	}
-
-	@Test
-	public void testFolderMigration() {
-		FileSystem inMemoryFs = new InMemoryFileSystem();
-		inMemoryFs.folder("\u006F\u0302").create();
-		FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
-		Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
-		Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
-		Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
-		Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
-	}
-
-	@Test
-	public void testNoFolderMigration() {
-		FileSystem inMemoryFs = new InMemoryFileSystem();
-		inMemoryFs.folder("\u00F4").create();
-		FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
-		Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
-		Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
-		Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
-		Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
-	}
-
-	@Test
-	public void testNfcDisplayNames() {
-		FileSystem inMemoryFs = new InMemoryFileSystem();
-		inMemoryFs.folder("a\u00F4").create();
-		inMemoryFs.folder("b\u006F\u0302").create();
-		FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
-		Assert.assertEquals("a\u00F4", normalizationFs.folder("a\u00F4").name());
-		Assert.assertEquals("b\u00F4", normalizationFs.folder("b\u006F\u0302").name());
-	}
-
-	@Test
-	public void testNfdDisplayNames() {
-		FileSystem inMemoryFs = new InMemoryFileSystem();
-		inMemoryFs.folder("a\u00F4").create();
-		inMemoryFs.folder("b\u006F\u0302").create();
-		FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFD);
-		Assert.assertEquals("a\u006F\u0302", normalizationFs.folder("a\u00F4").name());
-		Assert.assertEquals("b\u006F\u0302", normalizationFs.folder("b\u006F\u0302").name());
-	}
-
-}

+ 0 - 48
main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java

@@ -1,48 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.charsets;
-
-import java.text.Normalizer.Form;
-
-import org.cryptomator.filesystem.File;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class NormalizedNameFileTest {
-
-	private File delegateNfc;
-	private File delegateNfd;
-
-	@Before
-	public void setup() {
-		delegateNfc = Mockito.mock(File.class);
-		delegateNfd = Mockito.mock(File.class);
-		Mockito.when(delegateNfc.name()).thenReturn("\u00C5");
-		Mockito.when(delegateNfd.name()).thenReturn("\u0041\u030A");
-	}
-
-	@Test
-	public void testDisplayNameInNfc() {
-		File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFC);
-		File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFC);
-		Assert.assertEquals("\u00C5", file1.name());
-		Assert.assertEquals("\u00C5", file2.name());
-	}
-
-	@Test
-	public void testDisplayNameInNfd() {
-		File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFD);
-		File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFD);
-		Assert.assertEquals("\u0041\u030A", file1.name());
-		Assert.assertEquals("\u0041\u030A", file2.name());
-	}
-
-}

+ 0 - 149
main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java

@@ -1,149 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.charsets;
-
-import java.text.Normalizer.Form;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class NormalizedNameFolderTest {
-
-	private Folder delegate;
-	private File delegateSubFileNfc;
-	private File delegateSubFileNfd;
-	private Folder delegateSubFolderNfc;
-	private Folder delegateSubFolderNfd;
-
-	@Before
-	public void setup() {
-		delegate = Mockito.mock(Folder.class);
-		delegateSubFileNfc = Mockito.mock(File.class);
-		delegateSubFileNfd = Mockito.mock(File.class);
-		Mockito.when(delegate.file("\u00C5")).thenReturn(delegateSubFileNfc);
-		Mockito.when(delegateSubFileNfc.name()).thenReturn("\u00C5");
-		Mockito.when(delegate.file("\u0041\u030A")).thenReturn(delegateSubFileNfd);
-		Mockito.when(delegateSubFileNfd.name()).thenReturn("\u0041\u030A");
-		delegateSubFolderNfc = Mockito.mock(Folder.class);
-		delegateSubFolderNfd = Mockito.mock(Folder.class);
-		Mockito.when(delegate.folder("\u00F4")).thenReturn(delegateSubFolderNfc);
-		Mockito.when(delegateSubFolderNfc.name()).thenReturn("\u00F4");
-		Mockito.when(delegate.folder("\u006F\u0302")).thenReturn(delegateSubFolderNfd);
-		Mockito.when(delegateSubFolderNfd.name()).thenReturn("\u006F\u0302");
-	}
-
-	@Test
-	public void testDisplayNameInNfc() {
-		Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFC);
-		Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFC);
-		Assert.assertEquals("\u00F4", folder1.name());
-		Assert.assertEquals("\u00F4", folder2.name());
-	}
-
-	@Test
-	public void testDisplayNameInNfd() {
-		Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFD);
-		Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFD);
-		Assert.assertEquals("\u006F\u0302", folder1.name());
-		Assert.assertEquals("\u006F\u0302", folder2.name());
-	}
-
-	@Test
-	public void testNoFolderMigration1() {
-		Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true);
-		Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		Folder sub1 = folder.folder("\u00F4");
-		Folder sub2 = folder.folder("\u006F\u0302");
-		Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testNoFolderMigration2() {
-		Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true);
-		Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		Folder sub1 = folder.folder("\u00F4");
-		Folder sub2 = folder.folder("\u006F\u0302");
-		Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testNoFolderMigration3() {
-		Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false);
-		Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		Folder sub1 = folder.folder("\u00F4");
-		Folder sub2 = folder.folder("\u006F\u0302");
-		Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testFolderMigration() {
-		Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false);
-		Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		Folder sub1 = folder.folder("\u00F4");
-		Mockito.verify(delegateSubFolderNfd).moveTo(delegateSubFolderNfc);
-		Folder sub2 = folder.folder("\u006F\u0302");
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testNoFileMigration1() {
-		Mockito.when(delegateSubFileNfc.exists()).thenReturn(true);
-		Mockito.when(delegateSubFileNfd.exists()).thenReturn(false);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		File sub1 = folder.file("\u00C5");
-		File sub2 = folder.file("\u0041\u030A");
-		Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testNoFileMigration2() {
-		Mockito.when(delegateSubFileNfc.exists()).thenReturn(true);
-		Mockito.when(delegateSubFileNfd.exists()).thenReturn(true);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		File sub1 = folder.file("\u00C5");
-		File sub2 = folder.file("\u0041\u030A");
-		Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testNoFileMigration3() {
-		Mockito.when(delegateSubFileNfc.exists()).thenReturn(false);
-		Mockito.when(delegateSubFileNfd.exists()).thenReturn(false);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		File sub1 = folder.file("\u00C5");
-		File sub2 = folder.file("\u0041\u030A");
-		Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
-		Assert.assertSame(sub1, sub2);
-	}
-
-	@Test
-	public void testFileMigration() {
-		Mockito.when(delegateSubFileNfc.exists()).thenReturn(false);
-		Mockito.when(delegateSubFileNfd.exists()).thenReturn(true);
-		Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
-		File sub1 = folder.file("\u00C5");
-		Mockito.verify(delegateSubFileNfd).moveTo(delegateSubFileNfc);
-		File sub2 = folder.file("\u0041\u030A");
-		Assert.assertSame(sub1, sub2);
-	}
-
-}

+ 0 - 22
main/filesystem-charsets/src/test/resources/log4j2.xml

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<Configuration status="WARN">
-
-	<Appenders>
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
-		</Console>
-		<Console name="StdErr" target="SYSTEM_ERR">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
-		</Console>
-	</Appenders>
-
-	<Loggers>
-		<Root level="DEBUG">
-			<AppenderRef ref="Console" />
-			<AppenderRef ref="StdErr" />
-		</Root>
-	</Loggers>
-
-</Configuration>

+ 0 - 1
main/filesystem-crypto-integration-tests/.gitignore

@@ -1 +0,0 @@
-/target/

+ 0 - 64
main/filesystem-crypto-integration-tests/pom.xml

@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Copyright (c) 2016 Sebastian Stenzel
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
-  
-  Contributors:
-      Sebastian Stenzel - initial API and implementation
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>1.3.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>filesystem-crypto-integration-tests</artifactId>
-	<name>Cryptomator filesystem: Encryption layer tests</name>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-api</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-crypto</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-nameshortening</artifactId>
-		</dependency>
-
-		<!-- DI -->
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger-compiler</artifactId>
-			<scope>provided</scope>
-		</dependency>
-
-		<!-- Tests -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-inmemory</artifactId>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 33
main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoEngineTestModule.java

@@ -1,33 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
-
-/**
- * Used as drop-in-replacement for {@link CryptoEngineModule} during unit tests.
- */
-public class CryptoEngineTestModule extends CryptoEngineModule {
-
-	@Override
-	public SecureRandom provideSecureRandom() {
-		return new SecureRandom() {
-
-			@Override
-			public void nextBytes(byte[] bytes) {
-				Arrays.fill(bytes, (byte) 0x00);
-			}
-
-		};
-	}
-
-}

+ 0 - 32
main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTestComponent.java

@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import javax.inject.Singleton;
-
-import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
-import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory;
-
-import dagger.Component;
-
-/**
- * To be used in integration tests, where a {@link CryptoFileSystem} is needed in conjunction with {@link CryptoEngineTestModule} (which mocks the CSPRNG) as follows:
- * <code>
- * 	DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build()
- * </code>
- */
-@Singleton
-@Component(modules = CryptoEngineModule.class)
-public interface CryptoFileSystemTestComponent {
-
-	CryptoFileSystemFactory cryptoFileSystemFactory();
-
-	ShorteningFileSystemFactory shorteningFileSystemFactory();
-
-}

+ 0 - 298
main/filesystem-crypto-integration-tests/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemIntegrationTest.java

@@ -1,298 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.Future;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.Node;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
-import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class CryptoFileSystemIntegrationTest {
-
-	private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemIntegrationTest.class);
-
-	private final CryptoFileSystemTestComponent cryptoFsComp = DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build();
-
-	private CryptoFileSystemDelegate cryptoDelegate;
-	private FileSystem ciphertextFs;
-	private FileSystem cleartextFs;
-
-	@Before
-	public void setupFileSystems() {
-		cryptoDelegate = Mockito.mock(CryptoFileSystemDelegate.class);
-		ciphertextFs = new InMemoryFileSystem();
-		FileSystem shorteningFs = cryptoFsComp.shorteningFileSystemFactory().get(ciphertextFs);
-		cryptoFsComp.cryptoFileSystemFactory().initializeNew(shorteningFs, "TopSecret");
-		cleartextFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(shorteningFs, "TopSecret", cryptoDelegate);
-	}
-
-	@Test(timeout = 1000)
-	public void testVaultStructureInitializationAndBackupBehaviour() throws UncheckedIOException, IOException {
-		final FileSystem physicalFs = new InMemoryFileSystem();
-		final File masterkeyFile = physicalFs.file("masterkey.cryptomator");
-		final File masterkeyBkupFile = physicalFs.file("masterkey.cryptomator.bkup");
-		final Folder physicalDataRoot = physicalFs.folder("d");
-		Assert.assertFalse(masterkeyFile.exists());
-		Assert.assertFalse(masterkeyBkupFile.exists());
-		Assert.assertFalse(physicalDataRoot.exists());
-
-		cryptoFsComp.cryptoFileSystemFactory().initializeNew(physicalFs, "asd");
-		Assert.assertTrue(masterkeyFile.exists());
-		Assert.assertFalse(masterkeyBkupFile.exists());
-		Assert.assertFalse(physicalDataRoot.exists());
-
-		@SuppressWarnings("unused")
-		final FileSystem cryptoFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(physicalFs, "asd", cryptoDelegate);
-		Assert.assertTrue(masterkeyBkupFile.exists());
-		Assert.assertTrue(physicalDataRoot.exists());
-		Assert.assertEquals(3, physicalFs.children().count()); // d + masterkey.cryptomator + masterkey.cryptomator.bkup
-		Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory
-	}
-
-	@Test
-	public void testEncryptionOfLongFolderNames() {
-		final String shortName = "normal folder name";
-		final String longName = "this will be a long filename after encryption, because its encrypted name is longer than onehundredandeighty characters";
-
-		final Folder shortFolder = cleartextFs.folder(shortName);
-		final Folder longFolder = cleartextFs.folder(longName);
-
-		shortFolder.create();
-		longFolder.create();
-
-		// because of the long file, a metadata folder should exist on the physical layer:
-		Assert.assertEquals(1, ciphertextFs.folder("m").folders().count());
-		Assert.assertTrue(ciphertextFs.folder("m").exists());
-
-		// but the shortened filenames must not be visible on the cleartext layer:
-		Assert.assertArrayEquals(new String[] {shortName, longName}, cleartextFs.folders().map(Node::name).sorted().toArray());
-	}
-
-	@Test
-	public void testEncryptionAndDecryptionOfFiles() {
-		// write test content to encrypted file
-		try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) {
-			writable.write(ByteBuffer.wrap("Hello ".getBytes()));
-			writable.write(ByteBuffer.wrap("World".getBytes()));
-		}
-
-		File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get();
-		Assert.assertTrue(physicalFile.exists());
-
-		// read test content from decrypted file
-		try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) {
-			ByteBuffer buf1 = ByteBuffer.allocate(5);
-			readable.read(buf1);
-			buf1.flip();
-			Assert.assertEquals("Hello", new String(buf1.array(), 0, buf1.remaining()));
-			ByteBuffer buf2 = ByteBuffer.allocate(10);
-			readable.read(buf2);
-			buf2.flip();
-			Assert.assertArrayEquals(" World".getBytes(), Arrays.copyOfRange(buf2.array(), 0, buf2.remaining()));
-		}
-	}
-
-	@Test
-	public void testForcedDecryptionOfManipulatedFile() {
-		// write test content to encrypted file
-		try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) {
-			writable.write(ByteBuffer.wrap("Hello World".getBytes()));
-		}
-
-		File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get();
-		Assert.assertTrue(physicalFile.exists());
-
-		// toggle last bit
-		try (WritableFile writable = physicalFile.openWritable(); ReadableFile readable = physicalFile.openReadable()) {
-			ByteBuffer buf = ByteBuffer.allocate((int) physicalFile.size());
-			readable.read(buf);
-			buf.array()[buf.limit() - 1] ^= 0x01;
-			buf.flip();
-			writable.write(buf);
-		}
-
-		// whitelist
-		Mockito.when(cryptoDelegate.shouldSkipAuthentication("/test1.txt")).thenReturn(true);
-
-		// read test content from decrypted file
-		try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) {
-			ByteBuffer buf = ByteBuffer.allocate(11);
-			readable.read(buf);
-			buf.flip();
-			Assert.assertArrayEquals("Hello World".getBytes(), buf.array());
-		}
-	}
-
-	@Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough
-	public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException {
-		File file = cleartextFs.file("benchmark.test");
-
-		final long encStart = System.nanoTime();
-		try (WritableFile writable = file.openWritable()) {
-			final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k
-			for (int i = 0; i < 1000; i++) { // 100M total
-				cleartext.rewind();
-				writable.write(cleartext);
-			}
-		}
-		final long encEnd = System.nanoTime();
-		LOG.debug("Encryption of 100M took {}ms", (encEnd - encStart) / 1000 / 1000);
-
-		final long decStart = System.nanoTime();
-		try (ReadableFile readable = file.openReadable()) {
-			final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k
-			for (int i = 0; i < 1000; i++) { // 100M total
-				cleartext.clear();
-				readable.read(cleartext);
-				cleartext.flip();
-				Assert.assertEquals(cleartext.get(), 0x00);
-			}
-		}
-		final long decEnd = System.nanoTime();
-		LOG.debug("Decryption of 100M took {}ms", (decEnd - decStart) / 1000 / 1000);
-
-		file.delete();
-	}
-
-	@Test
-	public void testRandomAccessOnLastBlock() {
-		// prepare test data:
-		ByteBuffer testData = ByteBuffer.allocate(16000 * Integer.BYTES); // < 64kb
-		for (int i = 0; i < 16000; i++) {
-			testData.putInt(i);
-		}
-
-		// write test data to file:
-		File cleartextFile = cleartextFs.file("test");
-		try (WritableFile writable = cleartextFile.openWritable()) {
-			testData.flip();
-			writable.write(testData);
-		}
-
-		// read last block:
-		try (ReadableFile readable = cleartextFile.openReadable()) {
-			ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
-			buf.clear();
-			readable.position(15999 * Integer.BYTES);
-			readable.read(buf);
-			buf.flip();
-			Assert.assertEquals(15999, buf.getInt());
-		}
-	}
-
-	@Test
-	public void testSequentialRandomAccess() {
-		// prepare test data:
-		ByteBuffer testData = ByteBuffer.allocate(1_000_000 * Integer.BYTES); // = 4MB
-		for (int i = 0; i < 1000000; i++) {
-			testData.putInt(i);
-		}
-
-		// write test data to file:
-		File cleartextFile = cleartextFs.file("test");
-		try (WritableFile writable = cleartextFile.openWritable()) {
-			testData.flip();
-			writable.write(testData);
-		}
-
-		// shuffle our test positions:
-		List<Integer> nums = new ArrayList<>();
-		for (int i = 0; i < 1_000_000; i++) {
-			nums.add(i);
-		}
-		Collections.shuffle(nums);
-
-		// read parts from positions:
-		try (ReadableFile readable = cleartextFile.openReadable()) {
-			ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
-			for (int i = 0; i < 1000; i++) {
-				int num = nums.get(i);
-				buf.clear();
-				readable.position(num * Integer.BYTES);
-				readable.read(buf);
-				buf.flip();
-				Assert.assertEquals(num, buf.getInt());
-			}
-		}
-	}
-
-	@Test
-	public void testParallelRandomAccess() {
-		// prepare test data:
-		ByteBuffer testData = ByteBuffer.allocate(1_000_000 * Integer.BYTES); // = 4MB
-		for (int i = 0; i < 1000000; i++) {
-			testData.putInt(i);
-		}
-
-		// write test data to file:
-		final File cleartextFile = cleartextFs.file("test");
-		try (WritableFile writable = cleartextFile.openWritable()) {
-			testData.flip();
-			writable.write(testData);
-		}
-
-		// shuffle our test positions:
-		List<Integer> nums = new ArrayList<>();
-		for (int i = 0; i < 1_000_000; i++) {
-			nums.add(i);
-		}
-		Collections.shuffle(nums);
-
-		// read parts from positions in parallel:
-		final ForkJoinPool pool = new ForkJoinPool(10);
-		final List<Future<Boolean>> tasks = new ArrayList<>();
-		for (int i = 0; i < 1000; i++) {
-			final int num = nums.get(i);
-			final ForkJoinTask<Boolean> task = ForkJoinTask.adapt(() -> {
-				try (ReadableFile readable = cleartextFile.openReadable()) {
-					ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
-					buf.clear();
-					readable.position(num * Integer.BYTES);
-					readable.read(buf);
-					buf.flip();
-					int numRead = buf.getInt();
-					return num == numRead;
-				}
-			});
-			pool.execute(task);
-			tasks.add(task);
-		}
-
-		// Wait for tasks to finish and check results
-		Assert.assertTrue(tasks.stream().allMatch(task -> {
-			try {
-				return task.get();
-			} catch (Exception e) {
-				e.printStackTrace();
-				return false;
-			}
-		}));
-	}
-
-}

+ 0 - 22
main/filesystem-crypto-integration-tests/src/test/resources/log4j2.xml

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<Configuration status="WARN">
-
-	<Appenders>
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
-		</Console>
-		<Console name="StdErr" target="SYSTEM_ERR">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
-		</Console>
-	</Appenders>
-
-	<Loggers>
-		<Root level="DEBUG">
-			<AppenderRef ref="Console" />
-			<AppenderRef ref="StdErr" />
-		</Root>
-	</Loggers>
-
-</Configuration>

+ 0 - 1
main/filesystem-crypto/.gitignore

@@ -1 +0,0 @@
-/target/

+ 0 - 93
main/filesystem-crypto/pom.xml

@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Copyright (c) 2015 Sebastian Stenzel
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
-  
-  Contributors:
-      Sebastian Stenzel - initial API and implementation
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>1.3.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>filesystem-crypto</artifactId>
-	<name>Cryptomator filesystem: Encryption layer</name>
-	
-	<properties>
-		<bouncycastle.version>1.51</bouncycastle.version>
-		<sivmode.version>1.2.0</sivmode.version>
-	</properties>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-api</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons</artifactId>
-		</dependency>
-		
-		<!-- Crypto -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>siv-mode</artifactId>
-			<version>${sivmode.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.bouncycastle</groupId>
-			<artifactId>bcprov-jdk15on</artifactId>
-			<version>${bouncycastle.version}</version>
-		</dependency>
-
-		<!-- Commons -->
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-lang3</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>commons-codec</groupId>
-			<artifactId>commons-codec</artifactId>
-		</dependency>
-		
-		<!-- JSON -->
-		<dependency>
-			<groupId>com.fasterxml.jackson.core</groupId>
-			<artifactId>jackson-databind</artifactId>
-		</dependency>
-		
-		<!-- DI -->
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger-compiler</artifactId>
-			<scope>provided</scope>
-		</dependency>
-		
-		<!-- Tests -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>filesystem-inmemory</artifactId>
-		</dependency>
-	</dependencies>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 29
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/AuthenticationFailedException.java

@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-public class AuthenticationFailedException extends CryptoException {
-
-	public AuthenticationFailedException() {
-		super();
-	}
-
-	public AuthenticationFailedException(String message) {
-		super(message);
-	}
-
-	public AuthenticationFailedException(Throwable cause) {
-		super(cause);
-	}
-
-	public AuthenticationFailedException(String message, Throwable cause) {
-		super(message, cause);
-	}
-
-}

+ 0 - 29
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/CryptoException.java

@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-public abstract class CryptoException extends RuntimeException {
-
-	public CryptoException() {
-		super();
-	}
-
-	public CryptoException(String message) {
-		super(message);
-	}
-
-	public CryptoException(Throwable cause) {
-		super(cause);
-	}
-
-	public CryptoException(String message, Throwable cause) {
-		super(message, cause);
-	}
-
-}

+ 0 - 31
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java

@@ -1,31 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-import javax.security.auth.Destroyable;
-
-/**
- * A Cryptor instance, once initialized with a set of keys, provides access to threadsafe cryptographic routines.
- */
-public interface Cryptor extends Destroyable {
-
-	FilenameCryptor getFilenameCryptor();
-
-	FileContentCryptor getFileContentCryptor();
-
-	void randomizeMasterkey();
-
-	void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException;
-
-	byte[] writeKeysToMasterkeyFile(CharSequence passphrase);
-
-	@Override
-	void destroy();
-
-}

+ 0 - 49
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java

@@ -1,49 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-import java.nio.ByteBuffer;
-import java.util.Optional;
-
-/**
- * Factory for stateful {@link FileContentEncryptor Encryptor}/{@link FileContentDecryptor Decryptor} instances, that are capable of processing data exactly once.
- */
-public interface FileContentCryptor {
-
-	public static final ByteBuffer EOF = ByteBuffer.allocate(0);
-
-	/**
-	 * @return The fixed number of bytes of the file header. The header length is implementation-specific.
-	 */
-	int getHeaderSize();
-
-	/**
-	 * @return The ciphertext position that correlates to the cleartext position.
-	 */
-	long toCiphertextPos(long cleartextPos);
-
-	/**
-	 * @param header The full fixed-length header of an encrypted file. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}.
-	 * @param firstCiphertextByte Position of the first ciphertext byte passed to the decryptor. If the decryptor can not fast-forward to the requested byte, an exception is thrown.
-	 *            If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the decryptors internal block size, an IllegalArgumentException will be thrown.
-	 * @param authenticate Skip authentication by setting this flag to <code>false</code>. Should be <code>true</code> by default.
-	 * @return A possibly new FileContentDecryptor instance which is capable of decrypting ciphertexts associated with the given file header.
-	 */
-	FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) throws IllegalArgumentException, AuthenticationFailedException;
-
-	/**
-	 * @param header The full fixed-length header of an encrypted file or {@link Optional#empty()}. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}.
-	 *            If the header is empty, a new one will be created by the returned encryptor.
-	 * @param firstCleartextByte Position of the first cleartext byte passed to the encryptor. If the encryptor can not fast-forward to the requested byte, an exception is thrown.
-	 *            If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the encryptors internal block size, an IllegalArgumentException will be thrown.
-	 * @return A possibly new FileContentEncryptor instance which is capable of encrypting cleartext associated with the given file header.
-	 */
-	FileContentEncryptor createFileContentEncryptor(Optional<ByteBuffer> header, long firstCleartextByte) throws IllegalArgumentException;
-
-}

+ 0 - 61
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java

@@ -1,61 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-import java.io.Closeable;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-
-import javax.security.auth.Destroyable;
-
-/**
- * Stateful, thus not thread-safe.
- */
-public interface FileContentDecryptor extends Destroyable, Closeable {
-
-	/**
-	 * Appends further ciphertext to this decryptor. This method might block until space becomes available. If so, it is interruptable.
-	 * 
-	 * @param cleartext Cleartext data or {@link FileContentCryptor#EOF} to indicate the end of a ciphertext.
-	 * @see #skipToPosition(long)
-	 */
-	void append(ByteBuffer ciphertext) throws InterruptedException;
-
-	/**
-	 * Cancels decryption due to an exception in the thread responsible for appending ciphertext.
-	 * The exception will be the root cause of an {@link UncheckedIOException} thrown by {@link #cleartext()} when retrieving the decrypted result.
-	 * 
-	 * @param cause The exception making it impossible to {@link #append(ByteBuffer)} further ciphertext.
-	 */
-	void cancelWithException(Exception cause) throws InterruptedException;
-
-	/**
-	 * Returns the next decrypted cleartext in byte-by-byte FIFO order, meaning in the order ciphertext has been appended to this encryptor.
-	 * However the number and size of the cleartext byte buffers doesn't need to resemble the ciphertext buffers.
-	 * 
-	 * This method might block if no cleartext is available yet.
-	 * 
-	 * @return Decrypted cleartext or {@link FileContentCryptor#EOF}.
-	 * @throws AuthenticationFailedException On MAC mismatches
-	 * @throws UncheckedIOException In case of I/O exceptions, e.g. caused by previous {@link #cancelWithException(Exception)}.
-	 */
-	ByteBuffer cleartext() throws InterruptedException, AuthenticationFailedException, UncheckedIOException;
-
-	/**
-	 * Clears file-specific sensitive information.
-	 */
-	@Override
-	void destroy();
-
-	@Override
-	default void close() {
-		this.destroy();
-	}
-
-}

+ 0 - 72
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java

@@ -1,72 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-import java.io.Closeable;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-
-import javax.security.auth.Destroyable;
-
-/**
- * Stateful, thus not thread-safe.
- */
-public interface FileContentEncryptor extends Destroyable, Closeable {
-
-	/**
-	 * Creates the encrypted file header. This header might depend on the already encrypted data,
-	 * thus the caller should make sure all data is processed before requesting the header.
-	 * 
-	 * @return Encrypted file header.
-	 */
-	ByteBuffer getHeader();
-
-	/**
-	 * @return the size of headers created by this {@code FileContentCryptor}. The length of headers returned by {@link #getHeader()} equals this value.
-	 */
-	int getHeaderSize();
-
-	/**
-	 * Appends further cleartext to this encryptor. This method might block until space becomes available.
-	 * 
-	 * @param cleartext Cleartext data or {@link FileContentCryptor#EOF} to indicate the end of a cleartext.
-	 */
-	void append(ByteBuffer cleartext) throws InterruptedException;
-
-	/**
-	 * Cancels encryption due to an exception in the thread responsible for appending cleartext.
-	 * The exception will be the root cause of an {@link UncheckedIOException} thrown by {@link #ciphertext()} when retrieving the encrypted result.
-	 * 
-	 * @param cause The exception making it impossible to {@link #append(ByteBuffer)} further cleartext.
-	 */
-	void cancelWithException(Exception cause) throws InterruptedException;
-
-	/**
-	 * Returns the next ciphertext in byte-by-byte FIFO order, meaning in the order cleartext has been appended to this encryptor.
-	 * However the number and size of the ciphertext byte buffers doesn't need to resemble the cleartext buffers.
-	 * 
-	 * This method might block if no ciphertext is available yet.
-	 * 
-	 * @return Encrypted ciphertext of {@link FileContentCryptor#EOF}.
-	 * @throws UncheckedIOException In case of I/O exceptions, e.g. caused by previous {@link #cancelWithException(Exception)}.
-	 */
-	ByteBuffer ciphertext() throws InterruptedException, UncheckedIOException;
-
-	/**
-	 * Clears file-specific sensitive information.
-	 */
-	@Override
-	void destroy();
-
-	@Override
-	default void close() {
-		this.destroy();
-	}
-
-}

+ 0 - 44
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java

@@ -1,44 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-import java.util.regex.Pattern;
-
-/**
- * Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts,
- * otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption.
- * 
- * @see <a href="https://en.wikipedia.org/wiki/Deterministic_encryption">Wikipedia on deterministic encryption</a>
- */
-public interface FilenameCryptor {
-
-	/**
-	 * @return constant length string, that is unlikely to collide with any other name.
-	 */
-	String hashDirectoryId(String cleartextDirectoryId);
-
-	/**
-	 * @return A Pattern that can be used to test, if a name is a well-formed ciphertext.
-	 */
-	Pattern encryptedNamePattern();
-
-	/**
-	 * @param cleartextName original filename including cleartext file extension
-	 * @param associatedData optional associated data, that will not get encrypted but needs to be provided during decryption
-	 * @return encrypted filename without any file extension
-	 */
-	String encryptFilename(String cleartextName, byte[]... associatedData);
-
-	/**
-	 * @param ciphertextName Ciphertext only, with any additional strings like file extensions stripped first.
-	 * @param associatedData the same associated data used during encryption, otherwise and {@link AuthenticationFailedException} will be thrown
-	 * @return cleartext filename, probably including its cleartext file extension.
-	 */
-	String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException;
-}

+ 0 - 17
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/InvalidPassphraseException.java

@@ -1,17 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-public class InvalidPassphraseException extends CryptoException {
-
-	public InvalidPassphraseException() {
-		super();
-	}
-
-}

+ 0 - 38
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java

@@ -1,38 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine;
-
-public class UnsupportedVaultFormatException extends CryptoException {
-
-	private final Integer detectedVersion;
-	private final Integer latestSupportedVersion;
-
-	public UnsupportedVaultFormatException(Integer detectedVersion, Integer latestSupportedVersion) {
-		super("Tried to open vault of version " + detectedVersion + ", latest supported version is " + latestSupportedVersion);
-		this.detectedVersion = detectedVersion;
-		this.latestSupportedVersion = latestSupportedVersion;
-	}
-
-	public Integer getDetectedVersion() {
-		return detectedVersion;
-	}
-
-	public Integer getLatestSupportedVersion() {
-		return latestSupportedVersion;
-	}
-
-	public boolean isVaultOlderThanSoftware() {
-		return detectedVersion == null || detectedVersion < latestSupportedVersion;
-	}
-
-	public boolean isSoftwareOlderThanVault() {
-		return detectedVersion > latestSupportedVersion;
-	}
-
-}

+ 0 - 71
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/AesKeyWrap.java

@@ -1,71 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-
-final class AesKeyWrap {
-
-	private static final String RFC3394_CIPHER = "AESWrap";
-
-	private AesKeyWrap() {
-	}
-
-	/**
-	 * @param kek Key encrypting key
-	 * @param key Key to be wrapped
-	 * @return Wrapped key
-	 */
-	public static byte[] wrap(SecretKey kek, SecretKey key) {
-		final Cipher cipher;
-		try {
-			cipher = Cipher.getInstance(RFC3394_CIPHER);
-			cipher.init(Cipher.WRAP_MODE, kek);
-		} catch (InvalidKeyException e) {
-			throw new IllegalArgumentException("Invalid key.", e);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-			throw new IllegalStateException("Algorithm/Padding should exist.", e);
-		}
-
-		try {
-			return cipher.wrap(key);
-		} catch (InvalidKeyException | IllegalBlockSizeException e) {
-			throw new IllegalStateException("Unable to wrap key.", e);
-		}
-	}
-
-	/**
-	 * @param kek Key encrypting key
-	 * @param wrappedKey Key to be unwrapped
-	 * @param keyAlgorithm Key designation, i.e. algorithm name to be associated with the unwrapped key.
-	 * @return Unwrapped key
-	 * @throws NoSuchAlgorithmException If keyAlgorithm is unknown
-	 * @throws InvalidKeyException If unwrapping failed (i.e. wrong kek)
-	 */
-	public static SecretKey unwrap(SecretKey kek, byte[] wrappedKey, String keyAlgorithm) throws InvalidKeyException, NoSuchAlgorithmException {
-		final Cipher cipher;
-		try {
-			cipher = Cipher.getInstance(RFC3394_CIPHER);
-			cipher.init(Cipher.UNWRAP_MODE, kek);
-		} catch (InvalidKeyException ex) {
-			throw new IllegalArgumentException("Invalid key.", ex);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
-			throw new IllegalStateException("Algorithm/Padding should exist.", ex);
-		}
-
-		return (SecretKey) cipher.unwrap(wrappedKey, keyAlgorithm, Cipher.SECRET_KEY);
-	}
-
-}

+ 0 - 15
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java

@@ -1,15 +0,0 @@
-package org.cryptomator.crypto.engine.impl;
-
-public final class Constants {
-
-	private Constants() {
-	}
-
-	static final Integer CURRENT_VAULT_VERSION = 5;
-
-	public static final int PAYLOAD_SIZE = 32 * 1024;
-	public static final int NONCE_SIZE = 16;
-	public static final int MAC_SIZE = 32;
-	public static final int CHUNK_SIZE = NONCE_SIZE + PAYLOAD_SIZE + MAC_SIZE;
-
-}

+ 0 - 41
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptoEngineModule.java

@@ -1,41 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-import org.cryptomator.crypto.engine.Cryptor;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public class CryptoEngineModule {
-
-	@Provides
-	public Cryptor provideCryptor(SecureRandom secureRandom) {
-		return new CryptorImpl(secureRandom);
-	}
-
-	@Provides
-	public SecureRandom provideSecureRandom() {
-		try {
-			// https://tersesystems.com/2015/12/17/the-right-way-to-use-securerandom/
-			final SecureRandom nativeRandom = SecureRandom.getInstanceStrong();
-			byte[] seed = nativeRandom.generateSeed(55); // NIST SP800-90A suggests 440 bits for SHA1 seed
-			SecureRandom sha1Random = SecureRandom.getInstance("SHA1PRNG");
-			sha1Random.setSeed(seed);
-			return sha1Random;
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("No strong PRNGs available.", e);
-		}
-	}
-
-}

+ 0 - 197
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java

@@ -1,197 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.DestroyFailedException;
-import javax.security.auth.Destroyable;
-
-import org.cryptomator.common.LazyInitializer;
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FilenameCryptor;
-import org.cryptomator.crypto.engine.InvalidPassphraseException;
-import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.exc.InvalidFormatException;
-
-class CryptorImpl implements Cryptor {
-
-	private static final int SCRYPT_SALT_LENGTH = 8;
-	private static final int SCRYPT_COST_PARAM = 1 << 14;
-	private static final int SCRYPT_BLOCK_SIZE = 8;
-	private static final int KEYLENGTH_IN_BYTES = 32;
-	private static final String ENCRYPTION_ALG = "AES";
-	private static final String MAC_ALG = "HmacSHA256";
-
-	private SecretKey encryptionKey;
-	private SecretKey macKey;
-	private final AtomicReference<FilenameCryptor> filenameCryptor = new AtomicReference<>();
-	private final AtomicReference<FileContentCryptor> fileContentCryptor = new AtomicReference<>();
-	private final SecureRandom randomSource;
-
-	public CryptorImpl(SecureRandom randomSource) {
-		this.randomSource = randomSource;
-	}
-
-	@Override
-	public FilenameCryptor getFilenameCryptor() {
-		assertKeysExist();
-		return LazyInitializer.initializeLazily(filenameCryptor, () -> {
-			return new FilenameCryptorImpl(encryptionKey, macKey);
-		});
-	}
-
-	@Override
-	public FileContentCryptor getFileContentCryptor() {
-		assertKeysExist();
-		return LazyInitializer.initializeLazily(fileContentCryptor, () -> {
-			return new FileContentCryptorImpl(encryptionKey, macKey, randomSource);
-		});
-	}
-
-	private void assertKeysExist() {
-		if (encryptionKey == null || encryptionKey.isDestroyed()) {
-			throw new IllegalStateException("No or invalid encryptionKey.");
-		}
-		if (macKey == null || macKey.isDestroyed()) {
-			throw new IllegalStateException("No or invalid MAC key.");
-		}
-	}
-
-	@Override
-	public void randomizeMasterkey() {
-		try {
-			KeyGenerator encKeyGen = KeyGenerator.getInstance(ENCRYPTION_ALG);
-			encKeyGen.init(KEYLENGTH_IN_BYTES * Byte.SIZE, randomSource);
-			encryptionKey = encKeyGen.generateKey();
-			KeyGenerator macKeyGen = KeyGenerator.getInstance(MAC_ALG);
-			macKeyGen.init(KEYLENGTH_IN_BYTES * Byte.SIZE, randomSource);
-			macKey = macKeyGen.generateKey();
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e);
-		}
-	}
-
-	@Override
-	public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) {
-		final KeyFile keyFile;
-		try {
-			final ObjectMapper om = new ObjectMapper();
-			keyFile = om.readValue(masterkeyFileContents, KeyFile.class);
-			if (keyFile == null) {
-				throw new InvalidFormatException("Could not read masterkey file", null, KeyFile.class);
-			}
-		} catch (IOException e) {
-			throw new IllegalArgumentException("Unable to parse masterkeyFileContents", e);
-		}
-		assert keyFile != null;
-
-		// check version
-		if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) {
-			throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
-		}
-
-		final byte[] kekBytes = Scrypt.scrypt(passphrase, keyFile.getScryptSalt(), keyFile.getScryptCostParam(), keyFile.getScryptBlockSize(), KEYLENGTH_IN_BYTES);
-		try {
-			final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
-			this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG);
-			// future use (as soon as we need to prevent downgrade attacks):
-			// final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
-			// final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
-			// if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
-			// destroyQuietly(macKey);
-			// throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
-			// }
-			this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG);
-		} catch (InvalidKeyException e) {
-			throw new InvalidPassphraseException();
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e);
-		} finally {
-			Arrays.fill(kekBytes, (byte) 0x00);
-		}
-	}
-
-	@Override
-	public byte[] writeKeysToMasterkeyFile(CharSequence passphrase) {
-		final byte[] scryptSalt = new byte[SCRYPT_SALT_LENGTH];
-		randomSource.nextBytes(scryptSalt);
-
-		final byte[] kekBytes = Scrypt.scrypt(passphrase, scryptSalt, SCRYPT_COST_PARAM, SCRYPT_BLOCK_SIZE, KEYLENGTH_IN_BYTES);
-		final byte[] wrappedEncryptionKey;
-		final byte[] wrappedMacKey;
-		try {
-			final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
-			wrappedEncryptionKey = AesKeyWrap.wrap(kek, encryptionKey);
-			wrappedMacKey = AesKeyWrap.wrap(kek, macKey);
-		} finally {
-			Arrays.fill(kekBytes, (byte) 0x00);
-		}
-
-		final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
-		final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
-
-		final KeyFile keyfile = new KeyFile();
-		keyfile.setVersion(CURRENT_VAULT_VERSION);
-		keyfile.setScryptSalt(scryptSalt);
-		keyfile.setScryptCostParam(SCRYPT_COST_PARAM);
-		keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE);
-		keyfile.setEncryptionMasterKey(wrappedEncryptionKey);
-		keyfile.setMacMasterKey(wrappedMacKey);
-		keyfile.setVersionMac(versionMac);
-
-		try {
-			final ObjectMapper om = new ObjectMapper();
-			return om.writeValueAsBytes(keyfile);
-		} catch (JsonProcessingException e) {
-			throw new IllegalArgumentException("Unable to create JSON from " + keyfile, e);
-		}
-	}
-
-	/* ======================= destruction ======================= */
-
-	@Override
-	public void destroy() {
-		destroyQuietly(encryptionKey);
-		destroyQuietly(macKey);
-	}
-
-	@Override
-	public boolean isDestroyed() {
-		return (encryptionKey == null || encryptionKey.isDestroyed()) && (macKey == null || macKey.isDestroyed());
-	}
-
-	private void destroyQuietly(Destroyable d) {
-		if (d == null) {
-			return;
-		}
-		try {
-			d.destroy();
-		} catch (DestroyFailedException e) {
-			// ignore
-		}
-	}
-
-}

+ 0 - 70
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessor.java

@@ -1,70 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.ThreadPoolExecutor;
-
-/**
- * Executes long-running computations and returns the result strictly in order of the job submissions, no matter how long each job takes.
- * 
- * The internally used thread pool is shut down automatically as soon as this FifiParallelDataProcessor is no longer referenced (see Finalization behaviour of {@link ThreadPoolExecutor}).
- */
-class FifoParallelDataProcessor<T> {
-
-	private final BlockingQueue<Future<T>> processedData;
-	private final ExecutorService executorService;
-
-	/**
-	 * @param numThreads How many jobs can run in parallel.
-	 * @param workAhead Maximum number of jobs accepted in {@link #submit(Callable)} without blocking until results are polled from {@link #processedData()}.
-	 */
-	public FifoParallelDataProcessor(int workAhead, ExecutorService executorService) {
-		this.processedData = new ArrayBlockingQueue<>(workAhead);
-		this.executorService = executorService;
-	}
-
-	/**
-	 * Enqueues a job for execution. The results of multiple submissions can be polled in FIFO order using {@link #processedData()}.
-	 * 
-	 * @param processingJob A task, that will compute a result.
-	 * @throws InterruptedException
-	 */
-	void submit(Callable<T> processingJob) throws InterruptedException {
-		Future<T> future = executorService.submit(processingJob);
-		processedData.put(future);
-	}
-
-	/**
-	 * Submits already pre-processed data, that can be polled in FIFO order from {@link #processedData()}.
-	 * 
-	 * @throws InterruptedException
-	 */
-	void submitPreprocessed(T preprocessedData) throws InterruptedException {
-		this.submit(() -> {
-			return preprocessedData;
-		});
-	}
-
-	/**
-	 * Result of previously {@link #submit(Callable) submitted} jobs in the same order as they have been submitted. Blocks if the job didn't finish yet.
-	 * 
-	 * @return Next job result
-	 * @throws InterruptedException If the calling thread was interrupted while waiting for the next result.
-	 */
-	T processedData() throws InterruptedException, ExecutionException {
-		return processedData.take().get();
-	}
-
-}

+ 0 - 75
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java

@@ -1,75 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE;
-import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE;
-
-import java.nio.ByteBuffer;
-import java.security.SecureRandom;
-import java.util.Optional;
-
-import javax.crypto.SecretKey;
-
-import org.cryptomator.crypto.engine.AuthenticationFailedException;
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentDecryptor;
-import org.cryptomator.crypto.engine.FileContentEncryptor;
-
-class FileContentCryptorImpl implements FileContentCryptor {
-
-	private final SecretKey encryptionKey;
-	private final SecretKey macKey;
-	private final SecureRandom randomSource;
-
-	FileContentCryptorImpl(SecretKey encryptionKey, SecretKey macKey, SecureRandom randomSource) {
-		this.encryptionKey = encryptionKey;
-		this.macKey = macKey;
-		this.randomSource = randomSource;
-	}
-
-	@Override
-	public int getHeaderSize() {
-		return FileHeader.HEADER_SIZE;
-	}
-
-	@Override
-	public long toCiphertextPos(long cleartextPos) {
-		long chunkNum = cleartextPos / PAYLOAD_SIZE;
-		long cleartextChunkStart = chunkNum * PAYLOAD_SIZE;
-		assert cleartextChunkStart <= cleartextPos;
-		long chunkInternalDiff = cleartextPos - cleartextChunkStart;
-		assert chunkInternalDiff >= 0 && chunkInternalDiff < PAYLOAD_SIZE;
-		long ciphertextChunkStart = chunkNum * CHUNK_SIZE;
-		return ciphertextChunkStart + chunkInternalDiff;
-	}
-
-	@Override
-	public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) throws IllegalArgumentException, AuthenticationFailedException {
-		if (header.remaining() != getHeaderSize()) {
-			throw new IllegalArgumentException("Invalid header.");
-		}
-		if (firstCiphertextByte % CHUNK_SIZE != 0) {
-			throw new IllegalArgumentException("Invalid starting point for decryption.");
-		}
-		return new FileContentDecryptorImpl(encryptionKey, macKey, header, firstCiphertextByte, authenticate);
-	}
-
-	@Override
-	public FileContentEncryptor createFileContentEncryptor(Optional<ByteBuffer> header, long firstCleartextByte) {
-		if (header.isPresent() && header.get().remaining() != getHeaderSize()) {
-			throw new IllegalArgumentException("Invalid header.");
-		}
-		if (firstCleartextByte % PAYLOAD_SIZE != 0) {
-			throw new IllegalArgumentException("Invalid starting point for encryption.");
-		}
-		return new FileContentEncryptorImpl(encryptionKey, macKey, randomSource, firstCleartextByte);
-	}
-
-}

+ 0 - 180
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java

@@ -1,180 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE;
-import static org.cryptomator.crypto.engine.impl.Constants.MAC_SIZE;
-import static org.cryptomator.crypto.engine.impl.Constants.NONCE_SIZE;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.function.Supplier;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-
-import org.cryptomator.crypto.engine.AuthenticationFailedException;
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentDecryptor;
-import org.cryptomator.io.ByteBuffers;
-
-class FileContentDecryptorImpl implements FileContentDecryptor {
-
-	private static final String HMAC_SHA256 = "HmacSHA256";
-	private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors();
-	private static final int READ_AHEAD = 2;
-	private static final ExecutorService SHARED_DECRYPTION_EXECUTOR = Executors.newFixedThreadPool(NUM_THREADS);
-
-	private final FifoParallelDataProcessor<ByteBuffer> dataProcessor = new FifoParallelDataProcessor<>(NUM_THREADS + READ_AHEAD, SHARED_DECRYPTION_EXECUTOR);
-	private final Supplier<Mac> hmacSha256;
-	private final FileHeader header;
-	private final boolean authenticate;
-	private ByteBuffer ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE);
-	private long chunkNumber = 0;
-
-	public FileContentDecryptorImpl(SecretKey headerKey, SecretKey macKey, ByteBuffer header, long firstCiphertextByte, boolean authenticate) {
-		this.hmacSha256 = new ThreadLocalMac(macKey, HMAC_SHA256);
-		this.header = FileHeader.decrypt(headerKey, hmacSha256, header);
-		this.authenticate = authenticate;
-		this.chunkNumber = firstCiphertextByte / CHUNK_SIZE; // floor() by int-truncation
-		
-		// vault version 5 and onwards should have filesize: -1
-		if (this.header.getPayload().getFilesize() != -1l) {
-			throw new UncheckedIOException(new IOException("Attempted to decrypt file with invalid header (probably from previous vault version)"));
-		}
-	}
-
-	@Override
-	public void append(ByteBuffer ciphertext) throws InterruptedException {
-		if (ciphertext == FileContentCryptor.EOF) {
-			submitCiphertextBuffer();
-			submitEof();
-		} else {
-			while (ciphertext.hasRemaining()) {
-				ByteBuffers.copy(ciphertext, ciphertextBuffer);
-				submitCiphertextBufferIfFull();
-			}
-		}
-	}
-
-	@Override
-	public void cancelWithException(Exception cause) throws InterruptedException {
-		dataProcessor.submit(() -> {
-			throw cause;
-		});
-	}
-
-	private void submitCiphertextBufferIfFull() throws InterruptedException {
-		if (!ciphertextBuffer.hasRemaining()) {
-			submitCiphertextBuffer();
-			ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE);
-		}
-	}
-
-	private void submitCiphertextBuffer() throws InterruptedException {
-		ciphertextBuffer.flip();
-		if (ciphertextBuffer.hasRemaining()) {
-			Callable<ByteBuffer> encryptionJob = new DecryptionJob(ciphertextBuffer, chunkNumber++);
-			dataProcessor.submit(encryptionJob);
-		}
-	}
-
-	private void submitEof() throws InterruptedException {
-		dataProcessor.submitPreprocessed(FileContentCryptor.EOF);
-	}
-
-	@Override
-	public ByteBuffer cleartext() throws InterruptedException {
-		try {
-			return dataProcessor.processedData();
-		} catch (ExecutionException e) {
-			if (e.getCause() instanceof AuthenticationFailedException) {
-				throw new AuthenticationFailedException(e);
-			} else if (e.getCause() instanceof IOException || e.getCause() instanceof UncheckedIOException) {
-				throw new UncheckedIOException(new IOException("Decryption failed due to I/O exception during ciphertext supply.", e));
-			} else {
-				throw new RuntimeException(e);
-			}
-		}
-	}
-
-	@Override
-	public void destroy() {
-		header.destroy();
-	}
-
-	private class DecryptionJob implements Callable<ByteBuffer> {
-
-		private final byte[] nonce;
-		private final ByteBuffer inBuf;
-		private final ByteBuffer chunkNumberBigEndian = ByteBuffer.allocate(Long.BYTES);
-		private final byte[] expectedMac;
-
-		public DecryptionJob(ByteBuffer ciphertextChunk, long chunkNumber) {
-			if (ciphertextChunk.remaining() < NONCE_SIZE + MAC_SIZE) {
-				throw new IllegalArgumentException("Chunk must at least contain a NONCE and a MAC");
-			}
-			this.nonce = new byte[NONCE_SIZE];
-			ByteBuffer nonceBuf = ciphertextChunk.asReadOnlyBuffer();
-			nonceBuf.position(0).limit(NONCE_SIZE);
-			nonceBuf.get(nonce);
-			this.inBuf = ciphertextChunk.asReadOnlyBuffer();
-			this.inBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE);
-			chunkNumberBigEndian.putLong(chunkNumber);
-			chunkNumberBigEndian.rewind();
-			this.expectedMac = new byte[MAC_SIZE];
-			ByteBuffer macBuf = ciphertextChunk.asReadOnlyBuffer();
-			macBuf.position(macBuf.limit() - MAC_SIZE);
-			macBuf.get(expectedMac);
-		}
-
-		@Override
-		public ByteBuffer call() {
-			try {
-				if (authenticate) {
-					Mac mac = hmacSha256.get();
-					mac.update(header.getIv());
-					mac.update(chunkNumberBigEndian.asReadOnlyBuffer());
-					mac.update(nonce);
-					mac.update(inBuf.asReadOnlyBuffer());
-					if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) {
-						chunkNumberBigEndian.rewind();
-						throw new AuthenticationFailedException("Auth error in chunk " + chunkNumberBigEndian.getLong());
-					}
-				}
-
-				Cipher cipher = ThreadLocalAesCtrCipher.get();
-				cipher.init(Cipher.DECRYPT_MODE, header.getPayload().getContentKey(), new IvParameterSpec(nonce));
-				ByteBuffer outBuf = ByteBuffer.allocate(cipher.getOutputSize(inBuf.remaining()));
-				cipher.update(inBuf, outBuf);
-				outBuf.flip();
-				return outBuf;
-			} catch (InvalidKeyException e) {
-				throw new IllegalStateException("File content key created by current class invalid.", e);
-			} catch (ShortBufferException e) {
-				throw new IllegalStateException("Buffer allocated for reported output size apparently not big enought.", e);
-			} catch (InvalidAlgorithmParameterException e) {
-				throw new IllegalStateException("CTR mode known to accept an IV (aka. nonce).", e);
-			}
-		}
-
-	}
-
-}

+ 0 - 187
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java

@@ -1,187 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import static org.cryptomator.crypto.engine.impl.Constants.NONCE_SIZE;
-import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.SecureRandom;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.LongAdder;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentEncryptor;
-import org.cryptomator.io.ByteBuffers;
-
-class FileContentEncryptorImpl implements FileContentEncryptor {
-
-	private static final String HMAC_SHA256 = "HmacSHA256";
-	private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors();
-	private static final int READ_AHEAD = 2;
-	private static final ExecutorService SHARED_DECRYPTION_EXECUTOR = Executors.newFixedThreadPool(NUM_THREADS);
-
-	private final FifoParallelDataProcessor<ByteBuffer> dataProcessor = new FifoParallelDataProcessor<>(NUM_THREADS + READ_AHEAD, SHARED_DECRYPTION_EXECUTOR);
-	private final ThreadLocalMac hmacSha256;
-	private final SecretKey headerKey;
-	private final FileHeader header;
-	private final SecureRandom randomSource;
-	private final LongAdder cleartextBytesScheduledForEncryption = new LongAdder();
-	private ByteBuffer cleartextBuffer = ByteBuffer.allocate(PAYLOAD_SIZE);
-	private long chunkNumber = 0;
-
-	public FileContentEncryptorImpl(SecretKey headerKey, SecretKey macKey, SecureRandom randomSource, long firstCleartextByte) {
-		if (firstCleartextByte != 0) {
-			throw new UnsupportedOperationException("Partial encryption not supported.");
-		}
-		this.hmacSha256 = new ThreadLocalMac(macKey, HMAC_SHA256);
-		this.headerKey = headerKey;
-		this.header = new FileHeader(randomSource);
-		this.randomSource = randomSource;
-	}
-
-	@Override
-	public ByteBuffer getHeader() {
-		header.getPayload().setFilesize(-1l);
-		return header.toByteBuffer(headerKey, hmacSha256);
-	}
-
-	@Override
-	public int getHeaderSize() {
-		return FileHeader.HEADER_SIZE;
-	}
-
-	@Override
-	public void append(ByteBuffer cleartext) throws InterruptedException {
-		cleartextBytesScheduledForEncryption.add(cleartext.remaining());
-		if (cleartext == FileContentCryptor.EOF) {
-			submitCleartextBuffer();
-			submitEof();
-		} else {
-			appendAllAndSubmitIfFull(cleartext);
-		}
-	}
-
-	private void appendAllAndSubmitIfFull(ByteBuffer cleartext) throws InterruptedException {
-		while (cleartext.hasRemaining()) {
-			ByteBuffers.copy(cleartext, cleartextBuffer);
-			submitCleartextBufferIfFull();
-		}
-	}
-
-	@Override
-	public void cancelWithException(Exception cause) throws InterruptedException {
-		dataProcessor.submit(() -> {
-			throw cause;
-		});
-	}
-
-	private void submitCleartextBufferIfFull() throws InterruptedException {
-		if (!cleartextBuffer.hasRemaining()) {
-			submitCleartextBuffer();
-			cleartextBuffer = ByteBuffer.allocate(PAYLOAD_SIZE);
-		}
-	}
-
-	private void submitCleartextBuffer() throws InterruptedException {
-		cleartextBuffer.flip();
-		if (cleartextBuffer.hasRemaining()) {
-			Callable<ByteBuffer> encryptionJob = new EncryptionJob(cleartextBuffer, chunkNumber++);
-			dataProcessor.submit(encryptionJob);
-		}
-	}
-
-	private void submitEof() throws InterruptedException {
-		dataProcessor.submitPreprocessed(FileContentCryptor.EOF);
-	}
-
-	@Override
-	public ByteBuffer ciphertext() throws InterruptedException {
-		try {
-			return dataProcessor.processedData();
-		} catch (ExecutionException e) {
-			if (e.getCause() instanceof IOException || e.getCause() instanceof UncheckedIOException) {
-				throw new UncheckedIOException(new IOException("Encryption failed due to I/O exception during cleartext supply.", e));
-			} else {
-				throw new RuntimeException(e);
-			}
-		}
-	}
-
-	@Override
-	public void destroy() {
-		header.destroy();
-	}
-
-	private class EncryptionJob implements Callable<ByteBuffer> {
-
-		private final ByteBuffer inBuf;
-		private final ByteBuffer chunkNumberBigEndian = ByteBuffer.allocate(Long.BYTES);
-
-		public EncryptionJob(ByteBuffer cleartextChunk, long chunkNumber) {
-			this.inBuf = cleartextChunk;
-			chunkNumberBigEndian.putLong(chunkNumber);
-			chunkNumberBigEndian.rewind();
-		}
-
-		@Override
-		public ByteBuffer call() {
-			try {
-				final Cipher cipher = ThreadLocalAesCtrCipher.get();
-				final Mac mac = hmacSha256.get();
-				final ByteBuffer outBuf = ByteBuffer.allocate(NONCE_SIZE + inBuf.remaining() + mac.getMacLength());
-
-				// nonce
-				byte[] nonce = new byte[NONCE_SIZE];
-				randomSource.nextBytes(nonce);
-				outBuf.put(nonce);
-
-				// payload:
-				cipher.init(Cipher.ENCRYPT_MODE, header.getPayload().getContentKey(), new IvParameterSpec(nonce));
-				assert cipher.getOutputSize(inBuf.remaining()) == inBuf.remaining() : "input length should be equal to output length in CTR mode.";
-				int bytesEncrypted = cipher.update(inBuf, outBuf);
-
-				// mac:
-				ByteBuffer ciphertextBuf = outBuf.asReadOnlyBuffer();
-				ciphertextBuf.position(NONCE_SIZE).limit(NONCE_SIZE + bytesEncrypted);
-				mac.update(header.getIv());
-				mac.update(chunkNumberBigEndian.asReadOnlyBuffer());
-				mac.update(nonce);
-				mac.update(ciphertextBuf);
-				byte[] authenticationCode = mac.doFinal();
-				outBuf.put(authenticationCode);
-
-				// flip and return:
-				outBuf.flip();
-				return outBuf;
-			} catch (InvalidKeyException e) {
-				throw new IllegalStateException("File content key created by current class invalid.", e);
-			} catch (ShortBufferException e) {
-				throw new IllegalStateException("Buffer allocated for reported output size apparently not big enought.", e);
-			} catch (InvalidAlgorithmParameterException e) {
-				throw new IllegalStateException("CTR mode known to accept an IV (aka. nonce).", e);
-			}
-		}
-
-	}
-
-}

+ 0 - 117
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java

@@ -1,117 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.nio.ByteBuffer;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.function.Supplier;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.security.auth.Destroyable;
-
-import org.cryptomator.crypto.engine.AuthenticationFailedException;
-
-class FileHeader implements Destroyable {
-
-	static final int HEADER_SIZE = 88;
-
-	private static final int IV_POS = 0;
-	private static final int IV_LEN = 16;
-	private static final int PAYLOAD_POS = 16;
-	private static final int PAYLOAD_LEN = 40;
-	private static final int MAC_POS = 56;
-	private static final int MAC_LEN = 32;
-
-	private final byte[] iv;
-	private final FileHeaderPayload payload;
-
-	public FileHeader(SecureRandom randomSource) {
-		this.iv = new byte[IV_LEN];
-		this.payload = new FileHeaderPayload(randomSource);
-		randomSource.nextBytes(iv);
-	}
-
-	private FileHeader(byte[] iv, FileHeaderPayload payload) {
-		this.iv = iv;
-		this.payload = payload;
-	}
-
-	public byte[] getIv() {
-		return iv;
-	}
-
-	public FileHeaderPayload getPayload() {
-		return payload;
-	}
-
-	public ByteBuffer toByteBuffer(SecretKey headerKey, Supplier<Mac> hmacSha256Factory) {
-		ByteBuffer result = ByteBuffer.allocate(HEADER_SIZE);
-		result.position(IV_POS).limit(IV_POS + IV_LEN);
-		result.put(iv);
-		result.position(PAYLOAD_POS).limit(PAYLOAD_POS + PAYLOAD_LEN);
-		result.put(payload.toCiphertextByteBuffer(headerKey, iv));
-		ByteBuffer resultSoFar = result.asReadOnlyBuffer();
-		resultSoFar.flip();
-		Mac mac = hmacSha256Factory.get();
-		assert mac.getMacLength() == MAC_LEN;
-		mac.update(resultSoFar);
-		result.position(MAC_POS).limit(MAC_POS + MAC_LEN);
-		result.put(mac.doFinal());
-		result.flip();
-		return result;
-	}
-
-	@Override
-	public boolean isDestroyed() {
-		return payload.isDestroyed();
-	}
-
-	@Override
-	public void destroy() {
-		payload.destroy();
-	}
-
-	public static FileHeader decrypt(SecretKey headerKey, Supplier<Mac> hmacSha256Factory, ByteBuffer header) throws IllegalArgumentException, AuthenticationFailedException {
-		if (header.remaining() != HEADER_SIZE) {
-			throw new IllegalArgumentException("Invalid header size.");
-		}
-
-		checkHeaderMac(header, hmacSha256Factory.get());
-
-		final byte[] iv = new byte[IV_LEN];
-		final ByteBuffer ivBuf = header.asReadOnlyBuffer();
-		ivBuf.position(IV_POS).limit(IV_POS + IV_LEN);
-		ivBuf.get(iv);
-
-		final ByteBuffer payloadBuf = header.asReadOnlyBuffer();
-		payloadBuf.position(PAYLOAD_POS).limit(PAYLOAD_POS + PAYLOAD_LEN);
-
-		final FileHeaderPayload payload = FileHeaderPayload.fromCiphertextByteBuffer(payloadBuf, headerKey, iv);
-
-		return new FileHeader(iv, payload);
-	}
-
-	private static void checkHeaderMac(ByteBuffer header, Mac mac) throws AuthenticationFailedException {
-		assert mac.getMacLength() == MAC_LEN;
-		ByteBuffer headerData = header.asReadOnlyBuffer();
-		headerData.position(0).limit(MAC_POS);
-		mac.update(headerData);
-		ByteBuffer headerMac = header.asReadOnlyBuffer();
-		headerMac.position(MAC_POS).limit(MAC_POS + MAC_LEN);
-		byte[] expectedMac = new byte[MAC_LEN];
-		headerMac.get(expectedMac);
-
-		if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) {
-			throw new AuthenticationFailedException("Corrupt header.");
-		}
-	}
-
-}

+ 0 - 149
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java

@@ -1,149 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.DestroyFailedException;
-import javax.security.auth.Destroyable;
-
-class FileHeaderPayload implements Destroyable {
-
-	private static final int FILESIZE_POS = 0;
-	private static final int FILESIZE_LEN = Long.BYTES;
-	private static final int CONTENT_KEY_POS = 8;
-	private static final int CONTENT_KEY_LEN = 32;
-	private static final String AES = "AES";
-
-	private long filesize;
-	private final SecretKey contentKey;
-
-	public FileHeaderPayload(SecureRandom randomSource) {
-		this.filesize = 0;
-		try {
-			KeyGenerator keyGen = KeyGenerator.getInstance(AES);
-			keyGen.init(CONTENT_KEY_LEN * Byte.SIZE, randomSource);
-			this.contentKey = keyGen.generateKey();
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e);
-		}
-	}
-
-	private FileHeaderPayload(long filesize, SecretKey contentKey) {
-		this.filesize = filesize;
-		this.contentKey = contentKey;
-	}
-
-	public long getFilesize() {
-		return filesize;
-	}
-
-	public void setFilesize(long filesize) {
-		this.filesize = filesize;
-	}
-
-	public SecretKey getContentKey() {
-		return contentKey;
-	}
-
-	@Override
-	public boolean isDestroyed() {
-		return contentKey.isDestroyed();
-	}
-
-	@Override
-	public void destroy() {
-		try {
-			contentKey.destroy();
-		} catch (DestroyFailedException e) {
-			// no-op
-		}
-	}
-
-	private ByteBuffer toCleartextByteBuffer() {
-		ByteBuffer cleartext = ByteBuffer.allocate(FILESIZE_LEN + CONTENT_KEY_LEN);
-		cleartext.position(FILESIZE_POS).limit(FILESIZE_POS + FILESIZE_LEN);
-		cleartext.putLong(filesize);
-		cleartext.position(CONTENT_KEY_POS).limit(CONTENT_KEY_POS + CONTENT_KEY_LEN);
-		cleartext.put(contentKey.getEncoded());
-		cleartext.flip();
-		return cleartext;
-	}
-
-	public ByteBuffer toCiphertextByteBuffer(SecretKey headerKey, byte[] iv) {
-		final ByteBuffer cleartext = toCleartextByteBuffer();
-		try {
-			Cipher cipher = ThreadLocalAesCtrCipher.get();
-			cipher.init(Cipher.ENCRYPT_MODE, headerKey, new IvParameterSpec(iv));
-			final int ciphertextLength = cipher.getOutputSize(cleartext.remaining());
-			assert ciphertextLength == cleartext.remaining() : "in counter mode outputlength == input length";
-			final ByteBuffer ciphertext = ByteBuffer.allocate(ciphertextLength);
-			cipher.doFinal(cleartext, ciphertext);
-			ciphertext.flip();
-			return ciphertext;
-		} catch (InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
-			throw new IllegalStateException("Unable to compute encrypted header.", e);
-		} finally {
-			Arrays.fill(cleartext.array(), (byte) 0x00);
-		}
-	}
-
-	public static FileHeaderPayload fromCiphertextByteBuffer(ByteBuffer ciphertextPayload, SecretKey headerKey, byte[] iv) {
-		final ByteBuffer cleartext = decryptPayload(ciphertextPayload, headerKey, iv);
-		try {
-			return fromCleartextByteBuffer(cleartext);
-		} finally {
-			// destroy evidence:
-			Arrays.fill(cleartext.array(), (byte) 0x00);
-		}
-	}
-
-	private static FileHeaderPayload fromCleartextByteBuffer(ByteBuffer cleartext) {
-		final byte[] contentKey = new byte[CONTENT_KEY_LEN];
-		try {
-			cleartext.position(FILESIZE_POS).limit(FILESIZE_POS + FILESIZE_LEN);
-			final long filesize = cleartext.getLong();
-			cleartext.position(CONTENT_KEY_POS).limit(CONTENT_KEY_POS + CONTENT_KEY_LEN);
-			cleartext.get(contentKey);
-			return new FileHeaderPayload(filesize, new SecretKeySpec(contentKey, AES));
-		} finally {
-			// destroy evidence:
-			Arrays.fill(contentKey, (byte) 0x00);
-		}
-	}
-
-	private static ByteBuffer decryptPayload(ByteBuffer ciphertext, SecretKey headerKey, byte[] iv) {
-		try {
-			Cipher cipher = ThreadLocalAesCtrCipher.get();
-			cipher.init(Cipher.DECRYPT_MODE, headerKey, new IvParameterSpec(iv));
-			final int cleartextLength = cipher.getOutputSize(ciphertext.remaining());
-			assert cleartextLength == ciphertext.remaining() : "in counter mode outputlength == input length";
-			final ByteBuffer cleartext = ByteBuffer.allocate(cleartextLength);
-			cipher.doFinal(ciphertext, cleartext);
-			cleartext.flip();
-			return cleartext;
-		} catch (InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
-			throw new IllegalStateException("Unable to decrypt header.", e);
-		}
-	}
-
-}

+ 0 - 98
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java

@@ -1,98 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.regex.Pattern;
-
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.SecretKey;
-
-import org.apache.commons.codec.binary.Base32;
-import org.apache.commons.codec.binary.BaseNCodec;
-import org.cryptomator.crypto.engine.AuthenticationFailedException;
-import org.cryptomator.crypto.engine.FilenameCryptor;
-import org.cryptomator.siv.SivMode;
-import org.cryptomator.siv.UnauthenticCiphertextException;
-
-class FilenameCryptorImpl implements FilenameCryptor {
-
-	private static final BaseNCodec BASE32 = new Base32();
-	// https://tools.ietf.org/html/rfc4648#section-6
-	private static final Pattern BASE32_PATTERN = Pattern.compile("^([A-Z2-7]{8})*[A-Z2-7=]{8}");
-	private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
-	private static final ThreadLocal<SivMode> AES_SIV = new ThreadLocal<SivMode>() {
-		@Override
-		protected SivMode initialValue() {
-			return new SivMode();
-		};
-	};
-
-	private final SecretKey encryptionKey;
-	private final SecretKey macKey;
-
-	FilenameCryptorImpl(SecretKey encryptionKey, SecretKey macKey) {
-		this.encryptionKey = encryptionKey;
-		this.macKey = macKey;
-	}
-
-	@Override
-	public String hashDirectoryId(String cleartextDirectoryId) {
-		final byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8);
-		byte[] encryptedBytes = AES_SIV.get().encrypt(encryptionKey, macKey, cleartextBytes);
-		final byte[] hashedBytes = SHA1.get().digest(encryptedBytes);
-		return BASE32.encodeAsString(hashedBytes);
-	}
-
-	@Override
-	public Pattern encryptedNamePattern() {
-		return BASE32_PATTERN;
-	}
-
-	@Override
-	public String encryptFilename(String cleartextName, byte[]... associatedData) {
-		final byte[] cleartextBytes = cleartextName.getBytes(UTF_8);
-		final byte[] encryptedBytes = AES_SIV.get().encrypt(encryptionKey, macKey, cleartextBytes, associatedData);
-		return BASE32.encodeAsString(encryptedBytes);
-	}
-
-	@Override
-	public String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException {
-		final byte[] encryptedBytes = BASE32.decode(ciphertextName);
-		try {
-			final byte[] cleartextBytes = AES_SIV.get().decrypt(encryptionKey, macKey, encryptedBytes, associatedData);
-			return new String(cleartextBytes, UTF_8);
-		} catch (UnauthenticCiphertextException | IllegalBlockSizeException e) {
-			throw new AuthenticationFailedException("Invalid ciphertext.", e);
-		}
-	}
-
-	private static class ThreadLocalSha1 extends ThreadLocal<MessageDigest> {
-
-		@Override
-		protected MessageDigest initialValue() {
-			try {
-				return MessageDigest.getInstance("SHA-1");
-			} catch (NoSuchAlgorithmException e) {
-				throw new AssertionError("SHA-1 exists in every JVM");
-			}
-		}
-
-		@Override
-		public MessageDigest get() {
-			final MessageDigest messageDigest = super.get();
-			messageDigest.reset();
-			return messageDigest;
-		}
-	}
-
-}

+ 0 - 100
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java

@@ -1,100 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.io.Serializable;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonPropertyOrder;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "primaryMasterKey", "hmacMasterKey", "versionMac"})
-class KeyFile implements Serializable {
-
-	private static final long serialVersionUID = 8578363158959619885L;
-
-	@JsonProperty("version")
-	private Integer version;
-
-	@JsonProperty("scryptSalt")
-	private byte[] scryptSalt;
-
-	@JsonProperty("scryptCostParam")
-	private int scryptCostParam;
-
-	@JsonProperty("scryptBlockSize")
-	private int scryptBlockSize;
-
-	@JsonProperty("primaryMasterKey")
-	private byte[] encryptionMasterKey;
-
-	@JsonProperty("hmacMasterKey")
-	private byte[] macMasterKey;
-
-	@JsonProperty("versionMac")
-	private byte[] versionMac;
-
-	public Integer getVersion() {
-		return version;
-	}
-
-	public void setVersion(Integer version) {
-		this.version = version;
-	}
-
-	public byte[] getScryptSalt() {
-		return scryptSalt;
-	}
-
-	public void setScryptSalt(byte[] scryptSalt) {
-		this.scryptSalt = scryptSalt;
-	}
-
-	public int getScryptCostParam() {
-		return scryptCostParam;
-	}
-
-	public void setScryptCostParam(int scryptCostParam) {
-		this.scryptCostParam = scryptCostParam;
-	}
-
-	public int getScryptBlockSize() {
-		return scryptBlockSize;
-	}
-
-	public void setScryptBlockSize(int scryptBlockSize) {
-		this.scryptBlockSize = scryptBlockSize;
-	}
-
-	public byte[] getEncryptionMasterKey() {
-		return encryptionMasterKey;
-	}
-
-	public void setEncryptionMasterKey(byte[] encryptionMasterKey) {
-		this.encryptionMasterKey = encryptionMasterKey;
-	}
-
-	public byte[] getMacMasterKey() {
-		return macMasterKey;
-	}
-
-	public void setMacMasterKey(byte[] macMasterKey) {
-		this.macMasterKey = macMasterKey;
-	}
-
-	public byte[] getVersionMac() {
-		return versionMac;
-	}
-
-	public void setVersionMac(byte[] versionMac) {
-		this.versionMac = versionMac;
-	}
-
-}

+ 0 - 50
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java

@@ -1,50 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.util.Arrays;
-
-import org.bouncycastle.crypto.generators.SCrypt;
-
-final class Scrypt {
-
-	private Scrypt() {
-	}
-
-	/**
-	 * Derives a key from the given passphrase.
-	 * This implementation makes sure, any copies of the passphrase used during key derivation are overwritten in memory asap (before next GC cycle).
-	 * 
-	 * @param passphrase The passphrase
-	 * @param salt Salt, ideally randomly generated
-	 * @param costParam Cost parameter <code>N</code>, larger than 1, a power of 2 and less than <code>2^(128 * costParam / 8)</code>
-	 * @param blockSize Block size <code>r</code>
-	 * @param keyLengthInBytes Key output length <code>dkLen</code>
-	 * @return Derived key
-	 * @see <a href="https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-04#section-2">RFC Draft</a>
-	 */
-	public static byte[] scrypt(CharSequence passphrase, byte[] salt, int costParam, int blockSize, int keyLengthInBytes) {
-		// This is an attempt to get the password bytes without copies of the password being created in some dark places inside the JVM:
-		final ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
-		final byte[] pw = new byte[buf.remaining()];
-		buf.get(pw);
-		try {
-			return SCrypt.generate(pw, salt, costParam, blockSize, 1, keyLengthInBytes);
-		} finally {
-			Arrays.fill(pw, (byte) 0); // overwrite bytes
-			buf.rewind(); // just resets markers
-			buf.put(pw); // this is where we overwrite the actual bytes
-		}
-	}
-
-}

+ 0 - 36
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalAesCtrCipher.java

@@ -1,36 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
-
-final class ThreadLocalAesCtrCipher {
-
-	private ThreadLocalAesCtrCipher() {
-	}
-
-	private static final String AES_CTR = "AES/CTR/NoPadding";
-	private static final ThreadLocal<Cipher> THREAD_LOCAL_CIPHER = ThreadLocal.withInitial(ThreadLocalAesCtrCipher::newCipherInstance);
-
-	private static Cipher newCipherInstance() {
-		try {
-			return Cipher.getInstance(AES_CTR);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-			throw new IllegalStateException("Could not create Cipher.", e);
-		}
-	}
-
-	public static Cipher get() {
-		return THREAD_LOCAL_CIPHER.get();
-	}
-
-}

+ 0 - 46
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalMac.java

@@ -1,46 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.crypto.engine.impl;
-
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.function.Supplier;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-
-class ThreadLocalMac extends ThreadLocal<Mac>implements Supplier<Mac> {
-
-	private final SecretKey macKey;
-	private final String macAlgorithm;
-
-	ThreadLocalMac(SecretKey macKey, String macAlgorithm) {
-		this.macKey = macKey;
-		this.macAlgorithm = macAlgorithm;
-	}
-
-	@Override
-	protected Mac initialValue() {
-		try {
-			Mac mac = Mac.getInstance(macAlgorithm);
-			mac.init(macKey);
-			return mac;
-		} catch (NoSuchAlgorithmException | InvalidKeyException e) {
-			throw new IllegalStateException("Could not create MAC.", e);
-		}
-	}
-
-	@Override
-	public Mac get() {
-		Mac mac = super.get();
-		mac.reset();
-		return mac;
-	}
-
-}

+ 0 - 12
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/package-info.java

@@ -1,12 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-/**
- * This is where the actual encryption, decryption, hashing and authenticating takes place.
- */
-package org.cryptomator.crypto.engine;

+ 0 - 35
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFile.java

@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.UncheckedIOException;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.delegating.DelegatingFile;
-
-class BlockAlignedFile extends DelegatingFile<BlockAlignedFolder> {
-
-	private final int blockSize;
-
-	public BlockAlignedFile(BlockAlignedFolder parent, File delegate, int blockSize) {
-		super(parent, delegate);
-		this.blockSize = blockSize;
-	}
-
-	@Override
-	public BlockAlignedReadableFile openReadable() throws UncheckedIOException {
-		return new BlockAlignedReadableFile(delegate.openReadable(), blockSize);
-	}
-
-	@Override
-	public BlockAlignedWritableFile openWritable() throws UncheckedIOException {
-		return new BlockAlignedWritableFile(delegate::openWritable, delegate::openReadable, blockSize);
-	}
-
-}

+ 0 - 61
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystem.java

@@ -1,61 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.delegating.DelegatingFileSystem;
-
-/**
- * Provides a decoration layer for the {@link org.cryptomator.filesystem Filesystem API}, which guarantees, that all read/write attempts to underlying files always begin at a block start position.
- * Block start positions are integer multiples of a block size + a fixed block shift.
- * <p>
- * In general the formula to align a requested read with a physical read is <code>floor(x / blockSize) * blockSize</code><br/>
- * For example <code>blockSize=10</code> result in the following block-aligned read/write attempts:
- * 
- * <table>
- * <thead>
- * <tr>
- * <th>Requested Read</th>
- * <th>Physical Read</th>
- * </tr>
- * </thead>
- * <tbody>
- * <tr>
- * <td>0</td>
- * <td>0</td></td>
- * <tr>
- * <td>5</td>
- * <td>0</td></td>
- * <tr>
- * <td>9</td>
- * <td>0</td></td>
- * <tr>
- * <td>10</td>
- * <td>10</td></td>
- * <tr>
- * <td>11</td>
- * <td>10</td></td>
- * <tr>
- * <td>35</td>
- * <td>30</td></td>
- * </tbody>
- * </table>
- */
-class BlockAlignedFileSystem extends BlockAlignedFolder implements DelegatingFileSystem {
-
-	public BlockAlignedFileSystem(Folder delegate, int blockSize) {
-		super(null, delegate, blockSize);
-	}
-
-	@Override
-	public Folder getDelegate() {
-		return delegate;
-	}
-
-}

+ 0 - 29
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystemFactory.java

@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-
-@Singleton
-public class BlockAlignedFileSystemFactory {
-
-	@Inject
-	public BlockAlignedFileSystemFactory() {
-	}
-
-	public FileSystem get(Folder root) {
-		return new BlockAlignedFileSystem(root, PAYLOAD_SIZE);
-	}
-}

+ 0 - 34
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFolder.java

@@ -1,34 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.delegating.DelegatingFolder;
-
-class BlockAlignedFolder extends DelegatingFolder<BlockAlignedFolder, BlockAlignedFile> {
-
-	private final int blockSize;
-
-	public BlockAlignedFolder(BlockAlignedFolder parent, Folder delegate, int blockSize) {
-		super(parent, delegate);
-		this.blockSize = blockSize;
-	}
-
-	@Override
-	protected BlockAlignedFile newFile(File delegate) {
-		return new BlockAlignedFile(this, delegate, blockSize);
-	}
-
-	@Override
-	protected BlockAlignedFolder newFolder(Folder delegate) {
-		return new BlockAlignedFolder(this, delegate, blockSize);
-	}
-
-}

+ 0 - 108
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFile.java

@@ -1,108 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.io.ByteBuffers;
-
-class BlockAlignedReadableFile implements ReadableFile {
-
-	private final ReadableFile delegate;
-	private final int blockSize;
-	private final ByteBuffer currentBlockBuffer;
-	private boolean eofReached = false;
-	private Mode mode = Mode.PASSTHROUGH;
-
-	private enum Mode {
-		BLOCK_ALIGNED, PASSTHROUGH;
-	}
-
-	public BlockAlignedReadableFile(ReadableFile delegate, int blockSize) {
-		if (blockSize < 1) {
-			throw new IllegalArgumentException("Invalid block size");
-		}
-		this.delegate = delegate;
-		this.blockSize = blockSize;
-		this.currentBlockBuffer = ByteBuffer.allocate(blockSize);
-		this.currentBlockBuffer.flip(); // so remaining() is 0 -> next read will read from physical source.
-	}
-
-	@Override
-	public void position(long logicalPosition) throws UncheckedIOException {
-		switchToBlockAlignedMode();
-		long blockNumber = logicalPosition / blockSize;
-		long physicalPosition = blockNumber * blockSize;
-		assert physicalPosition <= logicalPosition;
-		int diff = (int) (logicalPosition - physicalPosition);
-		assert diff >= 0;
-		assert diff < blockSize;
-		delegate.position(physicalPosition);
-		eofReached = false;
-		readCurrentBlock();
-		currentBlockBuffer.position(diff);
-	}
-
-	// visible for testing
-	void switchToBlockAlignedMode() {
-		mode = Mode.BLOCK_ALIGNED;
-	}
-
-	@Override
-	public int read(ByteBuffer target) throws UncheckedIOException {
-		switch (mode) {
-		case PASSTHROUGH:
-			return delegate.read(target);
-		case BLOCK_ALIGNED:
-			return readBlockAligned(target);
-		default:
-			throw new IllegalStateException("Unsupported mode " + mode);
-		}
-	}
-
-	private int readBlockAligned(ByteBuffer target) {
-		if (eofReached) {
-			return -1;
-		} else {
-			int read = 0;
-			while (!eofReached && target.hasRemaining()) {
-				read += ByteBuffers.copy(currentBlockBuffer, target);
-				readCurrentBlockIfNeeded();
-			}
-			return read;
-		}
-	}
-
-	private void readCurrentBlockIfNeeded() {
-		if (!currentBlockBuffer.hasRemaining()) {
-			readCurrentBlock();
-		}
-	}
-
-	private void readCurrentBlock() {
-		currentBlockBuffer.clear();
-		if (delegate.read(currentBlockBuffer) == -1) {
-			eofReached = true;
-		}
-		currentBlockBuffer.flip();
-	}
-
-	@Override
-	public boolean isOpen() {
-		return delegate.isOpen();
-	}
-
-	@Override
-	public void close() throws UncheckedIOException {
-		delegate.close();
-	}
-
-}

+ 0 - 136
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFile.java

@@ -1,136 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.function.Supplier;
-
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
-import org.cryptomator.io.ByteBuffers;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class BlockAlignedWritableFile implements WritableFile {
-
-	private static final Logger LOG = LoggerFactory.getLogger(BlockAlignedWritableFile.class);
-
-	private final Supplier<WritableFile> openWritable;
-	private final Supplier<ReadableFile> openReadable;
-	private final int blockSize;
-	private final ByteBuffer currentBlockBuffer;
-	private Mode mode = Mode.PASSTHROUGH;
-	private Optional<WritableFile> delegate;
-	private long logicalPosition;
-
-	private enum Mode {
-		BLOCK_ALIGNED, PASSTHROUGH;
-	}
-
-	public BlockAlignedWritableFile(Supplier<WritableFile> openWritable, Supplier<ReadableFile> openReadable, int blockSize) {
-		this.openWritable = openWritable;
-		this.openReadable = openReadable;
-		this.blockSize = blockSize;
-		this.currentBlockBuffer = ByteBuffer.allocate(blockSize);
-		currentBlockBuffer.flip(); // make sure the buffer has no remaining bytes by default
-		delegate = Optional.of(openWritable.get());
-	}
-
-	@Override
-	public void position(long logicalPosition) throws UncheckedIOException {
-		switchToBlockAlignedMode();
-		this.logicalPosition = logicalPosition;
-		readCurrentBlock();
-	}
-
-	// visible for testing
-	void switchToBlockAlignedMode() {
-		LOG.trace("switching to blockaligend write...");
-		mode = Mode.BLOCK_ALIGNED;
-	}
-
-	@Override
-	public int write(ByteBuffer source) throws UncheckedIOException {
-		switch (mode) {
-		case PASSTHROUGH:
-			return delegate.get().write(source);
-		case BLOCK_ALIGNED:
-			return writeBlockAligned(source);
-		default:
-			throw new IllegalStateException("Unsupported mode " + mode);
-		}
-	}
-
-	private int writeBlockAligned(ByteBuffer source) {
-		int writtenTotal = 0;
-		while (source.hasRemaining()) {
-			int written = ByteBuffers.copy(source, currentBlockBuffer);
-			logicalPosition += written;
-			writeCurrentBlockIfNeeded();
-			writtenTotal += written;
-		}
-		return writtenTotal;
-	}
-
-	@Override
-	public void close() throws UncheckedIOException {
-		currentBlockBuffer.flip();
-		writeCurrentBlock();
-		delegate.ifPresent(WritableFile::close);
-	}
-
-	private void writeCurrentBlockIfNeeded() {
-		if (!currentBlockBuffer.hasRemaining()) {
-			writeCurrentBlock();
-			readCurrentBlock();
-		}
-	}
-
-	private void writeCurrentBlock() {
-		currentBlockBuffer.rewind();
-		delegate.get().write(currentBlockBuffer);
-	}
-
-	private void readCurrentBlock() {
-		// TODO lock that shit
-
-		// determine right position:
-		long blockNumber = logicalPosition / blockSize;
-		long physicalPosition = blockNumber * blockSize;
-
-		// switch from write to read access:
-		delegate.get().close();
-		currentBlockBuffer.clear();
-		try (ReadableFile r = openReadable.get()) {
-			r.position(physicalPosition);
-			int numRead = r.read(currentBlockBuffer);
-			assert numRead == currentBlockBuffer.position();
-		}
-		int advance = (int) (logicalPosition - physicalPosition);
-		currentBlockBuffer.position(advance);
-
-		// continue write access:
-		WritableFile w = openWritable.get();
-		w.position(physicalPosition);
-		delegate = Optional.of(w);
-	}
-
-	@Override
-	public boolean isOpen() {
-		return delegate.get().isOpen();
-	}
-
-	@Override
-	public void truncate() throws UncheckedIOException {
-		delegate.get().truncate();
-	}
-
-}

+ 0 - 63
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextReader.java

@@ -1,63 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.InterruptedIOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.Callable;
-
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentDecryptor;
-import org.cryptomator.filesystem.ReadableFile;
-
-class CiphertextReader implements Callable<Void> {
-
-	private static final int READ_BUFFER_SIZE = 32 * 1024 + 32; // aligned with encrypted chunk size + MAC size
-
-	private final ReadableFile file;
-	private final FileContentDecryptor decryptor;
-	private final long startpos;
-
-	public CiphertextReader(ReadableFile file, FileContentDecryptor decryptor, long startpos) {
-		this.file = file;
-		this.decryptor = decryptor;
-		this.startpos = startpos;
-	}
-
-	@Override
-	public Void call() throws InterruptedIOException {
-		try {
-			callInterruptibly();
-		} catch (InterruptedException e) {
-			throw new InterruptedIOException("Task interrupted while waiting for ciphertext");
-		}
-		return null;
-	}
-
-	private void callInterruptibly() throws InterruptedException {
-		try {
-			file.position(startpos);
-			int bytesRead = -1;
-			do {
-				ByteBuffer ciphertext = ByteBuffer.allocate(READ_BUFFER_SIZE);
-				bytesRead = file.read(ciphertext);
-				if (bytesRead > 0) {
-					ciphertext.flip();
-					assert bytesRead == ciphertext.remaining();
-					decryptor.append(ciphertext);
-				}
-			} while (bytesRead > 0);
-			decryptor.append(FileContentCryptor.EOF);
-		} catch (UncheckedIOException e) {
-			decryptor.cancelWithException(e);
-		}
-	}
-
-}

+ 0 - 51
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextWriter.java

@@ -1,51 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.InterruptedIOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.Callable;
-
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentEncryptor;
-import org.cryptomator.filesystem.WritableFile;
-
-class CiphertextWriter implements Callable<Void> {
-
-	private final WritableFile file;
-	private final FileContentEncryptor encryptor;
-
-	public CiphertextWriter(WritableFile file, FileContentEncryptor encryptor) {
-		this.file = file;
-		this.encryptor = encryptor;
-	}
-
-	@Override
-	public Void call() throws InterruptedIOException {
-		try {
-			callInterruptibly();
-		} catch (InterruptedException e) {
-			throw new InterruptedIOException("Task interrupted while waiting for ciphertext");
-		}
-		return null;
-	}
-
-	private void callInterruptibly() throws InterruptedException {
-		try {
-			ByteBuffer ciphertext;
-			while ((ciphertext = encryptor.ciphertext()) != FileContentCryptor.EOF) {
-				file.write(ciphertext);
-			}
-		} catch (UncheckedIOException e) {
-			encryptor.cancelWithException(e);
-		}
-	}
-
-}

+ 0 - 105
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java

@@ -1,105 +0,0 @@
-package org.cryptomator.filesystem.crypto;
-
-import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;
-
-import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.function.Function;
-import java.util.regex.MatchResult;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.commons.lang3.StringUtils;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.ReadableFile;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-final class ConflictResolver {
-
-	private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class);
-	private static final int UUID_FIRST_GROUP_STRLEN = 8;
-	private static final int MAX_DIR_FILE_SIZE = 87; // "normal" file header has 88 bytes
-
-	private final Pattern encryptedNamePattern;
-	private final Function<String, Optional<String>> nameDecryptor;
-	private final Function<String, Optional<String>> nameEncryptor;
-
-	public ConflictResolver(Pattern encryptedNamePattern, Function<String, Optional<String>> nameDecryptor, Function<String, Optional<String>> nameEncryptor) {
-		this.encryptedNamePattern = encryptedNamePattern;
-		this.nameDecryptor = nameDecryptor;
-		this.nameEncryptor = nameEncryptor;
-	}
-
-	public File resolveIfNecessary(File file) {
-		Matcher m = encryptedNamePattern.matcher(StringUtils.removeStart(file.name(), DIR_PREFIX));
-		if (m.matches()) {
-			// full match, use file as is
-			return file;
-		} else if (m.find(0)) {
-			// partial match, might be conflicting
-			return resolveConflict(file, m.toMatchResult());
-		} else {
-			// no match, file not relevant
-			return file;
-		}
-	}
-
-	private File resolveConflict(File conflictingFile, MatchResult matchResult) {
-		String ciphertext = matchResult.group();
-		boolean isDirectory = conflictingFile.name().startsWith(DIR_PREFIX);
-		Optional<String> cleartext = nameDecryptor.apply(ciphertext);
-		if (cleartext.isPresent()) {
-			Folder folder = conflictingFile.parent().get();
-			File canonicalFile = folder.file(isDirectory ? DIR_PREFIX + ciphertext : ciphertext);
-			if (isDirectory && canonicalFile.exists() && isSameFileBasedOnSample(canonicalFile, conflictingFile, MAX_DIR_FILE_SIZE)) {
-				// there must not be two directories pointing to the same directory id. In this case no human interaction is needed to resolve this conflict:
-				conflictingFile.delete();
-				return canonicalFile;
-			} else {
-				// conventional conflict detected! look for an alternative name:
-				File alternativeFile;
-				String conflictId;
-				do {
-					conflictId = createConflictId();
-					String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")";
-					String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get();
-					alternativeFile = folder.file(isDirectory ? DIR_PREFIX + alternativeCiphertext : alternativeCiphertext);
-				} while (alternativeFile.exists());
-				LOG.debug("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile);
-				conflictingFile.moveTo(alternativeFile);
-				return alternativeFile;
-			}
-		} else {
-			// not decryptable; false positive
-			return conflictingFile;
-		}
-	}
-
-	private boolean isSameFileBasedOnSample(File file1, File file2, int sampleSize) {
-		if (file1.size() != file2.size()) {
-			return false;
-		} else {
-			try (ReadableFile r1 = file1.openReadable(); ReadableFile r2 = file2.openReadable()) {
-				ByteBuffer beginOfFile1 = ByteBuffer.allocate(sampleSize);
-				ByteBuffer beginOfFile2 = ByteBuffer.allocate(sampleSize);
-				int bytesRead1 = r1.read(beginOfFile1);
-				int bytesRead2 = r2.read(beginOfFile2);
-				if (bytesRead1 == bytesRead2) {
-					beginOfFile1.flip();
-					beginOfFile2.flip();
-					return beginOfFile1.equals(beginOfFile2);
-				} else {
-					return false;
-				}
-			}
-		}
-	}
-
-	private String createConflictId() {
-		return UUID.randomUUID().toString().substring(0, UUID_FIRST_GROUP_STRLEN);
-	}
-
-}

+ 0 - 16
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java

@@ -1,16 +0,0 @@
-package org.cryptomator.filesystem.crypto;
-
-public final class Constants {
-
-	private Constants() {
-	}
-
-	static final String DATA_ROOT_DIR = "d";
-	static final String ROOT_DIRECOTRY_ID = "";
-
-	public static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
-	public static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
-
-	static final String DIR_PREFIX = "0";
-
-}

+ 0 - 116
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java

@@ -1,116 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE;
-import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE;
-
-import java.io.UncheckedIOException;
-import java.nio.file.FileAlreadyExistsException;
-import java.util.Optional;
-
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
-
-class CryptoFile extends CryptoNode implements File {
-
-	public CryptoFile(CryptoFolder parent, String name, Cryptor cryptor) {
-		super(parent, name, cryptor);
-	}
-
-	@Override
-	protected Optional<String> encryptedName() {
-		return parent().get().encryptChildName(name());
-	}
-
-	@Override
-	public long size() throws UncheckedIOException {
-		if (!physicalFile().isPresent()) {
-			return -1l;
-		} else {
-			File file = physicalFile().get();
-			long ciphertextSize = file.size() - cryptor.getFileContentCryptor().getHeaderSize();
-			long overheadPerChunk = CHUNK_SIZE - PAYLOAD_SIZE;
-			long numFullChunks = ciphertextSize / CHUNK_SIZE; // floor by int-truncation
-			long additionalCiphertextBytes = ciphertextSize % CHUNK_SIZE;
-			if (additionalCiphertextBytes > 0 && additionalCiphertextBytes <= overheadPerChunk) {
-				throw new IllegalArgumentException("Method not defined for input value " + ciphertextSize);
-			}
-			long additionalCleartextBytes = (additionalCiphertextBytes == 0) ? 0 : additionalCiphertextBytes - overheadPerChunk;
-			assert additionalCleartextBytes >= 0;
-			return PAYLOAD_SIZE * numFullChunks + additionalCleartextBytes;
-		}
-	}
-
-	@Override
-	public ReadableFile openReadable() {
-		boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString());
-		ReadableFile physicalReadable = forceGetPhysicalFile().openReadable();
-		boolean success = false;
-		try {
-			final ReadableFile result = new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalReadable, authenticate, this::reportAuthError);
-			success = true;
-			return result;
-		} finally {
-			if (!success) {
-				physicalReadable.close();
-			}
-		}
-	}
-
-	private void reportAuthError() {
-		fileSystem().delegate().authenticationFailed(this.toString());
-	}
-
-	@Override
-	public WritableFile openWritable() {
-		if (parent.folder(name).exists()) {
-			throw new UncheckedIOException(new FileAlreadyExistsException(toString()));
-		}
-		WritableFile physicalWrtiable = forceGetPhysicalFile().openWritable();
-		boolean success = false;
-		try {
-			final WritableFile result = new CryptoWritableFile(cryptor.getFileContentCryptor(), physicalWrtiable);
-			success = true;
-			return result;
-		} finally {
-			if (!success) {
-				physicalWrtiable.close();
-			}
-		}
-	}
-
-	@Override
-	public String toString() {
-		return parent.toString() + name;
-	}
-
-	@Override
-	public int compareTo(File o) {
-		return toString().compareTo(o.toString());
-	}
-
-	@Override
-	public void delete() throws UncheckedIOException {
-		forceGetPhysicalFile().delete();
-	}
-
-	@Override
-	public void moveTo(File destination) throws UncheckedIOException {
-		if (destination instanceof CryptoFile) {
-			CryptoFile dst = (CryptoFile) destination;
-			forceGetPhysicalFile().moveTo(dst.forceGetPhysicalFile());
-		} else {
-			throw new IllegalArgumentException("Can not move CryptoFile to conventional File.");
-		}
-	}
-
-}

+ 0 - 108
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java

@@ -1,108 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import static org.cryptomator.filesystem.crypto.Constants.DATA_ROOT_DIR;
-import static org.cryptomator.filesystem.crypto.Constants.ROOT_DIRECOTRY_ID;
-
-import java.io.UncheckedIOException;
-import java.time.Instant;
-import java.util.Optional;
-
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.crypto.engine.InvalidPassphraseException;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-
-class CryptoFileSystem extends CryptoFolder implements FileSystem {
-
-	private final Folder physicalRoot;
-	private final CryptoFileSystemDelegate delegate;
-
-	public CryptoFileSystem(Folder physicalRoot, Cryptor cryptor, CryptoFileSystemDelegate delegate, CharSequence passphrase) throws InvalidPassphraseException {
-		super(null, "", cryptor);
-		if (cryptor.isDestroyed()) {
-			throw new IllegalArgumentException("Cryptor's keys must not be destroyed.");
-		}
-		this.physicalRoot = physicalRoot;
-		this.delegate = delegate;
-		create();
-	}
-
-	CryptoFileSystemDelegate delegate() {
-		return delegate;
-	}
-
-	@Override
-	protected Optional<String> getDirectoryId() {
-		return Optional.of(ROOT_DIRECOTRY_ID);
-	}
-
-	@Override
-	protected Optional<File> physicalFile() {
-		throw new UnsupportedOperationException("Crypto filesystem root doesn't provide a directory file, as the directory ID is fixed.");
-	}
-
-	@Override
-	protected Folder physicalDataRoot() {
-		return physicalRoot.folder(DATA_ROOT_DIR);
-	}
-
-	@Override
-	public Optional<Long> quotaUsedBytes() {
-		return physicalRoot.fileSystem().quotaUsedBytes();
-	}
-
-	@Override
-	public Optional<Long> quotaAvailableBytes() {
-		return physicalRoot.fileSystem().quotaAvailableBytes();
-	}
-
-	@Override
-	public Optional<CryptoFolder> parent() {
-		return Optional.empty();
-	}
-
-	@Override
-	public boolean exists() {
-		return forceGetPhysicalFolder().exists();
-	}
-
-	@Override
-	public Optional<Instant> creationTime() throws UncheckedIOException {
-		return forceGetPhysicalFolder().creationTime();
-	}
-
-	@Override
-	public Instant lastModified() {
-		return forceGetPhysicalFolder().lastModified();
-	}
-
-	@Override
-	public void delete() {
-		throw new UnsupportedOperationException("Can not delete CryptoFileSytem root.");
-	}
-
-	@Override
-	public void moveTo(Folder target) {
-		throw new UnsupportedOperationException("Can not move CryptoFileSytem root.");
-	}
-
-	@Override
-	public void create() {
-		forceGetPhysicalFolder().create();
-	}
-
-	@Override
-	public String toString() {
-		return "/";
-	}
-
-}

+ 0 - 29
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java

@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-public interface CryptoFileSystemDelegate {
-
-	/**
-	 * Reports the path for resources, that could not be decrypted due to authentication errors.
-	 * 
-	 * @param cleartextPath Unix-style vault-relative path
-	 */
-	void authenticationFailed(String cleartextPath);
-
-	/**
-	 * Allows the delegate to deactivate authentication during decryption.
-	 * This bears the risk of CCAs, thus this method should only return <code>true</code> for data recovery purposes.
-	 * 
-	 * @param cleartextPath Unix-style vault-relative path
-	 * @return Must always <b>default to <code>false</code></b>, except when authentication should be skipped.
-	 */
-	boolean shouldSkipAuthentication(String cleartextPath);
-
-}

+ 0 - 61
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java

@@ -1,61 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_FILENAME;
-
-import java.io.UncheckedIOException;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.crypto.engine.InvalidPassphraseException;
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-
-@Singleton
-public class CryptoFileSystemFactory {
-
-	private final Masterkeys masterkeys;
-	private final BlockAlignedFileSystemFactory blockAlignedFileSystemFactory;
-
-	@Inject
-	public CryptoFileSystemFactory(Masterkeys masterkeys, BlockAlignedFileSystemFactory blockAlignedFileSystemFactory) {
-		this.masterkeys = masterkeys;
-		this.blockAlignedFileSystemFactory = blockAlignedFileSystemFactory;
-	}
-
-	public boolean isValidVaultStructure(Folder vaultLocation) {
-		return vaultLocation.file(MASTERKEY_FILENAME).exists();
-	}
-
-	public void initializeNew(Folder vaultLocation, CharSequence passphrase) {
-		masterkeys.initialize(vaultLocation, passphrase);
-	}
-
-	public FileSystem unlockExisting(Folder vaultLocation, CharSequence passphrase, CryptoFileSystemDelegate delegate) throws InvalidPassphraseException {
-		final Cryptor cryptor = masterkeys.decrypt(vaultLocation, passphrase);
-		masterkeys.backup(vaultLocation);
-		final FileSystem cryptoFs = new CryptoFileSystem(vaultLocation, cryptor, delegate, passphrase);
-		return blockAlignedFileSystemFactory.get(cryptoFs);
-	}
-
-	public void changePassphrase(Folder vaultLocation, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException {
-		masterkeys.backup(vaultLocation);
-		try {
-			masterkeys.changePassphrase(vaultLocation, oldPassphrase, newPassphrase);
-			// At this point the backup is still using the old password.
-			// It will be changed as soon as the user unlocks the vault the next time.
-			// This way he can still restore the old password, if he doesn't remember the new one.
-		} catch (UncheckedIOException e) {
-			masterkeys.restoreBackup(vaultLocation);
-		}
-	}
-}

+ 0 - 243
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java

@@ -1,243 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.lang3.StringUtils.removeStart;
-import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;
-
-import java.io.FileNotFoundException;
-import java.io.UncheckedIOException;
-import java.nio.file.FileAlreadyExistsException;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
-import java.util.stream.Stream;
-
-import org.apache.commons.lang3.StringUtils;
-import org.cryptomator.common.LazyInitializer;
-import org.cryptomator.common.WeakValuedCache;
-import org.cryptomator.common.streams.AutoClosingStream;
-import org.cryptomator.crypto.engine.CryptoException;
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.filesystem.Deleter;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.Node;
-import org.cryptomator.io.FileContents;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class CryptoFolder extends CryptoNode implements Folder {
-
-	private static final Logger LOG = LoggerFactory.getLogger(CryptoFolder.class);
-	private final WeakValuedCache<String, CryptoFolder> folders = WeakValuedCache.usingLoader(this::newFolder);
-	private final WeakValuedCache<String, CryptoFile> files = WeakValuedCache.usingLoader(this::newFile);
-	private final AtomicReference<String> directoryId = new AtomicReference<>();
-	private final ConflictResolver conflictResolver;
-
-	public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) {
-		super(parent, name, cryptor);
-		this.conflictResolver = new ConflictResolver(cryptor.getFilenameCryptor().encryptedNamePattern(), this::decryptChildName, this::encryptChildName);
-	}
-
-	/* ======================= name + directory id ======================= */
-
-	@Override
-	protected Optional<String> encryptedName() {
-		if (parent().isPresent()) {
-			return parent().get().encryptChildName(name()).map(s -> DIR_PREFIX + s);
-		} else {
-			return Optional.of(DIR_PREFIX + cryptor.getFilenameCryptor().encryptFilename(name()));
-		}
-	}
-
-	Optional<Folder> physicalFolder() {
-		if (getDirectoryId().isPresent()) {
-			final String encryptedThenHashedDirId = cryptor.getFilenameCryptor().hashDirectoryId(getDirectoryId().get());
-			return Optional.of(physicalDataRoot().folder(encryptedThenHashedDirId.substring(0, 2)).folder(encryptedThenHashedDirId.substring(2)));
-		} else {
-			return Optional.empty();
-		}
-	}
-
-	Folder forceGetPhysicalFolder() {
-		return physicalFolder().orElseThrow(() -> {
-			return new UncheckedIOException(new FileNotFoundException(toString()));
-		});
-	}
-
-	protected Optional<String> getDirectoryId() {
-		return Optional.ofNullable(LazyInitializer.initializeLazily(directoryId, () -> {
-			return physicalFile().filter(File::exists).map(FileContents.UTF_8::readContents).orElse(null);
-		}));
-	}
-
-	/* ======================= children ======================= */
-
-	@Override
-	public Stream<? extends Node> children() {
-		return AutoClosingStream.from(Stream.concat(files(), folders()));
-	}
-
-	private Stream<File> nonConflictingFiles() {
-		if (exists()) {
-			final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
-			return files.filter(startsWithEncryptedName()).map(conflictResolver::resolveIfNecessary).distinct();
-		} else {
-			throw new UncheckedIOException(new FileNotFoundException(format("Folder %s does not exist", this)));
-		}
-	}
-
-	private Predicate<File> startsWithEncryptedName() {
-		final Pattern encryptedNamePattern = cryptor.getFilenameCryptor().encryptedNamePattern();
-		return (File file) -> encryptedNamePattern.matcher(removeStart(file.name(),DIR_PREFIX)).find();
-	}
-
-	Optional<String> decryptChildName(String ciphertextFileName) {
-		return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
-			try {
-				return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId);
-			} catch (CryptoException e) {
-				LOG.warn("Filename decryption of {} failed: {}", ciphertextFileName, e.getMessage());
-				return null;
-			}
-		});
-	}
-
-	Optional<String> encryptChildName(String cleartextFileName) {
-		return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
-			return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId);
-		});
-	}
-
-	@Override
-	public Stream<CryptoFile> files() {
-		return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
-	}
-
-	@Override
-	public Stream<CryptoFolder> folders() {
-		return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix()).map(this::removeDirPrefix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
-	}
-
-	private Predicate<String> startsWithDirPrefix() {
-		return (String encryptedFolderName) -> StringUtils.startsWith(encryptedFolderName, DIR_PREFIX);
-	}
-
-	private String removeDirPrefix(String encryptedFolderName) {
-		return StringUtils.removeStart(encryptedFolderName, DIR_PREFIX);
-	}
-
-	@Override
-	public CryptoFile file(String name) {
-		return files.get(name);
-	}
-
-	@Override
-	public CryptoFolder folder(String name) {
-		return folders.get(name);
-	}
-
-	private CryptoFile newFile(String name) {
-		return new CryptoFile(this, name, cryptor);
-	}
-
-	private CryptoFolder newFolder(String name) {
-		return new CryptoFolder(this, name, cryptor);
-	}
-
-	/* ======================= create/move/delete ======================= */
-
-	@Override
-	public void create() {
-		parent.create();
-		final boolean newDirIdGiven = directoryId.compareAndSet(null, UUID.randomUUID().toString());
-		final File dirFile = forceGetPhysicalFile();
-		final Folder dir = forceGetPhysicalFolder();
-		if (dirFile.exists() && dir.exists()) {
-			return;
-		} else if (!newDirIdGiven) {
-			throw new IllegalStateException("Newly created folder, that didn't exist before, already had an directoryId.");
-		}
-		if (parent.file(name).exists()) {
-			throw new UncheckedIOException(new FileAlreadyExistsException(parent.file(name).toString()));
-		}
-		FileContents.UTF_8.writeContents(dirFile, directoryId.get());
-		dir.create();
-	}
-
-	@Override
-	public void moveTo(Folder target) {
-		if (target instanceof CryptoFolder) {
-			moveToInternal((CryptoFolder) target);
-		} else {
-			throw new UnsupportedOperationException("Can not move CryptoFolder to conventional folder.");
-		}
-	}
-
-	private void moveToInternal(CryptoFolder target) {
-		if (this.isAncestorOf(target) || target.isAncestorOf(this)) {
-			throw new IllegalArgumentException("Can not move directories containing one another (src: " + this + ", dst: " + target + ")");
-		}
-		assert target.parent().isPresent() : "Target can not be root, thus has a parent";
-
-		// prepare target:
-		target.delete();
-		target.parent().get().create();
-
-		// perform the actual move:
-		final File dirFile = forceGetPhysicalFile();
-		final String dirId = getDirectoryId().get();
-		boolean dirIdMovedSuccessfully = target.directoryId.compareAndSet(null, dirId);
-		if (!dirIdMovedSuccessfully) {
-			throw new IllegalStateException("Target's directoryId wasn't null, even though it has been explicitly deleted.");
-		}
-		dirFile.moveTo(target.forceGetPhysicalFile());
-
-		// cut all ties:
-		this.invalidateDirectoryIdsRecursively();
-
-		assert !exists();
-		assert target.exists();
-	}
-
-	@Override
-	public void delete() {
-		if (!exists()) {
-			assert directoryId.get() == null : "nonexisting folder still has a directory id";
-			return;
-		}
-		Deleter.deleteContent(this);
-		Folder physicalFolder = forceGetPhysicalFolder();
-		physicalFolder.delete();
-		Folder physicalFolderParent = physicalFolder.parent().get();
-		if (physicalFolderParent.exists() && physicalFolderParent.folders().count() == 0) {
-			physicalFolderParent.delete();
-		}
-		forceGetPhysicalFile().delete();
-		invalidateDirectoryIdsRecursively();
-	}
-
-	private void invalidateDirectoryIdsRecursively() {
-		directoryId.set(null);
-		folders.forEach((name, folder) -> {
-			folder.invalidateDirectoryIdsRecursively();
-		});
-	}
-
-	@Override
-	public String toString() {
-		return parent.toString() + name + "/";
-	}
-
-}

+ 0 - 122
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java

@@ -1,122 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.FileNotFoundException;
-import java.io.UncheckedIOException;
-import java.time.Instant;
-import java.util.Optional;
-
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.Node;
-
-abstract class CryptoNode implements Node {
-
-	protected final CryptoFolder parent;
-	protected final String name;
-	protected final Cryptor cryptor;
-
-	public CryptoNode(CryptoFolder parent, String name, Cryptor cryptor) {
-		this.parent = parent;
-		this.name = name;
-		this.cryptor = cryptor;
-	}
-
-	protected Folder physicalDataRoot() {
-		return parent.physicalDataRoot();
-	}
-
-	protected abstract Optional<String> encryptedName();
-
-	protected Optional<File> physicalFile() {
-		if (parent.exists() && encryptedName().isPresent()) {
-			return Optional.of(parent.forceGetPhysicalFolder().file(encryptedName().get()));
-		} else {
-			return Optional.empty();
-		}
-	}
-
-	protected File forceGetPhysicalFile() {
-		return physicalFile().orElseThrow(() -> {
-			return new UncheckedIOException(new FileNotFoundException(toString()));
-		});
-	}
-
-	@Override
-	public CryptoFileSystem fileSystem() {
-		return (CryptoFileSystem) Node.super.fileSystem();
-	}
-
-	@Override
-	public Optional<CryptoFolder> parent() {
-		return Optional.of(parent);
-	}
-
-	@Override
-	public String name() {
-		return name;
-	}
-
-	@Override
-	public boolean exists() {
-		return physicalFile().map(File::exists).orElse(false);
-	}
-
-	@Override
-	public Instant lastModified() {
-		return forceGetPhysicalFile().lastModified();
-	}
-
-	@Override
-	public void setLastModified(Instant lastModified) throws UncheckedIOException {
-		forceGetPhysicalFile().setLastModified(lastModified);
-	}
-
-	@Override
-	public Optional<Instant> creationTime() throws UncheckedIOException {
-		return forceGetPhysicalFile().creationTime();
-	}
-
-	@Override
-	public void setCreationTime(Instant creationTime) throws UncheckedIOException {
-		forceGetPhysicalFile().setCreationTime(creationTime);
-	}
-
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((name == null) ? 0 : name.hashCode());
-		result = prime * result + ((parent == null) ? 0 : parent.hashCode());
-		return result;
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (obj instanceof CryptoNode) {
-			CryptoNode other = (CryptoNode) obj;
-			return this.getClass() == other.getClass() //
-					&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
-					&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
-		} else {
-			return false;
-		}
-	}
-
-	/**
-	 * Unix-style cleartext path rooted at the vault's top-level directory.
-	 * 
-	 * @return Vault-relative cleartext path.
-	 */
-	@Override
-	public abstract String toString();
-
-}

+ 0 - 111
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java

@@ -1,111 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-import org.cryptomator.crypto.engine.AuthenticationFailedException;
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentDecryptor;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.io.ByteBuffers;
-
-class CryptoReadableFile implements ReadableFile {
-
-	private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
-
-	private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
-	private final ByteBuffer header;
-	private final FileContentCryptor cryptor;
-	private final ReadableFile file;
-	private final boolean authenticate;
-	private final Runnable onAuthError;
-	private FileContentDecryptor decryptor;
-	private Future<Void> readAheadTask;
-	private ByteBuffer bufferedCleartext = EMPTY_BUFFER;
-
-	public CryptoReadableFile(FileContentCryptor cryptor, ReadableFile file, boolean authenticate, Runnable onAuthError) {
-		this.header = ByteBuffer.allocate(cryptor.getHeaderSize());
-		this.cryptor = cryptor;
-		this.file = file;
-		this.authenticate = authenticate;
-		this.onAuthError = onAuthError;
-		file.position(0);
-		int headerBytesRead = file.read(header);
-		if (headerBytesRead != header.capacity()) {
-			throw new IllegalArgumentException("File too short to contain a header.");
-		}
-		header.flip();
-		this.position(0);
-	}
-
-	@Override
-	public int read(ByteBuffer target) {
-		try {
-			if (bufferedCleartext == FileContentCryptor.EOF) {
-				return -1;
-			}
-			int bytesRead = 0;
-			while (target.remaining() > 0 && bufferedCleartext != FileContentCryptor.EOF) {
-				bufferCleartext();
-				bytesRead += readFromBufferedCleartext(target);
-			}
-			return bytesRead;
-		} catch (InterruptedException e) {
-			throw new UncheckedIOException(new InterruptedIOException("Task interrupted while waiting for cleartext"));
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	@Override
-	public void position(long position) throws UncheckedIOException {
-		if (readAheadTask != null) {
-			readAheadTask.cancel(true);
-			bufferedCleartext = EMPTY_BUFFER;
-		}
-		long ciphertextPos = cryptor.toCiphertextPos(position);
-		decryptor = cryptor.createFileContentDecryptor(header.asReadOnlyBuffer(), ciphertextPos, authenticate);
-		readAheadTask = executorService.submit(new CiphertextReader(file, decryptor, header.remaining() + ciphertextPos));
-	}
-
-	private void bufferCleartext() throws InterruptedException, IOException {
-		if (!bufferedCleartext.hasRemaining()) {
-			try {
-				bufferedCleartext = decryptor.cleartext();
-			} catch (AuthenticationFailedException e) {
-				onAuthError.run();
-				throw new IOException("Failed to decrypt file due to an authentication error.", e);
-			}
-		}
-	}
-
-	private int readFromBufferedCleartext(ByteBuffer target) {
-		assert bufferedCleartext != null;
-		return ByteBuffers.copy(bufferedCleartext, target);
-	}
-
-	@Override
-	public boolean isOpen() {
-		return file.isOpen();
-	}
-
-	@Override
-	public void close() {
-		executorService.shutdownNow();
-		file.close();
-	}
-
-}

+ 0 - 113
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java

@@ -1,113 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-import org.cryptomator.crypto.engine.FileContentCryptor;
-import org.cryptomator.crypto.engine.FileContentEncryptor;
-import org.cryptomator.filesystem.WritableFile;
-import org.cryptomator.io.ByteBuffers;
-
-class CryptoWritableFile implements WritableFile {
-
-	final WritableFile file;
-	private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
-	private final FileContentCryptor cryptor;
-
-	private FileContentEncryptor encryptor;
-	private Future<Void> writeTask;
-
-	public CryptoWritableFile(FileContentCryptor cryptor, WritableFile file) {
-		this.file = file;
-		this.cryptor = cryptor;
-		initialize(0);
-	}
-
-	private void initialize(long firstCleartextByte) {
-		encryptor = cryptor.createFileContentEncryptor(Optional.empty(), firstCleartextByte);
-		writeHeader(); // write header with "zero content length" to avoid read access while still writing
-		writeTask = executorService.submit(new CiphertextWriter(file, encryptor));
-	}
-
-	private void writeHeader() {
-		ByteBuffer header = encryptor.getHeader();
-		header.rewind();
-		file.position(0);
-		file.write(header);
-	}
-
-	@Override
-	public int write(ByteBuffer source) {
-		final int size = source.remaining();
-		final ByteBuffer cleartextCopy = ByteBuffer.allocate(size);
-		ByteBuffers.copy(source, cleartextCopy);
-		cleartextCopy.flip();
-		try {
-			encryptor.append(cleartextCopy);
-			return size;
-		} catch (InterruptedException e) {
-			throw new UncheckedIOException(new InterruptedIOException("Task interrupted while waiting for encryptor capacity"));
-		}
-	}
-
-	@Override
-	public void position(long position) {
-		throw new UnsupportedOperationException("Partial write not implemented yet.");
-	}
-
-	@Override
-	public void truncate() {
-		terminateAndWaitForWriteTask();
-		file.truncate();
-		initialize(0);
-	}
-
-	@Override
-	public boolean isOpen() {
-		return file.isOpen();
-	}
-
-	@Override
-	public void close() {
-		try {
-			if (file.isOpen()) {
-				terminateAndWaitForWriteTask();
-				writeHeader();
-			}
-		} finally {
-			executorService.shutdownNow();
-			file.close();
-		}
-	}
-
-	private void terminateAndWaitForWriteTask() {
-		try {
-			encryptor.append(FileContentCryptor.EOF);
-			writeTask.get();
-		} catch (ExecutionException e) {
-			if (e.getCause() instanceof UncheckedIOException || e.getCause() instanceof IOException) {
-				throw new UncheckedIOException(new IOException(e));
-			} else {
-				throw new IllegalStateException("Unexpected exception while waiting for encrypted file to be written", e);
-			}
-		} catch (InterruptedException e) {
-			throw new UncheckedIOException(new InterruptedIOException("Task interrupted while flushing encrypted content"));
-		}
-	}
-
-}

+ 0 - 112
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java

@@ -1,112 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.crypto;
-
-import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_BACKUP_FILENAME;
-import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_FILENAME;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-
-import org.apache.commons.io.IOUtils;
-import org.cryptomator.crypto.engine.Cryptor;
-import org.cryptomator.crypto.engine.InvalidPassphraseException;
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.WritableFile;
-
-@Singleton
-class Masterkeys {
-
-	private final Provider<Cryptor> cryptorProvider;
-
-	@Inject
-	public Masterkeys(Provider<Cryptor> cryptorProvider) {
-		this.cryptorProvider = cryptorProvider;
-	}
-
-	public void initialize(Folder vaultLocation, CharSequence passphrase) {
-		File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
-		Cryptor cryptor = cryptorProvider.get();
-		try {
-			cryptor.randomizeMasterkey();
-			writeMasterKey(masterkeyFile, cryptor, passphrase);
-		} finally {
-			cryptor.destroy();
-		}
-	}
-
-	public Cryptor decrypt(Folder vaultLocation, CharSequence passphrase) throws InvalidPassphraseException {
-		File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
-		Cryptor cryptor = cryptorProvider.get();
-		boolean success = false;
-		try {
-			readMasterKey(masterkeyFile, cryptor, passphrase);
-			success = true;
-		} finally {
-			if (!success) {
-				cryptor.destroy();
-			}
-		}
-		return cryptor;
-	}
-
-	public void changePassphrase(Folder vaultLocation, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException {
-		File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
-		Cryptor cryptor = cryptorProvider.get();
-		try {
-			readMasterKey(masterkeyFile, cryptor, oldPassphrase);
-			writeMasterKey(masterkeyFile, cryptor, newPassphrase);
-		} finally {
-			cryptor.destroy();
-		}
-	}
-
-	public void backup(Folder vaultLocation) {
-		File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
-		File backupFile = vaultLocation.file(MASTERKEY_BACKUP_FILENAME);
-		masterkeyFile.copyTo(backupFile);
-	}
-
-	public void restoreBackup(Folder vaultLocation) {
-		File backupFile = vaultLocation.file(MASTERKEY_BACKUP_FILENAME);
-		File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
-		backupFile.copyTo(masterkeyFile);
-	}
-
-	/* I/O */
-
-	private static void readMasterKey(File file, Cryptor cryptor, CharSequence passphrase) throws UncheckedIOException, InvalidPassphraseException {
-		try ( //
-				ReadableByteChannel channel = file.openReadable(); //
-				InputStream in = Channels.newInputStream(channel)) {
-			final byte[] fileContents = IOUtils.toByteArray(in);
-			cryptor.readKeysFromMasterkeyFile(fileContents, passphrase);
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	private static void writeMasterKey(File file, Cryptor cryptor, CharSequence passphrase) throws UncheckedIOException {
-		try (WritableFile writable = file.openWritable()) {
-			writable.truncate();
-			final byte[] fileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
-			writable.write(ByteBuffer.wrap(fileContents));
-		}
-	}
-
-}

+ 0 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/package-info.java


Some files were not shown because too many files changed in this diff