package peergos.shared.corenode; import peergos.shared.*; import peergos.shared.cbor.*; import peergos.shared.crypto.asymmetric.*; import peergos.shared.crypto.random.*; import peergos.shared.user.*; import peergos.shared.user.fs.*; import java.io.*; import java.util.*; import java.util.concurrent.*; public class TofuCoreNode implements CoreNode { public static final String KEY_STORE_NAME = ".keystore"; private final CoreNode source; private final TofuKeyStore tofu; private UserContext context; public TofuCoreNode(CoreNode source, TofuKeyStore tofu) { this.source = source; this.tofu = tofu; } public void setContext(UserContext context) { this.context = context; } private static String getStorePath(String username) { return "/" + username + "/" + KEY_STORE_NAME; } public static CompletableFuture<TofuKeyStore> load(String username, TrieNode root, NetworkAccess network, SafeRandom random) { if (username == null) return CompletableFuture.completedFuture(new TofuKeyStore()); return root.getByPath(getStorePath(username), network).thenCompose(fileOpt -> { if (! fileOpt.isPresent()) return CompletableFuture.completedFuture(new TofuKeyStore()); return fileOpt.get().getInputStream(network, random, x -> {}).thenCompose(reader -> { byte[] storeData = new byte[(int) fileOpt.get().getSize()]; return reader.readIntoArray(storeData, 0, storeData.length) .thenApply(x -> TofuKeyStore.fromCbor(CborObject.fromByteArray(storeData))); }); }); } private CompletableFuture<Boolean> commit() { return context.getUserRoot() .thenCompose(home -> { byte[] data = tofu.serialize(); AsyncReader.ArrayBacked dataReader = new AsyncReader.ArrayBacked(data); return home.uploadFile(KEY_STORE_NAME, dataReader, true, (long) data.length, context.network, context.crypto.random, x-> {}, context.fragmenter()); }); } public CompletableFuture<Boolean> updateUser(String username) { return source.getChain(username).thenCompose(chain -> { tofu.updateChain(username, chain); return commit(); }); } @Override public CompletableFuture<Optional<PublicSigningKey>> getPublicKey(String username) { Optional<PublicSigningKey> local = tofu.getPublicKey(username); if (local.isPresent()) return CompletableFuture.completedFuture(local); return source.getChain(username) .thenCompose(chain -> { tofu.updateChain(username, chain); return commit().thenApply(x -> tofu.getPublicKey(username)); }); } @Override public CompletableFuture<String> getUsername(PublicSigningKey key) { Optional<String> local = tofu.getUsername(key); if (local.isPresent()) return CompletableFuture.completedFuture(local.get()); return source.getUsername(key) .thenCompose(username -> { return source.getChain(username).thenCompose(chain -> { tofu.updateChain(username, chain); return commit().thenApply(x -> username); }); }); } @Override public CompletableFuture<List<UserPublicKeyLink>> getChain(String username) { List<UserPublicKeyLink> localChain = tofu.getChain(username); if (! localChain.isEmpty()) return CompletableFuture.completedFuture(localChain); return source.getChain(username) .thenCompose(chain -> { tofu.updateChain(username, chain); return commit().thenApply(x -> tofu.getChain(username)); }); } @Override public CompletableFuture<Boolean> updateChain(String username, List<UserPublicKeyLink> chain) { tofu.updateChain(username, chain); return commit().thenCompose(x -> source.updateChain(username, chain)); } @Override public CompletableFuture<List<String>> getUsernames(String prefix) { return source.getUsernames(prefix); } @Override public CompletableFuture<Boolean> followRequest(PublicSigningKey target, byte[] encryptedPermission) { return source.followRequest(target, encryptedPermission); } @Override public CompletableFuture<byte[]> getFollowRequests(PublicSigningKey owner) { return source.getFollowRequests(owner); } @Override public CompletableFuture<Boolean> removeFollowRequest(PublicSigningKey owner, byte[] data) { return source.removeFollowRequest(owner, data); } @Override public void close() throws IOException {} }