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();
+ }
+
}