package eu.tpmusielak.securephoto.container;
import java.io.*;
import java.util.Arrays;
import java.util.Random;
/**
* Created by IntelliJ IDEA.
* User: Tomasz P. Musielak
* Date: 15.02.12
* Time: 11:53
*/
public final class SPImageRoll implements Serializable, SPIFile {
private static final long serialVersionUID = 10;
public static final long NO_EXPIRY = -1;
public final static String DIGEST_ALGORITHM = "SHA-1";
public static final String DEFAULT_EXTENSION = "spr";
public static final int DIGEST_LENGTH = 20;
public class Header implements Serializable {
private final long expiryDate;
private final byte[] uniqueID;
private int frameCount;
private byte[] currentHash;
private byte[] signature;
public Header(byte[] uniqueID, long expiryDate) {
this.expiryDate = expiryDate;
this.uniqueID = uniqueID;
frameCount = 0;
currentHash = Arrays.copyOf(uniqueID, uniqueID.length);
signature = null;
}
public long getExpiryDate() {
return expiryDate;
}
public byte[] getUniqueID() {
return uniqueID;
}
public int getFrameCount() {
return frameCount;
}
public byte[] getCurrentHash() {
return currentHash;
}
}
private Header header;
private File rollFile;
public SPImageRoll(File file) {
Random random = new Random();
byte[] uniqueID = new byte[DIGEST_LENGTH];
random.nextBytes(uniqueID);
new SPImageRoll(file, uniqueID, NO_EXPIRY);
}
public SPImageRoll(File file, byte[] uniqueID) {
new SPImageRoll(file, uniqueID, NO_EXPIRY);
}
public SPImageRoll(File file, long expiryDate) {
Random random = new Random();
byte[] uniqueID = new byte[DIGEST_LENGTH];
random.nextBytes(uniqueID);
new SPImageRoll(file, uniqueID, expiryDate);
}
public SPImageRoll(File file, byte[] uniqueID, long expiryDate) {
rollFile = file;
header = new Header(uniqueID, expiryDate);
writeHeader();
}
// Constructor for reading SPImageRoll from file
private SPImageRoll(Header header, File file) {
this.header = header;
rollFile = file;
}
public static SPImageRoll fromFile(File file) throws IOException, ClassNotFoundException {
FileInputStream inputStream = new FileInputStream(file);
long fileLength = file.length();
ObjectInput objectInput = new ObjectInputStream(inputStream);
Header header = (Header) objectInput.readObject();
inputStream.close();
objectInput.close();
return new SPImageRoll(header, file);
}
private void writeHeader() {
writeImage(null);
}
private void writeImage(SPImage image) {
try {
RandomAccessFile file = new RandomAccessFile(rollFile, "rw");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutput objectOutput = new ObjectOutputStream(byteArrayOutputStream);
objectOutput.writeObject(header);
byte[] headerBytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
objectOutput.close();
file.seek(0); // Go to beginning
file.write(headerBytes); // Update header
// If writing image
if (image != null) {
file.seek(file.length()); // Go to EOF
file.write(image.toByteArray()); // Write new image
}
file.close();
// TODO: add exception handling
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public int addImage(SPImage image) {
header.currentHash = image.getFrameHash();
header.frameCount++;
writeImage(image);
return header.frameCount - 1;
}
public void sign(byte[] signature) {
header.signature = signature;
}
public int getFrameCount() {
return header.frameCount;
}
public byte[] getCurrentHash() {
return header.currentHash;
}
public SPImage getFrame(int index) {
SPImage frame = null;
if (index > (getFrameCount() - 1) || index < 0)
throw new IndexOutOfBoundsException();
try {
FileInputStream fileInputStream = new FileInputStream(rollFile);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Header rollHeader = (Header) objectInputStream.readObject(); // Read and discard the header
long skipped;
// Now the pointer is at the first frame header
SPImageHeader frameHeader = null;
for (int i = 0; i < index; i++) {
objectInputStream = new ObjectInputStream(fileInputStream); // KLUDGE
frameHeader = (SPImageHeader) objectInputStream.readObject(); // Read the SPImageHeader
long frameSize = frameHeader.getSize();
skipped = fileInputStream.skip(frameSize); // Skip the frame
if (skipped != frameSize)
throw new IOException("Could not skip the frame");
}
// Now we are at the header of the right frame
objectInputStream = new ObjectInputStream(fileInputStream); // KLUDGE
frameHeader = (SPImageHeader) objectInputStream.readObject(); // Reading and discarding the frame header
objectInputStream = new ObjectInputStream(fileInputStream); // KLUDGE
frame = (SPImage) objectInputStream.readObject(); // Reading the frame;
// TODO: Exception handling
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return frame;
}
public SPImage.VerificationStatus checkIntegrity(SPValidator validator) {
validator.log(String.format("Unique ID: %s", byteArrayToHex(header.uniqueID)));
validator.log(String.format("Images stored: %d", header.frameCount));
validator.log(String.format("Current hash: %s", byteArrayToHex(header.currentHash)));
if (header.signature == null) {
validator.log("No signature.");
} else {
validator.log("Signature present.");
}
if (header.frameCount == 0)
return SPImage.VerificationStatus.OK;
SPImage.VerificationStatus verificationStatus = SPImage.VerificationStatus.UNKNOWN;
SPImage.VerificationStatus[] frameStatus = new SPImage.VerificationStatus[header.frameCount];
validator.log("");
validator.log(String.format("Looking up SPRoll ID: %s", byteArrayToHex(header.uniqueID)));
SPRInfo sprInfo = validator.lookupSPR(header.uniqueID);
if (sprInfo != null) {
validator.log(sprInfo.toString());
} else {
validator.log("Rogue SecurePhoto Roll");
return SPImage.VerificationStatus.FAILED;
}
byte[] calculatedHash = header.uniqueID;
for (int i = 0; i < header.frameCount; i++) {
validator.log(String.format("Verifying frame %d of %d:", i + 1, header.frameCount));
SPImage frame = getFrame(i);
verificationStatus = frame.checkIntegrity(validator, calculatedHash);
switch (verificationStatus) {
case OK:
case UNKNOWN:
break;
case FAILED:
return SPImage.VerificationStatus.FAILED;
}
calculatedHash = frame.getFrameHash();
validator.log("");
frameStatus[i] = verificationStatus;
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for (SPImage.VerificationStatus status : frameStatus) {
switch (status) {
case OK:
sb.append("+");
break;
case FAILED:
sb.append("-");
break;
case UNKNOWN:
sb.append("?");
break;
}
sb.append(",");
}
sb.append("]");
validator.log("Roll validation status: " + sb.toString());
return verificationStatus;
}
private static String byteArrayToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
sb.append(Integer.toHexString(aByte & 0xFF).toUpperCase());
}
return sb.toString();
}
public Header getHeader() {
return header;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SPImageRoll \n");
sb.append(String.format("\n"));
// Date date = new Date(header.expiryDate);
sb.append(String.format("Unique ID: %s\n", byteArrayToHex(header.uniqueID)));
// sb.append(String.format("Expiry date: %s\n", date.toString()));
sb.append(String.format("Images stored: %d\n", header.frameCount));
sb.append(String.format("Current hash: %s\n", byteArrayToHex(header.currentHash)));
return sb.toString();
}
}