Added ClusterIOTest and improved some stuff.
This commit is contained in:
parent
4e401d98a5
commit
0c5cd95905
7
pom.xml
7
pom.xml
|
@ -36,6 +36,11 @@
|
|||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -60,7 +65,7 @@
|
|||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<version>5.7.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -2,21 +2,19 @@ package nl.andrewlalis.crystalkeep.control;
|
|||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import nl.andrewlalis.crystalkeep.io.ClusterIO;
|
||||
import nl.andrewlalis.crystalkeep.model.*;
|
||||
import nl.andrewlalis.crystalkeep.model.serialization.ClusterLoader;
|
||||
import nl.andrewlalis.crystalkeep.view.ClusterTreeItem;
|
||||
import nl.andrewlalis.crystalkeep.view.CrystalItemTreeCell;
|
||||
import nl.andrewlalis.crystalkeep.view.ShardTreeItem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class MainViewController implements ModelListener {
|
||||
private Model model;
|
||||
|
@ -65,42 +63,50 @@ public class MainViewController implements ModelListener {
|
|||
public void load() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Load a Cluster");
|
||||
chooser.setInitialDirectory(ClusterLoader.CLUSTER_PATH.toFile());
|
||||
chooser.setInitialDirectory(ClusterIO.CLUSTER_PATH.toFile());
|
||||
chooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Cluster Files", "cts"));
|
||||
File file = chooser.showOpenDialog(this.clusterTreeView.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
ClusterLoader loader = new ClusterLoader();
|
||||
var password = loader.promptPassword();
|
||||
if (password.isEmpty() || password.get().isEmpty()) return;
|
||||
ClusterIO loader = new ClusterIO();
|
||||
var password = this.promptPassword();
|
||||
Cluster cluster;
|
||||
try {
|
||||
var cluster = loader.load(file.toPath(), password.get());
|
||||
model.setActiveCluster(cluster);
|
||||
model.setActiveClusterPath(file.toPath());
|
||||
if (password.isEmpty() || password.get().isEmpty()) {
|
||||
cluster = loader.loadUnencrypted(file.toPath());
|
||||
} else {
|
||||
cluster = loader.load(file.toPath(), password.get());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
new Alert(Alert.AlertType.WARNING, "Could not load cluster.").showAndWait();
|
||||
return;
|
||||
}
|
||||
model.setActiveCluster(cluster);
|
||||
model.setActiveClusterPath(file.toPath());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void save() {
|
||||
if (model.getActiveCluster() == null) return;
|
||||
ClusterLoader loader = new ClusterLoader();
|
||||
ClusterIO loader = new ClusterIO();
|
||||
Path path = model.getActiveClusterPath();
|
||||
if (path == null) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Save Cluster");
|
||||
chooser.setInitialDirectory(ClusterLoader.CLUSTER_PATH.toFile());
|
||||
chooser.setInitialDirectory(ClusterIO.CLUSTER_PATH.toFile());
|
||||
chooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Cluster Files", "cts"));
|
||||
File file = chooser.showSaveDialog(this.clusterTreeView.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
path = file.toPath();
|
||||
}
|
||||
var password = loader.promptPassword();
|
||||
if (password.isEmpty() || password.get().isEmpty()) return;
|
||||
var password = this.promptPassword();
|
||||
try {
|
||||
new ClusterLoader().save(model.getActiveCluster(), path, password.get());
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
if (password.isEmpty() || password.get().isEmpty()) {
|
||||
loader.saveUnencrypted(model.getActiveCluster(), path);
|
||||
} else {
|
||||
loader.save(model.getActiveCluster(), path, password.get());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
var alert = new Alert(Alert.AlertType.ERROR, "Could not save cluster.");
|
||||
alert.showAndWait();
|
||||
|
@ -113,4 +119,23 @@ public class MainViewController implements ModelListener {
|
|||
model.setActiveCluster(c);
|
||||
model.setActiveClusterPath(null);
|
||||
}
|
||||
|
||||
private Optional<String> promptPassword() {
|
||||
Dialog<String> d = new Dialog<>();
|
||||
d.setTitle("Enter Password");
|
||||
d.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
|
||||
|
||||
PasswordField pwField = new PasswordField();
|
||||
VBox content = new VBox(10);
|
||||
content.setAlignment(Pos.CENTER);
|
||||
content.getChildren().addAll(new Label("Enter password"), pwField);
|
||||
d.getDialogPane().setContent(content);
|
||||
d.setResultConverter(param -> {
|
||||
if (param == ButtonType.OK) {
|
||||
return pwField.getText();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return d.showAndWait();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package nl.andrewlalis.crystalkeep.io;
|
||||
|
||||
import nl.andrewlalis.crystalkeep.io.serialization.ClusterSerializer;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* This class is responsible for reading and writing clusters to files, which
|
||||
* may be encrypted with a password-based AES-256 CBC.
|
||||
*
|
||||
* <p>
|
||||
* All saved files start with a single byte with value 1 to indicate that
|
||||
* the file is encrypted, or 0 if it is not encrypted. If encrypted, the
|
||||
* next 8 bytes will contain the salt that was used to encrypt the file.
|
||||
* Encrypted files will then contain a 16 byte initialization vector.
|
||||
* The remaining part of the file is simply the contents.
|
||||
* </p>
|
||||
*
|
||||
* @see nl.andrewlalis.crystalkeep.io.serialization.ClusterSerializer
|
||||
*/
|
||||
public class ClusterIO {
|
||||
public static final Path CLUSTER_PATH = Path.of("clusters");
|
||||
|
||||
private final SecureRandom random;
|
||||
|
||||
public ClusterIO() {
|
||||
try {
|
||||
this.random = SecureRandom.getInstanceStrong();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Could not initialize secure random.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an encrypted cluster from the given path, using the given password
|
||||
* as the secret key value.
|
||||
* @param path The path of the file to load from.
|
||||
* @param password The password to use to decrypt the contents.
|
||||
* @return The cluster that was loaded.
|
||||
* @throws Exception If the file could not be found, or could not be
|
||||
* decrypted and read properly.
|
||||
*/
|
||||
public Cluster load(Path path, String password) throws Exception {
|
||||
try (var is = Files.newInputStream(path)) {
|
||||
int encryptionFlag = is.read();
|
||||
if (encryptionFlag == 0) throw new IOException("File is not encrypted.");
|
||||
byte[] salt = is.readNBytes(8);
|
||||
byte[] iv = is.readNBytes(16);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, this.getSecretKey(password.toCharArray(), salt), new IvParameterSpec(iv));
|
||||
try (CipherInputStream cis = new CipherInputStream(is, cipher)) {
|
||||
return ClusterSerializer.readCluster(cis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a cluster to an encrypted file, using the given password as the
|
||||
* secret key.
|
||||
* @param cluster The cluster to save.
|
||||
* @param path The path to the file to save the cluster at.
|
||||
* @param password The password to use when saving.
|
||||
* @throws IOException If an error occurs when writing the file.
|
||||
* @throws GeneralSecurityException If we could not obtain a secret key.
|
||||
*/
|
||||
public void save(Cluster cluster, Path path, String password) throws IOException, GeneralSecurityException {
|
||||
Files.createDirectories(path.getParent());
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
byte[] iv = new byte[16];
|
||||
this.random.nextBytes(iv);
|
||||
byte[] salt = new byte[8];
|
||||
this.random.nextBytes(salt);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, this.getSecretKey(password.toCharArray(), salt), new IvParameterSpec(iv));
|
||||
try (OutputStream fos = Files.newOutputStream(path)) {
|
||||
fos.write(1);
|
||||
fos.write(salt);
|
||||
fos.write(iv);
|
||||
try (CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
|
||||
ClusterSerializer.writeCluster(cluster, cos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an unencrypted cluster file.
|
||||
* @param cluster The cluster to save.
|
||||
* @param path The file to save the cluster to.
|
||||
* @throws IOException If the file could not be saved.
|
||||
*/
|
||||
public void saveUnencrypted(Cluster cluster, Path path) throws IOException {
|
||||
Files.createDirectories(path.getParent());
|
||||
try (var os = Files.newOutputStream(path)) {
|
||||
os.write(0);
|
||||
ClusterSerializer.writeCluster(cluster, os);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an unencrypted cluster from a file.
|
||||
* @param path The path to load the cluster from.
|
||||
* @return The cluster that was loaded.
|
||||
* @throws IOException If the file could not be loaded.
|
||||
*/
|
||||
public Cluster loadUnencrypted(Path path) throws IOException {
|
||||
try (var is = Files.newInputStream(path)) {
|
||||
int encryptionFlag = is.read();
|
||||
if (encryptionFlag == 1) throw new IOException("File is encrypted.");
|
||||
return ClusterSerializer.readCluster(is);
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey getSecretKey(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
|
||||
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewlalis.crystalkeep.model.serialization;
|
||||
package nl.andrewlalis.crystalkeep.io.serialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewlalis.crystalkeep.model.serialization;
|
||||
package nl.andrewlalis.crystalkeep.io.serialization;
|
||||
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
|
@ -16,7 +16,7 @@ import java.util.HashMap;
|
|||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import static nl.andrewlalis.crystalkeep.model.serialization.ByteUtils.toInt;
|
||||
import static nl.andrewlalis.crystalkeep.io.serialization.ByteUtils.toInt;
|
||||
|
||||
/**
|
||||
* This serializer class offers some methods for reading and writing clusters
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewlalis.crystalkeep.model.serialization;
|
||||
package nl.andrewlalis.crystalkeep.io.serialization;
|
||||
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package nl.andrewlalis.crystalkeep.model.serialization;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ClusterLoader {
|
||||
public static final Path CLUSTER_PATH = Path.of("clusters");
|
||||
public static final Path DEFAULT_CLUSTER = CLUSTER_PATH.resolve("default.cts");
|
||||
private static final byte[] SALT = "zf9i78vy".getBytes(StandardCharsets.UTF_8);
|
||||
private static final byte[] IV = "Fafioje;a324fsde".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
public Optional<String> promptPassword() {
|
||||
Dialog<String> d = new Dialog<>();
|
||||
d.setTitle("Enter Password");
|
||||
d.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
|
||||
|
||||
PasswordField pwField = new PasswordField();
|
||||
VBox content = new VBox(10);
|
||||
content.setAlignment(Pos.CENTER);
|
||||
content.getChildren().addAll(new Label("Enter password"), pwField);
|
||||
d.getDialogPane().setContent(content);
|
||||
d.setResultConverter(param -> {
|
||||
if (param == ButtonType.OK) {
|
||||
return pwField.getText();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return d.showAndWait();
|
||||
}
|
||||
|
||||
public Cluster load(Path path, String password) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, this.getSecretKey(password), new IvParameterSpec(IV));
|
||||
byte[] raw = Files.readAllBytes(path);
|
||||
return ClusterSerializer.readCluster(new ByteArrayInputStream(cipher.doFinal(raw)));
|
||||
}
|
||||
|
||||
public void save(Cluster cluster, Path path, String password) throws IOException, GeneralSecurityException {
|
||||
Files.createDirectories(CLUSTER_PATH);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ClusterSerializer.writeCluster(cluster, bos);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, this.getSecretKey(password), new IvParameterSpec(IV));
|
||||
Files.write(path, cipher.doFinal(bos.toByteArray()));
|
||||
bos.close();
|
||||
}
|
||||
|
||||
private SecretKey getSecretKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), SALT, 65536, 256);
|
||||
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@ package nl.andrewlalis.crystalkeep.model.shards;
|
|||
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
import nl.andrewlalis.crystalkeep.model.ShardType;
|
||||
import nl.andrewlalis.crystalkeep.model.serialization.ByteUtils;
|
||||
import nl.andrewlalis.crystalkeep.model.serialization.ShardSerializer;
|
||||
import nl.andrewlalis.crystalkeep.io.serialization.ByteUtils;
|
||||
import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
|
@ -2,8 +2,8 @@ package nl.andrewlalis.crystalkeep.model.shards;
|
|||
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
import nl.andrewlalis.crystalkeep.model.ShardType;
|
||||
import nl.andrewlalis.crystalkeep.model.serialization.ByteUtils;
|
||||
import nl.andrewlalis.crystalkeep.model.serialization.ShardSerializer;
|
||||
import nl.andrewlalis.crystalkeep.io.serialization.ByteUtils;
|
||||
import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
|
@ -2,11 +2,11 @@ package nl.andrewlalis.crystalkeep.view.shard_details;
|
|||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
|
||||
|
||||
|
@ -47,13 +47,28 @@ public class LoginCredentialsViewModel extends ShardViewModel<LoginCredentialsSh
|
|||
passwordsContainer.getChildren().add(passwordField);
|
||||
passwordsContainer.getChildren().add(rawPasswordField);
|
||||
gp.add(passwordsContainer, 1, 1);
|
||||
|
||||
var passwordActionsPane = new HBox(5);
|
||||
var showPasswordCheckbox = new CheckBox("Show password");
|
||||
showPasswordCheckbox.setSelected(false);
|
||||
showPasswordCheckbox.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
passwordField.setVisible(!newValue);
|
||||
rawPasswordField.setVisible(newValue);
|
||||
});
|
||||
gp.add(showPasswordCheckbox, 1, 2);
|
||||
var copyPasswordButton = new Button("Copy to Clipboard");
|
||||
copyPasswordButton.setOnAction(event -> {
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(shard.getPassword());
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
var typePasswordButton = new Button("Auto Type");
|
||||
typePasswordButton.setOnAction(event -> {
|
||||
System.out.println("Not yet implemented.");
|
||||
});
|
||||
typePasswordButton.setDisable(true);
|
||||
passwordActionsPane.getChildren().addAll(showPasswordCheckbox, copyPasswordButton, typePasswordButton);
|
||||
|
||||
gp.add(passwordActionsPane, 1, 2);
|
||||
return gp;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package nl.andrewlalis.crystalkeep.io;
|
||||
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* Tests the functionality of the {@link ClusterIO} class in loading and saving
|
||||
* clusters.
|
||||
*/
|
||||
public class ClusterIOTest {
|
||||
public static List<Cluster> testClusters() {
|
||||
List<Cluster> clusters = new ArrayList<>();
|
||||
clusters.add(new Cluster("Root"));
|
||||
Cluster c1 = new Cluster("C1");
|
||||
c1.addShard(new TextShard("text", LocalDateTime.now(), "hello world!"));
|
||||
clusters.add(c1);
|
||||
Cluster c2 = new Cluster("C2");
|
||||
Cluster c2Nested = new Cluster("Nested");
|
||||
c2Nested.addShard(new LoginCredentialsShard("login", LocalDateTime.now(), "andrew", "pass"));
|
||||
c2Nested.addShard(new TextShard("data", LocalDateTime.now(), "test, 1, 2, 3"));
|
||||
c2.addCluster(c2Nested);
|
||||
c2.addShard(new TextShard("more_data", LocalDateTime.now(), "Antidisestablishmentarianism"));
|
||||
clusters.add(c2);
|
||||
return clusters;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("testClusters")
|
||||
public void testUnencryptedIO(Cluster cluster) throws IOException, GeneralSecurityException {
|
||||
Path file = Files.createTempFile(UUID.randomUUID().toString(), "cts");
|
||||
var io = new ClusterIO();
|
||||
// Test normal save and load.
|
||||
io.saveUnencrypted(cluster, file);
|
||||
Cluster loaded = io.loadUnencrypted(file);
|
||||
assertEquals(cluster, loaded);
|
||||
// Test attempting to load an encrypted file, which throws an IOException.
|
||||
io.save(cluster, file, "testpass");
|
||||
assertThrows(IOException.class, () -> io.loadUnencrypted(file));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("testClusters")
|
||||
public void testEncryptedIO(Cluster cluster) throws Exception {
|
||||
Path file = Files.createTempFile(UUID.randomUUID().toString(), "cts");
|
||||
var io = new ClusterIO();
|
||||
// Test normal save and load.
|
||||
io.save(cluster, file, "test");
|
||||
Cluster loaded = io.load(file, "test");
|
||||
assertEquals(cluster, loaded);
|
||||
// Test attempting to load an unencrypted file, which throws an IOException.
|
||||
io.saveUnencrypted(cluster, file);
|
||||
assertThrows(IOException.class, () -> io.load(file, "test"));
|
||||
// Test attempting to load an encrypted file with the wrong password. An exception is thrown when reading it.
|
||||
io.save(cluster, file, "other");
|
||||
assertThrows(Exception.class, () -> io.load(file, "not_password"));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewlalis.crystalkeep.model.serialization;
|
||||
package nl.andrewlalis.crystalkeep.io.serialization;
|
||||
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
|
@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||
* transforming key model components to and from byte arrays for storage.
|
||||
*/
|
||||
public class ClusterSerializerTest {
|
||||
private static List<Shard> testShardIOData() {
|
||||
public static List<Shard> testShardIOData() {
|
||||
return List.of(
|
||||
new TextShard("a", LocalDateTime.now(), "Hello world!"),
|
||||
new TextShard("Another", LocalDateTime.now(), "Testing"),
|
||||
|
@ -58,7 +58,7 @@ public class ClusterSerializerTest {
|
|||
assertArrayEquals(data, data2, "Serialized data from a shard should not change.");
|
||||
}
|
||||
|
||||
private static List<Cluster> testClusterIOData() {
|
||||
public static List<Cluster> testClusterIOData() {
|
||||
List<Cluster> clusters = new ArrayList<>();
|
||||
clusters.add(new Cluster("test"));
|
||||
|
Loading…
Reference in New Issue