/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.client; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; import freenet.client.Metadata.SplitfileAlgorithm; import freenet.client.events.ClientEventProducer; import freenet.client.events.SimpleEventProducer; import freenet.support.compress.Compressor; /** Context object for an insert operation, including both simple and multi-file inserts. * * WARNING: Changing non-transient members on classes that are Serializable can result in * losing uploads. */ public class InsertContext implements Cloneable, Serializable { private static final long serialVersionUID = 1L; /** If true, don't try to compress the data */ public boolean dontCompress; /** Splitfile algorithm. */ private SplitfileAlgorithm splitfileAlgo; /** For migration only. */ private final short splitfileAlgorithm; /** Maximum number of retries (after the initial attempt) for each block * inserted. -1 = retry forever or until it succeeds (subject to * consecutiveRNFsCountAsSuccess) or until a fatal error. */ public int maxInsertRetries; /** On a very small network, any insert will RNF. Therefore we allow * some number of RNFs to equal success. */ public int consecutiveRNFsCountAsSuccess; /** Maximum number of data blocks per segment for splitfiles */ public int splitfileSegmentDataBlocks; /** Maximum number of check blocks per segment for splitfiles. Will be reduced proportionally if there * are fewer data blocks. */ public int splitfileSegmentCheckBlocks; /** Client events will be published to this, you can subscribe to them */ public final ClientEventProducer eventProducer; /** Can this insert write to the client-cache? We don't store all requests in the client cache, * in particular big stuff usually isn't written to it, to maximise its effectiveness. Plus, * local inserts are not written to the client-cache by default for privacy reasons. */ public boolean canWriteClientCache; /** a string that contains the codecs to use/try * if the string is null it defaults to COMPRESSOR_TYPES.Values(), * so old persistent inserts are not affected after update. * @see freenet.support.compress.Compressor.COMPRESSOR_TYPE#getCompressorsArray(String compressordescriptor, boolean pre1254) */ public String compressorDescriptor; public boolean forkOnCacheable; /** Number of extra inserts for a single block inserted on its own. */ public int extraInsertsSingleBlock; /** Number of extra inserts for a block inserted above a splitfile. */ public int extraInsertsSplitfileHeaderBlock; public boolean localRequestOnly; /** Don't insert USK DATEHINTs (and ignore them on polling for maximum edition). */ public boolean ignoreUSKDatehints; /** Compatibility mode. This determines exactly how we insert data, so that we can produce the * same CHK when reinserting a key even if it is with a later version of Freenet. It is * also important for e.g. auto-update to be able to insert keys compatible with older nodes, * but CompatibilityMode's are sometimes backwards compatible, there are separate versioning * systems for keys and Metadata, which will be set as appropriate for an insert depending on * the CompatibilityMode. */ public static enum CompatibilityMode { /** We do not know. */ COMPAT_UNKNOWN((short)0), /** No compatibility issues, use the most efficient metadata possible. Used only in the * front-end: MUST NOT be stored: Code should convert this to a specific mode as early as * possible, or inserts will break when a new mode is added. InsertContext does this. */ COMPAT_CURRENT((short)1), // The below *are* used in Metadata compatibility mode detection. And they are comparable by ordinal(). // This means we have to check for COMPAT_CURRENT as a special case. /** Exactly as before 1250: Segments of exactly 128 data, 128 check, check = data */ COMPAT_1250_EXACT((short)2), /** 1250 or previous: Segments up to 128 data 128 check, check <= data. */ COMPAT_1250((short)3), /** 1251/2/3: Basic even splitting, 1 extra check block if data blocks < 128, max 131 data blocks. */ COMPAT_1251((short)4), /** 1255: Second stage of even splitting, a whole bunch of segments lose one block rather than the last segment losing lots of blocks. And hashes too! */ COMPAT_1255((short)5), /** 1416: New CHK encryption */ COMPAT_1416((short)6), /** 1468: Fill in topDontCompress and topCompatibilityMode on splitfiles. Same blocks, but * slightly different metadata. */ COMPAT_1468((short)7); /** Code used in metadata for this CompatibilityMode. Hence we can remove old * CompatibilityMode's, and it's also convenient. */ public final short code; CompatibilityMode(short code) { this.code = code; } /** cached values(). Never modify or pass this array to outside code! */ private static final CompatibilityMode[] values = values(); // Inserts should be converted to a specific compatibility mode as soon as possible, to avoid // problems when an insert is restarted on a newer build with a newer default compat mode. public static CompatibilityMode latest() { return values[values.length-1]; } /** Must be called whenever we accept a CompatibilityMode as e.g. a config option. Converts * the pseudo- */ public CompatibilityMode intern() { if(this == COMPAT_CURRENT) return latest(); return this; } private static final Map<Short, CompatibilityMode> modesByCode; static { HashMap<Short, CompatibilityMode> cmodes = new HashMap<Short, CompatibilityMode>(); for(CompatibilityMode mode : CompatibilityMode.values) { if(cmodes.containsKey(mode.code)) throw new Error("Duplicated code!"); cmodes.put(mode.code, mode); } modesByCode = Collections.unmodifiableMap(cmodes); } public static CompatibilityMode byCode(short code) { if(!modesByCode.containsKey(code)) throw new IllegalArgumentException(); return modesByCode.get(code); } public static boolean hasCode(short min) { return modesByCode.containsKey(min); } public static boolean maybeFutureCode(short code) { return code > latest().code; } /** The default compatibility mode for new inserts when it is not specified. Usually this * will be COMPAT_CURRENT (it will get converted into a specific mode later), but when a * new compatibility mode is deployed we may want to keep this at an earlier version to * avoid a period when data inserted with the new/testing builds can't be fetched with * earlier versions. */ public static final CompatibilityMode COMPAT_DEFAULT = COMPAT_CURRENT; } /** Backward compatibility support for network level metadata. */ private CompatibilityMode realCompatMode; /** Only for migration. FIXME remove. */ private long compatibilityMode; /** If true, don't insert, just generate the CHK */ public boolean getCHKOnly; /** If true, try to find the final URI as quickly as possible, and insert the upper layers as * soon as we can, rather than waiting for the lower layers. The default behaviour is safer, * because an attacker can usually only identify the datastream once he has the top block, or * once you have announced the key. */ public boolean earlyEncode; public CompatibilityMode getCompatibilityMode() { return realCompatMode; } public long getCompatibilityCode() { return realCompatMode.ordinal(); } public void setCompatibilityMode(CompatibilityMode mode) { this.realCompatMode = mode.intern(); } public InsertContext( int maxRetries, int rnfsToSuccess, int splitfileSegmentDataBlocks, int splitfileSegmentCheckBlocks, ClientEventProducer eventProducer, boolean canWriteClientCache, boolean forkOnCacheable, boolean localRequestOnly, String compressorDescriptor, int extraInsertsSingleBlock, int extraInsertsSplitfileHeaderBlock, CompatibilityMode compatibilityMode) { dontCompress = false; splitfileAlgo = SplitfileAlgorithm.ONION_STANDARD; splitfileAlgorithm = splitfileAlgo.code; this.consecutiveRNFsCountAsSuccess = rnfsToSuccess; this.maxInsertRetries = maxRetries; this.eventProducer = eventProducer; this.splitfileSegmentDataBlocks = splitfileSegmentDataBlocks; this.splitfileSegmentCheckBlocks = splitfileSegmentCheckBlocks; this.canWriteClientCache = canWriteClientCache; this.forkOnCacheable = forkOnCacheable; this.compressorDescriptor = compressorDescriptor; this.extraInsertsSingleBlock = extraInsertsSingleBlock; this.extraInsertsSplitfileHeaderBlock = extraInsertsSplitfileHeaderBlock; this.realCompatMode = compatibilityMode.intern(); this.localRequestOnly = localRequestOnly; this.ignoreUSKDatehints = false; } public InsertContext(InsertContext ctx, SimpleEventProducer producer) { this.dontCompress = ctx.dontCompress; this.splitfileAlgo = ctx.splitfileAlgo; splitfileAlgorithm = splitfileAlgo.code; this.consecutiveRNFsCountAsSuccess = ctx.consecutiveRNFsCountAsSuccess; this.maxInsertRetries = ctx.maxInsertRetries; this.eventProducer = producer; this.splitfileSegmentDataBlocks = ctx.splitfileSegmentDataBlocks; this.splitfileSegmentCheckBlocks = ctx.splitfileSegmentCheckBlocks; this.compressorDescriptor = ctx.compressorDescriptor; this.forkOnCacheable = ctx.forkOnCacheable; this.extraInsertsSingleBlock = ctx.extraInsertsSingleBlock; this.extraInsertsSplitfileHeaderBlock = ctx.extraInsertsSplitfileHeaderBlock; this.realCompatMode = ctx.realCompatMode; this.localRequestOnly = ctx.localRequestOnly; this.ignoreUSKDatehints = ctx.ignoreUSKDatehints; } /** Make public, but just call parent for a field for field copy */ @Override public InsertContext clone() { try { return (InsertContext) super.clone(); } catch (CloneNotSupportedException e) { // Impossible throw new Error(e); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (canWriteClientCache ? 1231 : 1237); result = prime * result + realCompatMode.ordinal(); result = prime * result + ((compressorDescriptor == null) ? 0 : compressorDescriptor.hashCode()); result = prime * result + consecutiveRNFsCountAsSuccess; result = prime * result + (dontCompress ? 1231 : 1237); // eventProducer is ignored. result = prime * result + extraInsertsSingleBlock; result = prime * result + extraInsertsSplitfileHeaderBlock; result = prime * result + (forkOnCacheable ? 1231 : 1237); result = prime * result + (ignoreUSKDatehints ? 1231 : 1237); result = prime * result + (localRequestOnly ? 1231 : 1237); result = prime * result + maxInsertRetries; result = prime * result + splitfileAlgo.code; result = prime * result + splitfileSegmentCheckBlocks; result = prime * result + splitfileSegmentDataBlocks; return result; } /** Are two InsertContext's equal? Ignores the EventProducer, compares only the actual config * values. */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; InsertContext other = (InsertContext) obj; if (canWriteClientCache != other.canWriteClientCache) return false; if (compatibilityMode != other.compatibilityMode) return false; if (compressorDescriptor == null) { if (other.compressorDescriptor != null) return false; } else if (!compressorDescriptor.equals(other.compressorDescriptor)) return false; if (consecutiveRNFsCountAsSuccess != other.consecutiveRNFsCountAsSuccess) return false; if (dontCompress != other.dontCompress) return false; // eventProducer is ignored, and assumed to be unique. if (extraInsertsSingleBlock != other.extraInsertsSingleBlock) return false; if (extraInsertsSplitfileHeaderBlock != other.extraInsertsSplitfileHeaderBlock) return false; if (forkOnCacheable != other.forkOnCacheable) return false; if (ignoreUSKDatehints != other.ignoreUSKDatehints) return false; if (localRequestOnly != other.localRequestOnly) return false; if (maxInsertRetries != other.maxInsertRetries) return false; if (splitfileAlgo != other.splitfileAlgo) return false; if (splitfileSegmentCheckBlocks != other.splitfileSegmentCheckBlocks) return false; if (splitfileSegmentDataBlocks != other.splitfileSegmentDataBlocks) return false; return true; } public SplitfileAlgorithm getSplitfileAlgorithm() { return splitfileAlgo; } /** Call when migrating from db4o era. FIXME remove. * @deprecated */ public void onResume() { // Used to encode it as a long. if(realCompatMode == null) realCompatMode = CompatibilityMode.byCode((short)compatibilityMode); // Max blocks was wrong too. splitfileSegmentDataBlocks = FECCodec.MAX_TOTAL_BLOCKS_PER_SEGMENT; splitfileSegmentCheckBlocks = FECCodec.MAX_TOTAL_BLOCKS_PER_SEGMENT; splitfileAlgo = SplitfileAlgorithm.getByCode(splitfileAlgorithm); } }