diff --git a/pom.xml b/pom.xml index 33a871bb..7e4f1229 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 1.3.0 + 1.3.1 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/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index b15be7e8..e311fe21 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -15,6 +15,7 @@ import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.AbstractMap; +import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.Map; @@ -204,7 +205,11 @@ public Builder withPassphrase(CharSequence passphrase) { return this; } - public Builder withFlags(Set flags) { + public Builder withFlags(FileSystemFlags... flags) { + return withFlags(asList(flags)); + } + + public Builder withFlags(Collection flags) { this.flags.clear(); this.flags.addAll(flags); return this; diff --git a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java index d22dad86..29c3ee16 100644 --- a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java +++ b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java @@ -69,11 +69,11 @@ public synchronized int read(ByteBuffer dst, long position) throws IOException { int payloadSize = cryptor.fileContentCryptor().cleartextChunkSize(); while (dst.hasRemaining()) { long pos = position + read; - long chunkIndex = pos / payloadSize; - int offset = (int) pos % payloadSize; - int len = min(dst.remaining(), payloadSize - offset); + long chunkIndex = pos / payloadSize; // floor by int-truncation + int offsetInChunk = (int) (pos % payloadSize); // known to fit in int, because payloadSize is int + int len = min(dst.remaining(), payloadSize - offsetInChunk); // known to fit in int, because second argument is int final ChunkData chunkData = chunkCache.get(chunkIndex); - chunkData.copyDataStartingAt(offset).to(dst); + chunkData.copyDataStartingAt(offsetInChunk).to(dst); read += len; } dst.limit(origLimit); @@ -138,7 +138,7 @@ public synchronized void truncate(long size) throws IOException { if (originalSize > size) { int cleartextChunkSize = cryptor.fileContentCryptor().cleartextChunkSize(); long indexOfLastChunk = (size + cleartextChunkSize - 1) / cleartextChunkSize - 1; - int sizeOfIncompleteChunk = (int) (size % cleartextChunkSize); + int sizeOfIncompleteChunk = (int) (size % cleartextChunkSize); // known to fit in int, because cleartextChunkSize is int if (sizeOfIncompleteChunk > 0) { chunkCache.get(indexOfLastChunk).truncate(sizeOfIncompleteChunk); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index b8b084c4..06809a26 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -37,7 +37,6 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; -import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -206,7 +205,7 @@ public void testNoImplicitInitialization() throws IOException { URI uri = CryptoFileSystemUri.create(pathToVault); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // - .withFlags(EnumSet.noneOf(FileSystemFlags.class)) // + .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // .withPassphrase("asd") // .build(); @@ -229,7 +228,7 @@ public void testImplicitInitialization() throws IOException { URI uri = CryptoFileSystemUri.create(pathToVault); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // - .withFlags(EnumSet.of(FileSystemFlags.INIT_IMPLICITLY)) // + .withFlags(FileSystemFlags.INIT_IMPLICITLY) // .withMasterkeyFilename("masterkey.cryptomator") // .withPassphrase("asd") // .build(); diff --git a/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java index e514e685..24bb685c 100644 --- a/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java +++ b/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java @@ -7,13 +7,17 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; +import java.nio.file.StandardOpenOption; +import java.util.EnumSet; import java.util.concurrent.atomic.AtomicLong; import org.cryptomator.cryptofs.OpenCounter.OpenState; import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.FileContentCryptor; import org.cryptomator.cryptolib.api.FileHeader; import org.junit.Rule; import org.junit.Test; @@ -21,6 +25,8 @@ import org.junit.experimental.theories.Theory; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -108,4 +114,68 @@ public void testCloseDelegatesToCryptoFileChannelFactory() throws IOException { verify(cryptoFileChannelFactory).close(); } + @Test + public void testRead() throws IOException { + int cleartextChunkSize = 1000; // 1 kb per chunk + ByteBuffer buf = ByteBuffer.allocate(10); + size.set(10_000_000_000l); // 10 gb total file size + + FileContentCryptor fileContentCryptor = Mockito.mock(FileContentCryptor.class); + when(cryptor.fileContentCryptor()).thenReturn(fileContentCryptor); + when(fileContentCryptor.cleartextChunkSize()).thenReturn(cleartextChunkSize); + when(chunkCache.get(Mockito.anyLong())).then(invocation -> { + return ChunkData.wrap(ByteBuffer.allocate(cleartextChunkSize)); + }); + + // A read from frist chunk: + buf.clear(); + inTest.read(buf, 0); + + // B read from second and third chunk: + buf.clear(); + inTest.read(buf, 1999); + + // C read from position > maxint + buf.clear(); + inTest.read(buf, 5_000_000_000l); + + InOrder inOrder = Mockito.inOrder(chunkCache, chunkCache, chunkCache, chunkCache); + inOrder.verify(chunkCache).get(0l); // A + inOrder.verify(chunkCache).get(1l); // B + inOrder.verify(chunkCache).get(2l); // B + inOrder.verify(chunkCache).get(5_000_000l); // C + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testWrite() throws IOException { + int cleartextChunkSize = 1000; // 1 kb per chunk + size.set(10_000_000_000l); // 10 gb total file size + + FileContentCryptor fileContentCryptor = Mockito.mock(FileContentCryptor.class); + when(cryptor.fileContentCryptor()).thenReturn(fileContentCryptor); + when(fileContentCryptor.cleartextChunkSize()).thenReturn(cleartextChunkSize); + when(chunkCache.get(Mockito.anyLong())).then(invocation -> { + return ChunkData.wrap(ByteBuffer.allocate(cleartextChunkSize)); + }); + + // A change 10 bytes inside first chunk: + ByteBuffer buf1 = ByteBuffer.allocate(10); + inTest.write(EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.WRITE)), buf1, 0); + + // B change complete second chunk: + ByteBuffer buf2 = ByteBuffer.allocate(1000); + inTest.write(EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.WRITE)), buf2, 1000); + + // C change complete chunk at position > maxint: + ByteBuffer buf3 = ByteBuffer.allocate(1000); + inTest.write(EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.WRITE)), buf3, 5_000_000_000l); + + InOrder inOrder = Mockito.inOrder(chunkCache, chunkCache, chunkCache); + inOrder.verify(chunkCache).get(0l); // A + inOrder.verify(chunkCache).set(Mockito.eq(1l), Mockito.any()); // B + inOrder.verify(chunkCache).set(Mockito.eq(5_000_000l), Mockito.any()); // C + inOrder.verifyNoMoreInteractions(); + } + }