package peergos.shared.user.fs; import jsinterop.annotations.*; import peergos.shared.*; import peergos.shared.crypto.*; import peergos.shared.crypto.asymmetric.*; import peergos.shared.crypto.random.*; import peergos.shared.crypto.symmetric.*; import peergos.shared.user.*; import peergos.shared.util.*; import java.io.*; import java.time.*; import java.util.*; import java.util.concurrent.*; import java.util.stream.*; public class FileUploader implements AutoCloseable { private final String name; private final long offset, length; private final FileProperties props; private final SymmetricKey baseKey, metaKey; private final long nchunks; private final Location parentLocation; private final SymmetricKey parentparentKey; private final ProgressConsumer<Long> monitor; private final Fragmenter fragmenter; private final AsyncReader reader; // resettable input stream @JsConstructor public FileUploader(String name, AsyncReader fileData, int offsetHi, int offsetLow, int lengthHi, int lengthLow, SymmetricKey baseKey, SymmetricKey metaKey, Location parentLocation, SymmetricKey parentparentKey, ProgressConsumer<Long> monitor, FileProperties fileProperties, Fragmenter fragmenter) { long length = lengthLow + ((lengthHi & 0xFFFFFFFFL) << 32); if (fileProperties == null) this.props = new FileProperties(name, length, LocalDateTime.now(), false, Optional.empty()); else this.props = fileProperties; if (baseKey == null) baseKey = SymmetricKey.random(); this.fragmenter = fragmenter; long offset = offsetLow + ((offsetHi & 0xFFFFFFFFL) << 32); // Process and upload chunk by chunk to avoid running out of RAM, in reverse order to build linked list this.nchunks = length > 0 ? (length + Chunk.MAX_SIZE - 1) / Chunk.MAX_SIZE : 1; this.name = name; this.offset = offset; this.length = length; this.reader = fileData; this.baseKey = baseKey; this.metaKey = metaKey; this.parentLocation = parentLocation; this.parentparentKey = parentparentKey; this.monitor = monitor; } public FileUploader(String name, AsyncReader fileData, long offset, long length, SymmetricKey baseKey, SymmetricKey metaKey, Location parentLocation, SymmetricKey parentparentKey, ProgressConsumer<Long> monitor, FileProperties fileProperties, Fragmenter fragmenter) { this(name, fileData, (int)(offset >> 32), (int) offset, (int) (length >> 32), (int) length, baseKey, metaKey, parentLocation, parentparentKey, monitor, fileProperties, fragmenter); } public CompletableFuture<Location> uploadChunk(NetworkAccess network, SafeRandom random, PublicSigningKey owner, SigningKeyPair writer, long chunkIndex, Location currentLocation, ProgressConsumer<Long> monitor) { System.out.println("uploading chunk: "+chunkIndex + " of "+name); long position = chunkIndex * Chunk.MAX_SIZE; long fileLength = length; boolean isLastChunk = fileLength < position + Chunk.MAX_SIZE; int length = isLastChunk ? (int)(fileLength - position) : Chunk.MAX_SIZE; byte[] data = new byte[length]; return reader.readIntoArray(data, 0, data.length).thenCompose(b -> { byte[] nonce = random.randomBytes(TweetNaCl.SECRETBOX_NONCE_BYTES); Chunk chunk = new Chunk(data, metaKey, currentLocation.getMapKey(), nonce); LocatedChunk locatedChunk = new LocatedChunk(new Location(owner, writer.publicSigningKey, chunk.mapKey()), chunk); byte[] mapKey = random.randomBytes(32); Location nextLocation = new Location(owner, writer.publicSigningKey, mapKey); return uploadChunk(writer, props, parentLocation, parentparentKey, baseKey, locatedChunk, fragmenter, nextLocation, network, monitor).thenApply(c -> nextLocation); }); } public CompletableFuture<Location> upload(NetworkAccess network, SafeRandom random, PublicSigningKey owner, SigningKeyPair writer, Location currentChunk) { long t1 = System.currentTimeMillis(); Location originalChunk = currentChunk; List<Integer> input = IntStream.range(0, (int) nchunks).mapToObj(i -> Integer.valueOf(i)).collect(Collectors.toList()); return Futures.reduceAll(input, currentChunk, (loc, i) -> uploadChunk(network, random, owner, writer, i, loc, monitor), (a, b) -> b) .thenApply(loc -> { System.out.println("File encryption, erasure coding and upload took: " +(System.currentTimeMillis()-t1) + " mS"); return originalChunk; }); } public static CompletableFuture<Boolean> uploadChunk(SigningKeyPair writer, FileProperties props, Location parentLocation, SymmetricKey parentparentKey, SymmetricKey baseKey, LocatedChunk chunk, Fragmenter fragmenter, Location nextChunkLocation, NetworkAccess network, ProgressConsumer<Long> monitor) { EncryptedChunk encryptedChunk = chunk.chunk.encrypt(); List<Fragment> fragments = encryptedChunk.generateFragments(fragmenter); System.out.println(StringUtils.format("Uploading chunk with %d fragments\n", fragments.size())); SymmetricKey chunkKey = chunk.chunk.key(); byte[] nextLocationNonce = chunkKey.createNonce(); byte[] nextLocation = nextChunkLocation.encrypt(chunkKey, nextLocationNonce); CipherText encryptedNextChunkLocation = new CipherText(nextLocationNonce, nextLocation); return network.uploadFragments(fragments, chunk.location.owner, monitor, fragmenter.storageIncreaseFactor()).thenCompose(hashes -> { FileRetriever retriever = new EncryptedChunkRetriever(chunk.chunk.nonce(), encryptedChunk.getAuth(), hashes, Optional.of(encryptedNextChunkLocation), fragmenter); FileAccess metaBlob = FileAccess.create(baseKey, chunkKey, props, retriever, parentLocation, parentparentKey); return network.uploadChunk(metaBlob, new Location(chunk.location.owner, writer.publicSigningKey, chunk.chunk.mapKey()), writer); }); } public void close() throws IOException { reader.close(); } }