public class FilesystemStorage implements StorageServer {

    private boolean connected = false;

    private final int internalBFTId;

    private final File baseFp;

    public FilesystemStorage(int bftId, File baseDir) {
        this.baseFp = baseDir;
        this.internalBFTId = bftId;
    }

    /* memory storage does not need any connect operation */
    @Override
    public int connect() {
        if (!baseFp.isDirectory() || !baseFp.canWrite()) {
            assert (false);
            return -1;
        } else {
            this.connected = true;
            return 0;
        }
    }

    @Override
    public int disconnect() {
        this.connected = false;
        return 0;
    }

    @Override
    public byte[] putBlob(String id, byte[] blob) throws DisconnectedException {

        validateOnline();

        /* store data */
        File new_file = new File(baseFp, id);
        try (FileOutputStream out = new FileOutputStream(new_file)) {
            out.write(blob);
        } catch (IOException e) {
            assert (false);
        }
        return blob;
    }

    private void validateOnline() throws DisconnectedException {
        if (!this.connected) {
            throw new DisconnectedException();
        }
    }

    private boolean checkExistenceOfId(String id) {
        /* now do a fp.list().contains?(filename) */
        String[] files = baseFp.list();
        for (String f : files) {
            if (f.equalsIgnoreCase(id)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public byte[] getBlob(String id) throws DisconnectedException {

        validateOnline();

        if (!checkExistenceOfId(id)) {
            return new byte[0];
        }

        /* receive data */
        File new_file = new File(baseFp, id);
        assert (new_file.exists());
        assert (new_file.canRead());

        try (FileInputStream in = new FileInputStream(new_file)) {
            return IOUtils.toByteArray(in);
        } catch (IOException ex) {
            assert (false);
        }

        return new byte[0];
    }

    @Override
    public boolean isConnected() {
        return this.connected;
    }

    @Override
    public int getFragmentCount() throws DisconnectedException {
        validateOnline();
        return baseFp.list().length;
    }

    @Override
    public String getId() {
        return "storage:///" + this.baseFp.getAbsolutePath();
    }

    @Override
    public int getBFTId() {
        return this.internalBFTId;
    }
} public interface StorageServer extends ExecutionHandler {

    /**
     * returns stored fragment count -- used for debug purposes
     * 
     * @return the stored fragment count
     * @throws DisconnectedException 
     */
    int getFragmentCount() throws DisconnectedException;

    /**
     * connect to storage backend
     * 
     * @return 0 if there was no error
     */
    int connect();

    /**
     * disconnect from storage backend
     * 
     * @return 0 if there was no error
     */
    int disconnect();

    /**
     * check if storage backend is connected
     * 
     * @return true if it is connected
     */
    boolean isConnected();

    /**
     * return storage id
     * 
     * @return the storage id
     */
    String getId();

    /**
     * return internal BFT id
     * 
     * TODO: this feels weird (as the storage server should not know about the
     * BFT id, maybe this is a relict from the time when BFT itself was not
     * implemented as a storage server -- need to investigate further, maybe
     * we can remove it
     * 
     * @return the internal bft id
     */
    int getBFTId();
} public class TestEngine extends Engine {

    public TestEngine(ServerConfiguration servers, MetadataService naming, Distributor distributor, CryptoEngine crypto) {
        super(servers, naming, distributor, crypto);
    }

    public int getNumberOfServers() {
        return servers.getOnlineStorageServerCount();
    }

    public Distributor getDistributor() {
        return this.distributor;
    }

} public interface FSObject {

    Map<String, String> getMetadata();

    String setMetaData(String key, String value);

    String getPath();
}; public class SimpleFile implements Serializable, FSObject {

    private static final long serialVersionUID = 961269599570217856L;
    
    private final String path;
    
    private final byte[] data;
    
    private final Map<String, String> metadata;

    public SimpleFile(String path, byte[] data, Map<String, String> metadata) {
        this.path = path;
        this.data = data.clone();
        this.metadata = metadata;
    }

    @Override
    public Map<String, String> getMetadata() {
        return this.metadata;
    }

    @Override
    public String setMetaData(String key, String value) {
        return this.metadata.put(key, value);
    }

    @SuppressFBWarnings("EI_EXPOSE_REP")
    public byte[] getData() {
        return this.data;
    }

    @Override
    public String getPath() {
        return this.path;
    }
} public class BFTDistributor implements Distributor {

    protected OzymandiasClient client;

    private boolean alreadyConnected = false;

    private int clientSequence = 0;

    private final int clientId = 0;

    private final int f = 1;

    private final ServerConfiguration config;

    public BFTDistributor(ServerConfiguration config, NioEventLoopGroup loopGroup) {
        this.config = config;
        this.client = new OzymandiasClient(config.getBFTServerNetworkPortMap(), f, loopGroup);
    }

    @Override
    public boolean putFragmentSet(Set<Fragment> fragments) {

        Map<Integer, ClientCommand> msg = new HashMap<Integer, ClientCommand>();

        for (Fragment f : fragments) {
            msg.put(f.getStorageServer().getBFTId(), new WriteCommand(clientId, clientSequence, f.getFragmentId(), f.getData()));
        }

        this.client.sendRoundtripMessage(msg);
        clientSequence++;

        return fragments.size() >= (2 * f + 1);
    }

    @Override
    public boolean getFragmentSet(Set<Fragment> fragments) {

        Map<Integer, ClientCommand> msg = new HashMap<>();

        for (Fragment f : fragments) {
            msg.put(f.getStorageServer().getBFTId(), new ReadCommand(clientId, clientSequence, f.getFragmentId()));
        }

        ClientResult result = this.client.sendRoundtripMessage(msg);

        /* merge fragment results */
        for (Fragment f : fragments) {
            int bftid = f.getStorageServer().getBFTId();
            if (result.containsDataForServer(bftid)) {
                f.setData(result.getDataForServer(bftid));
                f.setSynchronized(true);
            } else {
                f.setSynchronized(false);
            }
        }

        clientSequence++;

        return fragments.size() >= (2 * f + 1);
    }

    /* starts up virtual servers */
    @Override
    public int connectServers() {

        if (alreadyConnected == true) {
            return config.getOnlineStorageServerCount();
        }

        /* create and connect client */
        try {
            this.client.connect();
            alreadyConnected = true;
        } catch (Exception e) {
            e.printStackTrace();
            assert (false);
            return -1;
        }

        return config.getOnlineStorageServerCount();
    }

    @Override
    public int disconnectServers() {
        /* TODO: how to disconnect servers? */
        return 0;
    }
} public interface Distributor {

    boolean putFragmentSet(Set<Fragment> fragments);

    public boolean getFragmentSet(Set<Fragment> fragments);

    int connectServers();

    int disconnectServers();
} public class TestServerConfiguration extends ServerConfiguration {

    private HashSet<OzymandiasServer> servers = new HashSet<>();

    private final NioEventLoopGroup loopGroup;

    private Thread[] replicas = new Thread[0];

    public TestServerConfiguration(Set<StorageServer> servers) {
        super(servers);
        this.loopGroup = new NioEventLoopGroup(16);
    }

    public TestServerConfiguration(Set<StorageServer> servers, NioEventLoopGroup loopGroup) {
        super(servers);
        this.loopGroup = loopGroup;
    }

    /**
     * TODO: should we move the setupTestServer / teardownTestServer methods
     * into a subclass?
     */
    public void setupTestServer(int f) {

        int i = 0;
        int servercount = serverMapId.size();

        replicas = new Thread[servercount];
        servers = new HashSet<>();

        for (StorageServer s : serverMapId.values()) {
            s.connect();

            OzymandiasServer tmp = new OzymandiasServer(s.getBFTId(), getBFTServerNetworkPortMap(), f, s, loopGroup, loopGroup);
            servers.add(tmp);
            Thread thr = new Thread(tmp);
            replicas[i++] = thr;
            thr.start();
        }

        /* connect servers */
        for (OzymandiasServer o : this.servers) {
            try {
                o.connectServers();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void teardownTestServer() {
        for (OzymandiasServer s : servers) {
            s.shutdown();
        }

        for (Thread s : replicas) {
            try {
                s.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public HashMap<String, Integer> getStorageFragmentCounts() throws DisconnectedException {

        HashMap<String, Integer> result = new HashMap<>();

        for (StorageServer s : getOnlineStorageServers()) {
            result.put(s.getId(), s.getFragmentCount());
        }

        return result;
    }
} public interface MetadataService {

    /**
     * connect and bootstrap to servers
     */
    int connect();

    /**
     * disconnect from all servers
     */
    int disconnect();

    /**
     * returns a matching for a given path
     */
    Set<Fragment> getDistributionFor(String path);

    /**
     * makes sure that everything is synchronized
     */
    int synchronize();

    /**
     * List files in correspondence to a path expression
     */
    Set<String> list(String path);

    /**
     * removes an object from the index
     */
    int delete(FSObject obj);

    /**
     * retrieves stat information about an object
     */
    Map<String, String> stat(String path);
} 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;
    }
}
import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
import io.netty.channel.nio.NioEventLoopGroup;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import at.ac.ait.archistar.backendserver.storageinterface.FilesystemStorage;
import at.ac.ait.archistar.backendserver.storageinterface.StorageServer;
import at.ac.ait.archistar.engine.TestEngine;
import at.ac.ait.archistar.engine.dataobjects.FSObject;
import at.ac.ait.archistar.engine.dataobjects.SimpleFile;
import at.ac.ait.archistar.engine.distributor.BFTDistributor;
import at.ac.ait.archistar.engine.distributor.Distributor;
import at.ac.ait.archistar.engine.distributor.TestServerConfiguration;
import at.ac.ait.archistar.engine.metadata.MetadataService;
import at.ac.ait.archistar.engine.metadata.SimpleMetadataService;
import at.archistar.crypto.CryptoEngine;
import at.archistar.crypto.RabinBenOrEngine;
import at.archistar.crypto.secretsharing.ReconstructionException;
import at.archistar.crypto.secretsharing.WeakSecurityException;
import at.archistar.crypto.random.FakeRandomSource;
import at.archistar.crypto.random.RandomSource;
import java.security.NoSuchAlgorithmException;
package at.ac.ait.archistar.integration;






public class EncryptedFileSystemTest extends AbstractIntegrationTest {

    private static Set<StorageServer> createNewServers() {
        File baseDir = new File("/tmp/test-encrypted-filesystem/" + UUID.randomUUID() + "/");
        baseDir.mkdirs();

        File dir1 = new File(baseDir, "1");
        dir1.mkdir();
        File dir2 = new File(baseDir, "2");
        dir2.mkdir();
        File dir3 = new File(baseDir, "3");
        dir3.mkdir();
        File dir4 = new File(baseDir, "4");
        dir4.mkdir();

        HashSet<StorageServer> servers = new HashSet<>();
        servers.add(new FilesystemStorage(0, dir1));