package at.ac.ait.archistar.engine.metadata; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import at.ac.ait.archistar.backendserver.fragments.Fragment; import at.ac.ait.archistar.backendserver.fragments.RemoteFragment; import at.ac.ait.archistar.backendserver.storageinterface.StorageServer; import at.ac.ait.archistar.engine.crypto.ArchistarSMCIntegrator; import at.ac.ait.archistar.engine.dataobjects.FSObject; import at.ac.ait.archistar.engine.distributor.Distributor; import at.ac.ait.archistar.engine.distributor.ServerConfiguration; import at.archistar.crypto.CryptoEngine; import at.archistar.crypto.secretsharing.ReconstructionException; /** * The metadata service is responsible for storing all meta-information about * filesystem layout, versions, etc. * * @author Andreas Happe <andreashappe@snikt.net> */ public class SimpleMetadataService implements MetadataService { private Map<String, Set<Fragment>> database; private final Distributor distributor; private final ServerConfiguration servers; private final CryptoEngine crypto; private final Logger logger = LoggerFactory.getLogger(SimpleMetadataService.class); public SimpleMetadataService(ServerConfiguration servers, Distributor distributor, CryptoEngine crypto) { this.distributor = distributor; this.servers = servers; this.crypto = crypto; } private Set<Fragment> getNewDistributionSet() { HashSet<Fragment> distribution = new HashSet<>(); for (StorageServer s : this.servers.getOnlineStorageServers()) { distribution.add(new RemoteFragment(UUID.randomUUID().toString(), s)); } return distribution; } private Set<Fragment> getNewDistributionSet(String fragmentId) { HashSet<Fragment> distribution = new HashSet<>(); for (StorageServer s : this.servers.getOnlineStorageServers()) { distribution.add(new RemoteFragment(fragmentId, s)); } return distribution; } private byte[] serializeDatabase() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeInt(database.size()); for (Entry<String, Set<Fragment>> es : database.entrySet()) { oos.writeObject(es.getKey()); oos.writeInt(es.getValue().size()); for (Fragment f : es.getValue()) { oos.writeObject(f.getFragmentId()); oos.writeObject(f.getStorageServer().getId()); } } } return baos.toByteArray(); } catch (IOException e) { assert (false); } return new byte[0]; } @Override public int connect() { int result = distributor.connectServers(); /* get a new distribution set and set fragment-id to index */ Set<Fragment> index = getNewDistributionSet("index"); /* use crypto engine to retrieve data */ distributor.getFragmentSet(index); byte[] data; try { data = ArchistarSMCIntegrator.decrypt(this.crypto, index); } catch (ReconstructionException e) { logger.warn("error during decryption"); data = null; } /* now either rebuild database or create a new one */ if (data != null) { database = deserializeDatabase(data); } else { this.database = new HashMap<>(); synchronize(); } return result; } private Map<String, Set<Fragment>> deserializeDatabase(byte[] readBlob) { HashMap<String, Set<Fragment>> database = new HashMap<>(); try { ByteArrayInputStream door = new ByteArrayInputStream(readBlob); ObjectInputStream reader = new ObjectInputStream(door); int mappingCount = reader.readInt(); for (int i = 0; i < mappingCount; i++) { String filename = (String) reader.readObject(); int fragmentCount = reader.readInt(); HashSet<Fragment> map = new HashSet<>(); for (int j = 0; j < fragmentCount; j++) { String id = (String) reader.readObject(); String serverid = (String) reader.readObject(); map.add(new RemoteFragment(id, servers.getStorageServer(serverid))); } database.put(filename, map); } } catch (IOException | ClassNotFoundException e) { logger.warn("could not de-serialize database!"); } return database; } /** * clear our local cache/directory database */ @Override public int disconnect() { synchronize(); return 0; } @Override public Set<Fragment> getDistributionFor(String path) { Set<Fragment> distribution = database.get(path); /* if we have no mapping, create one */ if (distribution == null) { distribution = getNewDistributionSet(); database.put(path, distribution); synchronize(); } return distribution; } /** * as we are non-persistent we do not need any forced synchronization * * TODO: can we move that to the distributor? * */ @Override public int synchronize() { /* this should actually be a merge not a simple sync (for multi-user usage) */ Set<Fragment> index = getNewDistributionSet("index"); byte[] data = serializeDatabase(); ArchistarSMCIntegrator.encrypt(this.crypto, data, index); distributor.putFragmentSet(index); return 0; } @Override public int delete(FSObject obj) { if (this.database.containsKey(obj.getPath())) { this.database.remove(obj.getPath()); } synchronize(); return 0; } @Override public Map<String, String> stat(String path) { if (this.database.containsKey(path)) { return new HashMap<>(); } else { return null; } } @Override public Set<String> list(String path) { Set<String> initialResult = this.database.keySet(); Set<String> result = new HashSet<>(); for (String key : initialResult) { if (path != null) { if (key.startsWith(path)) { result.add(key); } } else { result.add(key); } } return result; } }