package freenet.client.async; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.Serializable; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import freenet.keys.ClientCHK; import freenet.keys.NodeCHK; import freenet.support.Logger; import freenet.support.Fields; /** Contains the keys for a splitfile segment, in an efficient compressed form. These are * not changed, so the object never needs to be stored once created; this is good as it is * fairly large. SplitFileFetcherSegment keeps the data on which keys have been used * separately and passes it in. * * We will auto-upgrade SplitFileFetcherSegment's to use SplitFileSegmentKeys in the caller. * * @author toad */ public class SplitFileSegmentKeys implements Cloneable, Serializable { private static final long serialVersionUID = 1L; public final int dataBlocks; public final int checkBlocks; /** Modern splitfiles have a common decrypt key */ public final byte[] commonDecryptKey; /** Modern splitfiles have common extra bytes */ public final byte[] commonExtraBytes; /** Routing keys. */ public final byte[] routingKeys; /** Individual per-block decryption keys. Only on older splitfiles. */ public final byte[] decryptKeys; /** Individual per-block extra bytes. */ public final byte[] extraBytesForKeys; static final int EXTRA_BYTES_LENGTH = ClientCHK.EXTRA_LENGTH; // Bare constructor for Metadata. It will read the actual keys later. public SplitFileSegmentKeys(int blocksPerSegment, int checkBlocksPerSegment, byte[] splitfileSingleCryptoKey, byte splitfileSingleCryptoAlgorithm) { this.dataBlocks = blocksPerSegment; this.checkBlocks = checkBlocksPerSegment; routingKeys = new byte[NodeCHK.KEY_LENGTH * (dataBlocks + checkBlocks)]; if(splitfileSingleCryptoKey != null) { commonDecryptKey = splitfileSingleCryptoKey; commonExtraBytes = ClientCHK.getExtra(splitfileSingleCryptoAlgorithm, (short)-1, false); decryptKeys = null; extraBytesForKeys = null; } else { commonDecryptKey = null; commonExtraBytes = null; decryptKeys = new byte[ClientCHK.CRYPTO_KEY_LENGTH * (dataBlocks + checkBlocks)]; extraBytesForKeys = new byte[EXTRA_BYTES_LENGTH * (dataBlocks + checkBlocks)]; } } protected SplitFileSegmentKeys() { // For serialization. dataBlocks = 0; checkBlocks = 0; commonDecryptKey = null; commonExtraBytes = null; routingKeys = null; decryptKeys = null; extraBytesForKeys = null; } public int getBlockNumber(ClientCHK key, boolean[] ignoreSlots) { byte[] rkey = key.getRoutingKey(); byte[] ckey = null; byte[] extra = null; int x = 0; for(int i=0;i<(dataBlocks + checkBlocks);i++) { int oldX = x; x += NodeCHK.KEY_LENGTH; if(ignoreSlots != null && ignoreSlots[i]) { continue; } if(!Fields.byteArrayEqual(routingKeys, rkey, oldX, 0, NodeCHK.KEY_LENGTH)) continue; if(ckey == null) ckey = key.getCryptoKey(); assert(ClientCHK.CRYPTO_KEY_LENGTH == NodeCHK.KEY_LENGTH); // FIXME USE THE RIGHT CONSTANT, DONT ASSUME THE TWO LENGTHS ARE THE SAME if(commonDecryptKey != null) { if(!Arrays.equals(commonDecryptKey, ckey)) continue; } else { if(!Fields.byteArrayEqual(decryptKeys,ckey,oldX,0,NodeCHK.KEY_LENGTH)) continue; } if(extra == null) extra = key.getExtra(); if(commonExtraBytes != null) { if(!Arrays.equals(commonExtraBytes, extra)) continue; } else { if(!Fields.byteArrayEqual(extraBytesForKeys, extra, i * EXTRA_BYTES_LENGTH, 0, EXTRA_BYTES_LENGTH)) continue; } return i; } return -1; } public int getBlockNumber(NodeCHK key, boolean[] ignoreSlots) { byte[] rkey = key.getRoutingKey(); int x = 0; for(int i=0;i<(dataBlocks + checkBlocks);i++) { int oldX = x; x += NodeCHK.KEY_LENGTH; if(ignoreSlots != null && ignoreSlots[i]) { continue; } if(!Fields.byteArrayEqual(routingKeys, rkey, oldX, 0, NodeCHK.KEY_LENGTH)) continue; return i; } return -1; } public int[] getBlockNumbers(NodeCHK key, boolean[] ignoreSlots) { ArrayList<Integer> results = null; byte[] rkey = key.getRoutingKey(); int x = 0; for(int i=0;i<(dataBlocks + checkBlocks);i++) { int oldX = x; x += NodeCHK.KEY_LENGTH; if(ignoreSlots != null && ignoreSlots[i]) { continue; } if(!Fields.byteArrayEqual(routingKeys, rkey, oldX, 0, NodeCHK.KEY_LENGTH)) continue; if(results == null) results = new ArrayList<Integer>(); results.add(i); } if(results == null) return new int[0]; int[] ret = new int[results.size()]; for(int i=0;i<ret.length;i++) ret[i] = results.get(i); return ret; } public NodeCHK getNodeKey(int x, boolean[] ignoreSlots, boolean copy) { if(ignoreSlots != null) { if(ignoreSlots[x]) return null; } return getNodeKey(x, copy); } public ClientCHK getKey(int x, boolean[] ignoreSlots, boolean copy) { if(ignoreSlots != null) { if(ignoreSlots[x]) return null; } return getKey(x, copy); } private ClientCHK getKey(int x, boolean copy) { byte[] routingKey = new byte[32]; System.arraycopy(routingKeys, x * NodeCHK.KEY_LENGTH, routingKey, 0, NodeCHK.KEY_LENGTH); byte[] decryptKey; if(commonDecryptKey != null) { if(copy) { decryptKey = commonDecryptKey.clone(); } else { decryptKey = commonDecryptKey; } } else { int offset = x * ClientCHK.CRYPTO_KEY_LENGTH; decryptKey = Arrays.copyOfRange(decryptKeys, offset, offset + ClientCHK.CRYPTO_KEY_LENGTH); } byte[] extra; if(commonExtraBytes != null) { if(copy) { extra = commonExtraBytes.clone(); } else { extra = commonExtraBytes; } } else { int offset = x * EXTRA_BYTES_LENGTH; extra = Arrays.copyOfRange(extraBytesForKeys, offset, offset + EXTRA_BYTES_LENGTH); } try { return new ClientCHK(routingKey, decryptKey, extra); } catch (MalformedURLException e) { Logger.error(this, "Impossible: "+e); throw new IllegalStateException(e); } } private NodeCHK getNodeKey(int x, boolean copy) { int xr = x * NodeCHK.KEY_LENGTH; byte[] routingKey = Arrays.copyOfRange(routingKeys, xr, xr + NodeCHK.KEY_LENGTH); byte[] extra; if(commonExtraBytes != null) { extra = commonExtraBytes; } else { int xe = x * EXTRA_BYTES_LENGTH; extra = Arrays.copyOfRange(extraBytesForKeys, xe, xe + EXTRA_BYTES_LENGTH); } byte cryptoAlgorithm = ClientCHK.getCryptoAlgorithmFromExtra(extra); return new NodeCHK(routingKey, cryptoAlgorithm); } public void readKeys(DataInputStream dis, boolean check) throws IOException { int count = check ? checkBlocks : dataBlocks; int offset = check ? dataBlocks : 0; if(commonDecryptKey != null) { int rkOffset = offset * NodeCHK.KEY_LENGTH; for(int i=0;i<count;i++) { dis.readFully(routingKeys, rkOffset, NodeCHK.KEY_LENGTH); rkOffset += NodeCHK.KEY_LENGTH; } } else { int rkOffset = offset * NodeCHK.KEY_LENGTH; int extraOffset = offset * EXTRA_BYTES_LENGTH; assert(NodeCHK.KEY_LENGTH == ClientCHK.CRYPTO_KEY_LENGTH); for(int i=0;i<count;i++) { ClientCHK key = ClientCHK.readRawBinaryKey(dis); byte[] r = key.getRoutingKey(); System.arraycopy(r, 0, routingKeys, rkOffset, NodeCHK.KEY_LENGTH); byte[] c = key.getCryptoKey(); System.arraycopy(c, 0, decryptKeys, rkOffset, NodeCHK.KEY_LENGTH); rkOffset += NodeCHK.KEY_LENGTH; byte[] e = key.getExtra(); System.arraycopy(e, 0, extraBytesForKeys, extraOffset, EXTRA_BYTES_LENGTH); extraOffset += EXTRA_BYTES_LENGTH; } } } public void writeKeys(DataOutputStream dos, boolean check) throws IOException { int count = check ? checkBlocks : dataBlocks; int offset = check ? dataBlocks : 0; if(commonDecryptKey != null) { int rkOffset = offset * NodeCHK.KEY_LENGTH; for(int i=0;i<count;i++) { dos.write(routingKeys, rkOffset, NodeCHK.KEY_LENGTH); rkOffset += NodeCHK.KEY_LENGTH; } } else { int rkOffset = offset * NodeCHK.KEY_LENGTH; int extraOffset = offset * EXTRA_BYTES_LENGTH; assert(NodeCHK.KEY_LENGTH == ClientCHK.CRYPTO_KEY_LENGTH); for(int i=0;i<count;i++) { dos.write(extraBytesForKeys, extraOffset, EXTRA_BYTES_LENGTH); extraOffset += EXTRA_BYTES_LENGTH; dos.write(routingKeys, rkOffset, NodeCHK.KEY_LENGTH); dos.write(decryptKeys, rkOffset, NodeCHK.KEY_LENGTH); rkOffset += NodeCHK.KEY_LENGTH; } } } public static int storedKeysLength(int dataBlocks, int checkBlocks, boolean commonDecryptKey) { // FIXME URGENT Implement a unit test for storedKeysLength() vs writeKeys() vs readKeys(). int blocks = dataBlocks + checkBlocks; if(commonDecryptKey) { return blocks * NodeCHK.KEY_LENGTH; } else { return blocks * (EXTRA_BYTES_LENGTH + NodeCHK.KEY_LENGTH*2); } } public int getDataBlocks() { return dataBlocks; } public int getCheckBlocks() { return checkBlocks; } public void setKey(int i, ClientCHK key) { byte[] r = key.getRoutingKey(); System.arraycopy(r, 0, routingKeys, i * NodeCHK.KEY_LENGTH, NodeCHK.KEY_LENGTH); if(decryptKeys != null) { byte[] c = key.getCryptoKey(); System.arraycopy(c, 0, decryptKeys, i * ClientCHK.CRYPTO_KEY_LENGTH, ClientCHK.CRYPTO_KEY_LENGTH); } if(extraBytesForKeys != null) { byte[] e = key.getExtra(); System.arraycopy(e, 0, extraBytesForKeys, i * EXTRA_BYTES_LENGTH, EXTRA_BYTES_LENGTH); } } public NodeCHK[] listNodeKeys(boolean[] foundKeys, boolean copy) { ArrayList<NodeCHK> list = new ArrayList<NodeCHK>(); for(int i=0;i<dataBlocks+checkBlocks;i++) { NodeCHK k = getNodeKey(i, foundKeys, copy); if(k == null) continue; list.add(k); } return list.toArray(new NodeCHK[list.size()]); } @Override public SplitFileSegmentKeys clone() { try { return (SplitFileSegmentKeys) super.clone(); } catch (CloneNotSupportedException e) { throw new Error("Yes it is!"); } } // Not often used, not very efficient, but overriding equals() requires overriding hashCode(). @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + checkBlocks; result = prime * result + Arrays.hashCode(commonDecryptKey); result = prime * result + Arrays.hashCode(commonExtraBytes); result = prime * result + dataBlocks; result = prime * result + Arrays.hashCode(decryptKeys); result = prime * result + Arrays.hashCode(extraBytesForKeys); result = prime * result + Arrays.hashCode(routingKeys); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SplitFileSegmentKeys other = (SplitFileSegmentKeys) obj; if (checkBlocks != other.checkBlocks) return false; if (!Arrays.equals(commonDecryptKey, other.commonDecryptKey)) return false; if (!Arrays.equals(commonExtraBytes, other.commonExtraBytes)) return false; if (dataBlocks != other.dataBlocks) return false; if (!Arrays.equals(decryptKeys, other.decryptKeys)) return false; if (!Arrays.equals(extraBytesForKeys, other.extraBytesForKeys)) return false; if (!Arrays.equals(routingKeys, other.routingKeys)) return false; return true; } public int totalKeys() { return checkBlocks + dataBlocks; } }