package freenet.crypt;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
import freenet.support.HexUtil;
import freenet.support.Logger;
public class HashResult implements Comparable<HashResult>, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/** The type of hash. */
public final HashType type;
/** The result of the hash. Immutable. */
private final byte[] result;
/** Cached HashType.values(). Never modify or pass this array to outside code! */
private static final HashType[] HashType_values = HashType.values();
public HashResult(HashType hashType, byte[] bs) {
this.type = hashType;
this.result = bs;
assert(bs.length == type.hashLength);
}
protected HashResult() {
// For serialization.
type = null;
result = null;
}
public static HashResult[] readHashes(DataInputStream dis) throws IOException {
int bitmask = dis.readInt();
if(bitmask == 0) return null;
int count = 0;
for(HashType h : HashType_values) {
if((bitmask & h.bitmask) == h.bitmask) {
count++;
}
}
HashResult[] results = new HashResult[count];
int x = 0;
for(HashType h : HashType_values) {
if((bitmask & h.bitmask) == h.bitmask) {
results[x++] = HashResult.readFrom(h, dis);
}
}
return results;
}
private static HashResult readFrom(HashType h, DataInputStream dis) throws IOException {
byte[] buf = new byte[h.hashLength];
dis.readFully(buf);
return new HashResult(h, buf);
}
public static void write(HashResult[] hashes, DataOutputStream dos) throws IOException {
if(hashes == null) hashes = new HashResult[0];
int bitmask = 0;
for(HashResult hash : hashes)
bitmask |= hash.type.bitmask;
dos.writeInt(bitmask);
Arrays.sort(hashes);
HashType prev = null;
for(HashResult h : hashes) {
if(prev == h.type || (prev != null && prev.bitmask == h.type.bitmask)) throw new IllegalArgumentException("Multiple hashes of the same type!");
prev = h.type;
}
for(HashResult h : hashes)
h.writeTo(dos);
}
public void writeTo(OutputStream dos) throws IOException {
// Any given hash type has a fixed hash length, so just push the bytes.
dos.write(result);
}
@Override
public int compareTo(HashResult h) {
if(type.bitmask == h.type.bitmask) return 0;
if(type.bitmask > h.type.bitmask) return 1;
/* else if(type.bitmask < h.type.bitmask) */ return -1;
}
public static long makeBitmask(HashResult[] hashes) {
long l = 0;
for(HashResult hash : hashes)
l |= hash.type.bitmask;
return l;
}
public static boolean strictEquals(HashResult[] results, HashResult[] hashes) {
if(results.length != hashes.length) {
Logger.error(HashResult.class, "Hashes not equal: "+results.length+" hashes vs "+hashes.length+" hashes");
return false;
}
for(int i=0;i<results.length;i++) {
if(results[i].type != hashes[i].type) {
// FIXME Db4o kludge
if(HashType.valueOf(results[i].type.name()) != HashType.valueOf(hashes[i].type.name())) {
Logger.error(HashResult.class, "Hashes not the same type: "+results[i].type.name()+" vs "+hashes[i].type.name());
return false;
}
}
if(!Arrays.equals(results[i].result, hashes[i].result)) {
Logger.error(HashResult.class, "Hash "+results[i].type.name()+" not equal");
return false;
}
}
return true;
}
public static boolean contains(HashResult[] hashes, HashType type) {
for(HashResult res : hashes)
if(res.type == type || type.name().equals(res.type.name()))
return true;
return false;
}
public static byte[] get(HashResult[] hashes, HashType type) {
for(HashResult res : hashes)
if(res.type == type || type.name().equals(res.type.name()))
return res.result;
return null;
}
public static HashResult[] copy(HashResult[] hashes) {
if(hashes == null) return null;
HashResult[] out = new HashResult[hashes.length];
for(int i=0;i<hashes.length;i++) {
out[i] = hashes[i].clone();
}
return out;
}
@Override
public HashResult clone() {
try {
return (HashResult) super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
public String hashAsHex() {
return HexUtil.bytesToHex(result);
}
}