diff --git a/pom.xml b/pom.xml index f4965755..b04b5f5e 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 1.8.5 + 1.8.6 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs diff --git a/src/main/java/org/cryptomator/cryptofs/ConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/ConflictResolver.java index 38ccec01..46a38d27 100644 --- a/src/main/java/org/cryptomator/cryptofs/ConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/ConflictResolver.java @@ -122,7 +122,7 @@ private Path renameConflictingFile(Path canonicalPath, Path conflictingPath, Str } LOG.info("Moving conflicting file {} to {}", conflictingPath, alternativePath); Path resolved = Files.move(conflictingPath, alternativePath, StandardCopyOption.ATOMIC_MOVE); - longFileNameProvider.persistCachedIfDeflated(resolved); + longFileNameProvider.getCached(resolved).ifPresent(LongFileNameProvider.DeflatedFileName::persist); return resolved; } catch (AuthenticationFailedException e) { // not decryptable, no need to resolve any kind of conflict diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 4322b0ba..db4fac79 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -56,6 +56,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -304,7 +305,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute... attrs) throws // create dir if and only if the dirFile has been created right now (not if it has been created before): try { Files.createDirectories(ciphertextDir.path); - longFileNameProvider.persistCachedIfDeflated(ciphertextDirFile); + longFileNameProvider.getCached(ciphertextDirFile).ifPresent(LongFileNameProvider.DeflatedFileName::persist); } catch (IOException e) { // make sure there is no orphan dir file: Files.delete(ciphertextDirFile); @@ -355,7 +356,7 @@ private FileChannel newFileChannel(CryptoPath cleartextFilePath, EffectiveOpenOp } else { // might also throw FileAlreadyExists: FileChannel ch = openCryptoFiles.getOrCreate(ciphertextPath).newFileChannel(options); - longFileNameProvider.persistCachedIfDeflated(ciphertextPath); + longFileNameProvider.getCached(ciphertextPath).ifPresent(LongFileNameProvider.DeflatedFileName::persist); return ch; } } @@ -423,8 +424,10 @@ private void copySymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget, Path ciphertextSourceFile = cryptoPathMapper.getCiphertextFilePath(cleartextSource, CiphertextFileType.SYMLINK); Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.SYMLINK); CopyOption[] resolvedOptions = ArrayUtils.without(options, LinkOption.NOFOLLOW_LINKS).toArray(CopyOption[]::new); + Optional deflatedFileName = longFileNameProvider.getCached(ciphertextTargetFile); Files.copy(ciphertextSourceFile, ciphertextTargetFile, resolvedOptions); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile); + deflatedFileName.ifPresent(LongFileNameProvider.DeflatedFileName::persist); + } else { CryptoPath resolvedSource = symlinks.resolveRecursively(cleartextSource); CryptoPath resolvedTarget = symlinks.resolveRecursively(cleartextTarget); @@ -436,8 +439,9 @@ private void copySymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget, private void copyFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption[] options) throws IOException { Path ciphertextSourceFile = cryptoPathMapper.getCiphertextFilePath(cleartextSource, CiphertextFileType.FILE); Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.FILE); + Optional deflatedFileName = longFileNameProvider.getCached(ciphertextTargetFile); Files.copy(ciphertextSourceFile, ciphertextTargetFile, options); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile); + deflatedFileName.ifPresent(LongFileNameProvider.DeflatedFileName::persist); } private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption[] options) throws IOException { @@ -445,8 +449,9 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge Path ciphertextTargetDirFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.DIRECTORY); if (Files.notExists(ciphertextTargetDirFile)) { // create new: + Optional deflatedFileName = longFileNameProvider.getCached(ciphertextTargetDirFile); createDirectory(cleartextTarget); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetDirFile); + deflatedFileName.ifPresent(LongFileNameProvider.DeflatedFileName::persist); } else if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) { // keep existing (if empty): Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; @@ -525,7 +530,7 @@ private void moveSymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget, Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.SYMLINK); try (OpenCryptoFiles.TwoPhaseMove twoPhaseMove = openCryptoFiles.prepareMove(ciphertextSourceFile, ciphertextTargetFile)) { Files.move(ciphertextSourceFile, ciphertextTargetFile, options); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile); + longFileNameProvider.getCached(ciphertextTargetFile).ifPresent(LongFileNameProvider.DeflatedFileName::persist); twoPhaseMove.commit(); } } @@ -537,7 +542,7 @@ private void moveFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, Co Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.FILE); try (OpenCryptoFiles.TwoPhaseMove twoPhaseMove = openCryptoFiles.prepareMove(ciphertextSourceFile, ciphertextTargetFile)) { Files.move(ciphertextSourceFile, ciphertextTargetFile, options); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile); + longFileNameProvider.getCached(ciphertextTargetFile).ifPresent(LongFileNameProvider.DeflatedFileName::persist); twoPhaseMove.commit(); } } @@ -550,7 +555,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge if (!ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) { // try to move, don't replace: Files.move(ciphertextSourceDirFile, ciphertextTargetDirFile, options); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetDirFile); + longFileNameProvider.getCached(ciphertextTargetDirFile).ifPresent(LongFileNameProvider.DeflatedFileName::persist); } else if (ArrayUtils.contains(options, StandardCopyOption.ATOMIC_MOVE)) { // replace atomically (impossible): assert ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING); @@ -569,7 +574,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge Files.delete(ciphertextTargetDir); } Files.move(ciphertextSourceDirFile, ciphertextTargetDirFile, options); - longFileNameProvider.persistCachedIfDeflated(ciphertextTargetDirFile); + longFileNameProvider.getCached(ciphertextTargetDirFile).ifPresent(LongFileNameProvider.DeflatedFileName::persist); } dirIdProvider.move(ciphertextSourceDirFile, ciphertextTargetDirFile); cryptoPathMapper.invalidatePathMapping(cleartextSource); diff --git a/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java b/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java index 5dc965ec..a206b629 100644 --- a/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java @@ -13,13 +13,11 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.io.BaseEncoding; -import com.google.common.util.concurrent.UncheckedExecutionException; import org.cryptomator.cryptolib.common.MessageDigestSupplier; import javax.inject.Inject; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; @@ -27,6 +25,7 @@ import java.nio.file.StandardOpenOption; import java.time.Duration; import java.util.Arrays; +import java.util.Optional; import java.util.concurrent.ExecutionException; import static java.nio.charset.StandardCharsets.UTF_8; @@ -86,34 +85,51 @@ public String deflate(String longFileName) { return shortName; } - public void persistCachedIfDeflated(Path ciphertextFile) throws IOException { - String filename = ciphertextFile.getFileName().toString(); - if (isDeflated(filename)) { - persistCached(filename); - } + private Path resolveMetadataFile(String shortName) { + return metadataRoot.resolve(shortName.substring(0, 2)).resolve(shortName.substring(2, 4)).resolve(shortName); } - // visible for testing - void persistCached(String shortName) throws IOException { - readonlyFlag.assertWritable(); + public Optional getCached(Path ciphertextFile) { + String shortName = ciphertextFile.getFileName().toString(); String longName = longNames.getIfPresent(shortName); - if (longName == null) { - throw new IllegalStateException("Long name for " + shortName + " has not been shortened within the last " + MAX_CACHE_AGE); - } - Path file = resolveMetadataFile(shortName); - Path fileDir = file.getParent(); - assert fileDir != null : "resolveMetadataFile returned path to a file"; - Files.createDirectories(fileDir); - try (WritableByteChannel ch = Files.newByteChannel(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) { - ch.write(UTF_8.encode(longName)); - } catch (FileAlreadyExistsException e) { - // no-op: if the file already exists, we assume its content to be what we want (or we found a SHA1 collision ;-)) - assert Arrays.equals(Files.readAllBytes(file), longName.getBytes(UTF_8)); + if (longName != null) { + return Optional.of(new DeflatedFileName(shortName, longName)); + } else { + return Optional.empty(); } } - private Path resolveMetadataFile(String shortName) { - return metadataRoot.resolve(shortName.substring(0, 2)).resolve(shortName.substring(2, 4)).resolve(shortName); + public class DeflatedFileName { + + public final String shortName; + public final String longName; + + private DeflatedFileName(String shortName, String longName) { + this.shortName = shortName; + this.longName = longName; + } + + public void persist() { + readonlyFlag.assertWritable(); + try { + persistInternal(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void persistInternal() throws IOException { + Path file = resolveMetadataFile(shortName); + Path fileDir = file.getParent(); + assert fileDir != null : "resolveMetadataFile returned path to a file"; + Files.createDirectories(fileDir); + try (WritableByteChannel ch = Files.newByteChannel(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) { + ch.write(UTF_8.encode(longName)); + } catch (FileAlreadyExistsException e) { + // no-op: if the file already exists, we assume its content to be what we want (or we found a SHA1 collision ;-)) + assert Arrays.equals(Files.readAllBytes(file), longName.getBytes(UTF_8)); + } + } } } diff --git a/src/main/java/org/cryptomator/cryptofs/Symlinks.java b/src/main/java/org/cryptomator/cryptofs/Symlinks.java index 8af6bfcb..93b837e6 100644 --- a/src/main/java/org/cryptomator/cryptofs/Symlinks.java +++ b/src/main/java/org/cryptomator/cryptofs/Symlinks.java @@ -43,7 +43,7 @@ public void createSymbolicLink(CryptoPath cleartextPath, Path target, FileAttrib EffectiveOpenOptions openOptions = EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW), readonlyFlag); ByteBuffer content = UTF_8.encode(target.toString()); openCryptoFiles.writeCiphertextFile(ciphertextSymlinkFile, openOptions, content); - longFileNameProvider.persistCachedIfDeflated(ciphertextSymlinkFile); + longFileNameProvider.getCached(ciphertextSymlinkFile).ifPresent(LongFileNameProvider.DeflatedFileName::persist); } public CryptoPath readSymbolicLink(CryptoPath cleartextPath) throws IOException { diff --git a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java index 64352d65..de7828d2 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java @@ -54,7 +54,7 @@ public CryptoBasicFileAttributes(BasicFileAttributes delegate, CiphertextFileTyp } private static long getPlaintextFileSize(Path ciphertextPath, long size, Optional openCryptoFile, Cryptor cryptor) { - return openCryptoFile.map(OpenCryptoFile::size).orElseGet(() -> calculatePlaintextFileSize(ciphertextPath, size, cryptor)); + return openCryptoFile.flatMap(OpenCryptoFile::size).orElseGet(() -> calculatePlaintextFileSize(ciphertextPath, size, cryptor)); } private static long calculatePlaintextFileSize(Path ciphertextPath, long size, Cryptor cryptor) { diff --git a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java index 6da94530..af5d12f2 100644 --- a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java @@ -227,38 +227,58 @@ public MappedByteBuffer map(MapMode mode, long position, long size) { } @Override - public FileLock lock(long position, long size, boolean shared) throws IOException { + public FileLock lock(long pos, long size, boolean shared) throws IOException { assertOpen(); if (shared && !options.readable()) { throw new NonReadableChannelException(); // shared lock only available on readable channel } else if (!shared && !options.writable()) { throw new NonWritableChannelException(); // exclusive lock only available on writable channel } - long firstChunk = position / cryptor.fileContentCryptor().cleartextChunkSize(); - long lastChunk = firstChunk + size / cryptor.fileContentCryptor().cleartextChunkSize(); - long ciphertextPosition = cryptor.fileHeaderCryptor().headerSize() + firstChunk * cryptor.fileContentCryptor().ciphertextChunkSize(); - long ciphertextSize = (lastChunk - firstChunk + 1) * cryptor.fileContentCryptor().ciphertextChunkSize(); - FileLock ciphertextLock = ciphertextFileChannel.lock(ciphertextPosition, ciphertextSize, shared); - return new CleartextFileLock(this, ciphertextLock, position, size); + long beginOfFirstChunk = beginOfChunk(pos); + long beginOfLastChunk = beginOfChunk(pos + size); + final FileLock ciphertextLock; + if (beginOfFirstChunk == Long.MAX_VALUE || beginOfLastChunk == Long.MAX_VALUE) { + ciphertextLock = ciphertextFileChannel.lock(0l, Long.MAX_VALUE, shared); + } else { + long endOfLastChunk = beginOfLastChunk + cryptor.fileContentCryptor().ciphertextChunkSize(); + ciphertextLock = ciphertextFileChannel.lock(beginOfFirstChunk, endOfLastChunk - beginOfFirstChunk, shared); + } + return new CleartextFileLock(this, ciphertextLock, pos, size); } @Override - public FileLock tryLock(long position, long size, boolean shared) throws IOException { + public FileLock tryLock(long pos, long size, boolean shared) throws IOException { assertOpen(); if (shared && !options.readable()) { throw new NonReadableChannelException(); // shared lock only available on readable channel } else if (!shared && !options.writable()) { throw new NonWritableChannelException(); // exclusive lock only available on writable channel } - long firstChunk = position / cryptor.fileContentCryptor().cleartextChunkSize(); - long lastChunk = firstChunk + size / cryptor.fileContentCryptor().cleartextChunkSize(); - long ciphertextPosition = cryptor.fileHeaderCryptor().headerSize() + firstChunk * cryptor.fileContentCryptor().ciphertextChunkSize(); - long ciphertextSize = (lastChunk - firstChunk + 1) * cryptor.fileContentCryptor().ciphertextChunkSize(); - FileLock ciphertextLock = ciphertextFileChannel.tryLock(ciphertextPosition, ciphertextSize, shared); + long beginOfFirstChunk = beginOfChunk(pos); + long beginOfLastChunk = beginOfChunk(pos + size); + final FileLock ciphertextLock; + if (beginOfFirstChunk == Long.MAX_VALUE || beginOfLastChunk == Long.MAX_VALUE) { + ciphertextLock = ciphertextFileChannel.tryLock(0l, Long.MAX_VALUE, shared); + } else { + long endOfLastChunk = beginOfLastChunk + cryptor.fileContentCryptor().ciphertextChunkSize(); + ciphertextLock = ciphertextFileChannel.tryLock(beginOfFirstChunk, endOfLastChunk - beginOfFirstChunk, shared); + } if (ciphertextLock == null) { return null; } else { - return new CleartextFileLock(this, ciphertextLock, position, size); + return new CleartextFileLock(this, ciphertextLock, pos, size); + } + } + + // visible for testing + long beginOfChunk(long cleartextPos) { + long maxCiphertextPayloadSize = Long.MAX_VALUE - cryptor.fileHeaderCryptor().headerSize(); + long maxChunks = maxCiphertextPayloadSize / cryptor.fileContentCryptor().ciphertextChunkSize(); + long chunk = cleartextPos / cryptor.fileContentCryptor().cleartextChunkSize(); + if (chunk > maxChunks) { + return Long.MAX_VALUE; + } else { + return chunk * cryptor.fileContentCryptor().ciphertextChunkSize() + cryptor.fileHeaderCryptor().headerSize(); } } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java index fb54964e..1217d628 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.time.Instant; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; @@ -146,12 +147,15 @@ private void initFileSize(FileChannel ciphertextFileChannel) throws IOException } /** - * @return The size of the opened file - * @throws IllegalStateException If the OpenCryptoFile {@link OpenCryptoFiles#getOrCreate(Path) has been created} without {@link #newFileChannel(EffectiveOpenOptions) creating a file channel} next. + * @return The size of the opened file. Note that the filesize is unknown until a {@link #newFileChannel(EffectiveOpenOptions) file channel is opened}. In this case this method returns an empty optional. */ - public long size() { - Preconditions.checkState(fileSize.get() != -1l, "size must only be called after a FileChannel is created for this OpenCryptoFile"); - return fileSize.get(); + public Optional size() { + long val = fileSize.get(); + if (val == -1l) { + return Optional.empty(); + } else { + return Optional.of(val); + } } public FileTime getLastModifiedTime() { diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index 6defb010..5f4aae6c 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -110,6 +111,24 @@ public void afterEach() throws IOException { Files.deleteIfExists(file); } + @Test + public void testLockEmptyChannel() throws IOException { + try (FileChannel ch = FileChannel.open(file, CREATE, WRITE)) { + try (FileLock lock = ch.lock()) { + Assertions.assertNotNull(lock); + } + } + } + + @Test + public void testTryLockEmptyChannel() throws IOException { + try (FileChannel ch = FileChannel.open(file, CREATE, WRITE)) { + try (FileLock lock = ch.tryLock()) { + Assertions.assertNotNull(lock); + } + } + } + // tests https://github.com/cryptomator/cryptofs/issues/55 @Test public void testCreateNewFileSetsLastModifiedToNow() throws IOException, InterruptedException { diff --git a/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java b/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java index 1518f4dd..344d0f04 100644 --- a/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java @@ -18,10 +18,12 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.ReadOnlyFileSystemException; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; public class LongFileNameProviderTest { @@ -58,7 +60,7 @@ public void testDeflateAndInflate(@TempDir Path tmpPath) throws IOException { Assertions.assertEquals(orig, inflated1); Assertions.assertEquals(0, countFiles(tmpPath)); - prov1.persistCached(deflated); + prov1.getCached(Paths.get(deflated)).ifPresent(LongFileNameProvider.DeflatedFileName::persist); Assertions.assertEquals(1, countFiles(tmpPath)); LongFileNameProvider prov2 = new LongFileNameProvider(tmpPath, readonlyFlag); @@ -89,9 +91,14 @@ public void testDeflateMultipleTimes(@TempDir Path tmpPath) { public void testPerstistCachedFailsOnReadOnlyFileSystems(@TempDir Path tmpPath) { LongFileNameProvider prov = new LongFileNameProvider(tmpPath, readonlyFlag); + String orig = "longName"; + String shortened = prov.deflate(orig); + Optional cachedFileName = prov.getCached(Paths.get(shortened)); + + Assertions.assertTrue(cachedFileName.isPresent()); Mockito.doThrow(new ReadOnlyFileSystemException()).when(readonlyFlag).assertWritable(); Assertions.assertThrows(ReadOnlyFileSystemException.class, () -> { - prov.persistCached("whatever"); + cachedFileName.get().persist(); }); } diff --git a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java index 7e7fc89a..ac41721e 100644 --- a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java @@ -16,6 +16,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.InOrder; import org.mockito.Mockito; @@ -243,12 +245,21 @@ public void setup() { Assumptions.assumeTrue(fileContentCryptor.ciphertextChunkSize() == 110); } + @ParameterizedTest(name = "beginOfChunk({0}) == {1}") + @CsvSource({"0,50", "1,50", "99,50", "100,160", "199,160", "200,270", "300,380", "372,380", "399,380", "400,490", "4200,4670", "9223372036854775807,9223372036854775807"}) + @DisplayName("correctness of beginOfChunk()") + public void testBeginOfChunk(long cleaertextPos, long expectedCiphertextPos) { + long ciphertextPos = inTest.beginOfChunk(cleaertextPos); + + Assertions.assertEquals(expectedCiphertextPos, ciphertextPos); + } + @Test @DisplayName("unsuccessful tryLock()") public void testTryLockReturnsNullIfDelegateReturnsNull() throws IOException { when(ciphertextFileChannel.tryLock(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(null); - FileLock result = inTest.tryLock(380l, 4290l, true); + FileLock result = inTest.tryLock(372l, 3828l, true); Assertions.assertNull(result); } @@ -256,7 +267,7 @@ public void testTryLockReturnsNullIfDelegateReturnsNull() throws IOException { @Test @DisplayName("successful tryLock()") public void testTryLockReturnsCryptoFileLockWrappingDelegate() throws IOException { - when(ciphertextFileChannel.tryLock(380l, 4290l, true)).thenReturn(delegate); + when(ciphertextFileChannel.tryLock(380l, 4670l+110l-380l, true)).thenReturn(delegate); FileLock result = inTest.tryLock(372l, 3828l, true); @@ -272,7 +283,7 @@ public void testTryLockReturnsCryptoFileLockWrappingDelegate() throws IOExceptio @Test @DisplayName("successful lock()") public void testLockReturnsCryptoFileLockWrappingDelegate() throws IOException { - when(ciphertextFileChannel.lock(380l, 4290l, true)).thenReturn(delegate); + when(ciphertextFileChannel.lock(380l, 4670l+110l-380l, true)).thenReturn(delegate); FileLock result = inTest.lock(372l, 3828l, true); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java index 2332b748..f424d770 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java @@ -124,9 +124,7 @@ public void setup() throws IOException { @Order(0) @DisplayName("getting size fails before creating first file channel") public void testGetSizeBeforeCreatingFileChannel() { - Assertions.assertThrows(IllegalStateException.class, () -> { - openCryptoFile.size(); - }); + Assertions.assertFalse(openCryptoFile.size().isPresent()); } @Test @@ -143,7 +141,7 @@ public void createFileChannel() throws IOException { @Order(11) @DisplayName("getting size succeeds after creating first file channel") public void testGetSizeAfterCreatingFirstFileChannel() { - Assertions.assertEquals(0l, openCryptoFile.size()); + Assertions.assertEquals(0l, openCryptoFile.size().get()); } // related to https://github.com/cryptomator/cryptofs/issues/51 @@ -166,7 +164,7 @@ public void errorDuringCreationOfSecondChannel() { @Order(13) @DisplayName("getting size succeeds after creating second file channel") public void testGetSizeAfterCreatingSecondFileChannel() { - Assertions.assertEquals(0l, openCryptoFile.size()); + Assertions.assertEquals(0l, openCryptoFile.size().get()); }