package net.i2p.data.i2np; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.DataStructureImpl; import net.i2p.data.Hash; import net.i2p.data.SessionKey; import net.i2p.data.TunnelId; import net.i2p.util.Log; /** * Contains the delivery instructions * * @author jrandom */ public class DeliveryInstructions extends DataStructureImpl { private final static Log _log = new Log(DeliveryInstructions.class); private boolean _encrypted; private SessionKey _encryptionKey; private int _deliveryMode; public final static int DELIVERY_MODE_LOCAL = 0; public final static int DELIVERY_MODE_DESTINATION = 1; public final static int DELIVERY_MODE_ROUTER = 2; public final static int DELIVERY_MODE_TUNNEL = 3; private Hash _destinationHash; private Hash _routerHash; private TunnelId _tunnelId; private boolean _delayRequested; private long _delaySeconds; private final static int FLAG_MODE_LOCAL = 0; private final static int FLAG_MODE_DESTINATION = 1; private final static int FLAG_MODE_ROUTER = 2; private final static int FLAG_MODE_TUNNEL = 3; private final static long FLAG_ENCRYPTED = 128; private final static long FLAG_MODE = 96; private final static long FLAG_DELAY = 16; public DeliveryInstructions() { setEncrypted(false); setEncryptionKey(null); setDeliveryMode(-1); setDestination(null); setRouter(null); setTunnelId(null); setDelayRequested(false); setDelaySeconds(0); } public boolean getEncrypted() { return _encrypted; } public void setEncrypted(boolean encrypted) { _encrypted = encrypted; } public SessionKey getEncryptionKey() { return _encryptionKey; } public void setEncryptionKey(SessionKey key) { _encryptionKey = key; } public int getDeliveryMode() { return _deliveryMode; } public void setDeliveryMode(int mode) { _deliveryMode = mode; } public Hash getDestination() { return _destinationHash; } public void setDestination(Hash dest) { _destinationHash = dest; } public Hash getRouter() { return _routerHash; } public void setRouter(Hash router) { _routerHash = router; } public TunnelId getTunnelId() { return _tunnelId; } public void setTunnelId(TunnelId id) { _tunnelId = id; } public boolean getDelayRequested() { return _delayRequested; } public void setDelayRequested(boolean req) { _delayRequested = req; } public long getDelaySeconds() { return _delaySeconds; } public void setDelaySeconds(long seconds) { _delaySeconds = seconds; } public void readBytes(InputStream in) throws DataFormatException, IOException { long flags = DataHelper.readLong(in, 1); if (_log.shouldLog(Log.DEBUG)) _log.debug("Read flags: " + flags + " mode: " + flagMode(flags)); if (flagEncrypted(flags)) { SessionKey k = new SessionKey(); k.readBytes(in); setEncryptionKey(k); setEncrypted(true); } else { setEncrypted(false); } setDeliveryMode(flagMode(flags)); switch (flagMode(flags)) { case FLAG_MODE_LOCAL: break; case FLAG_MODE_DESTINATION: Hash destHash = new Hash(); destHash.readBytes(in); setDestination(destHash); break; case FLAG_MODE_ROUTER: Hash routerHash = new Hash(); routerHash.readBytes(in); setRouter(routerHash); break; case FLAG_MODE_TUNNEL: Hash tunnelRouterHash = new Hash(); tunnelRouterHash.readBytes(in); setRouter(tunnelRouterHash); TunnelId id = new TunnelId(); id.readBytes(in); setTunnelId(id); break; } if (flagDelay(flags)) { long delay = DataHelper.readLong(in, 4); setDelayRequested(true); setDelaySeconds(delay); } else { setDelayRequested(false); } } public int readBytes(byte data[], int offset) throws DataFormatException { int cur = offset; long flags = DataHelper.fromLong(data, cur, 1); cur++; if (_log.shouldLog(Log.DEBUG)) _log.debug("Read flags: " + flags + " mode: " + flagMode(flags)); if (flagEncrypted(flags)) { byte kd[] = new byte[SessionKey.KEYSIZE_BYTES]; System.arraycopy(data, cur, kd, 0, SessionKey.KEYSIZE_BYTES); cur += SessionKey.KEYSIZE_BYTES; setEncryptionKey(new SessionKey(kd)); setEncrypted(true); } else { setEncrypted(false); } setDeliveryMode(flagMode(flags)); switch (flagMode(flags)) { case FLAG_MODE_LOCAL: break; case FLAG_MODE_DESTINATION: byte destHash[] = new byte[Hash.HASH_LENGTH]; System.arraycopy(data, cur, destHash, 0, Hash.HASH_LENGTH); cur += Hash.HASH_LENGTH; setDestination(new Hash(destHash)); break; case FLAG_MODE_ROUTER: byte routerHash[] = new byte[Hash.HASH_LENGTH]; System.arraycopy(data, cur, routerHash, 0, Hash.HASH_LENGTH); cur += Hash.HASH_LENGTH; setRouter(new Hash(routerHash)); break; case FLAG_MODE_TUNNEL: byte tunnelRouterHash[] = new byte[Hash.HASH_LENGTH]; System.arraycopy(data, cur, tunnelRouterHash, 0, Hash.HASH_LENGTH); cur += Hash.HASH_LENGTH; setRouter(new Hash(tunnelRouterHash)); setTunnelId(new TunnelId(DataHelper.fromLong(data, cur, 4))); cur += 4; break; } if (flagDelay(flags)) { long delay = DataHelper.fromLong(data, cur, 4); cur += 4; setDelayRequested(true); setDelaySeconds(delay); } else { setDelayRequested(false); } return cur - offset; } private boolean flagEncrypted(long flags) { return (0 != (flags & FLAG_ENCRYPTED)); } private int flagMode(long flags) { long v = flags & FLAG_MODE; v >>>= 5; return (int)v; } private boolean flagDelay(long flags) { return (0 != (flags & FLAG_DELAY)); } private long getFlags() { long val = 0L; if (getEncrypted()) val = val | FLAG_ENCRYPTED; long fmode = 0; switch (getDeliveryMode()) { case FLAG_MODE_LOCAL: break; case FLAG_MODE_DESTINATION: fmode = FLAG_MODE_DESTINATION << 5; break; case FLAG_MODE_ROUTER: fmode = FLAG_MODE_ROUTER << 5; break; case FLAG_MODE_TUNNEL: fmode = FLAG_MODE_TUNNEL << 5; break; } val = val | fmode; if (getDelayRequested()) val = val | FLAG_DELAY; if (_log.shouldLog(Log.DEBUG)) _log.debug("getFlags() = " + val); return val; } private int getAdditionalInfoSize() { int additionalSize = 0; if (getEncrypted()) { if (_encryptionKey == null) throw new IllegalStateException("Encryption key is not set"); additionalSize += SessionKey.KEYSIZE_BYTES; } switch (getDeliveryMode()) { case FLAG_MODE_LOCAL: if (_log.shouldLog(Log.DEBUG)) _log.debug("mode = local"); break; case FLAG_MODE_DESTINATION: if (_destinationHash == null) throw new IllegalStateException("Destination hash is not set"); additionalSize += Hash.HASH_LENGTH; break; case FLAG_MODE_ROUTER: if (_routerHash == null) throw new IllegalStateException("Router hash is not set"); additionalSize += Hash.HASH_LENGTH; break; case FLAG_MODE_TUNNEL: if ( (_routerHash == null) || (_tunnelId == null) ) throw new IllegalStateException("Router hash or tunnel ID is not set"); additionalSize += Hash.HASH_LENGTH; additionalSize += 4; // tunnelId break; } if (getDelayRequested()) { additionalSize += 4; } return additionalSize; } private byte[] getAdditionalInfo() { int additionalSize = getAdditionalInfoSize(); byte rv[] = new byte[additionalSize]; int offset = 0; offset += getAdditionalInfo(rv, offset); if (offset != additionalSize) _log.log(Log.CRIT, "wtf, additionalSize = " + additionalSize + ", offset = " + offset); return rv; } private int getAdditionalInfo(byte rv[], int offset) { int origOffset = offset; if (getEncrypted()) { if (_encryptionKey == null) throw new IllegalStateException("Encryption key is not set"); System.arraycopy(_encryptionKey.getData(), 0, rv, offset, SessionKey.KEYSIZE_BYTES); offset += SessionKey.KEYSIZE_BYTES; if (_log.shouldLog(Log.DEBUG)) _log.debug("IsEncrypted"); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Is NOT Encrypted"); } switch (getDeliveryMode()) { case FLAG_MODE_LOCAL: if (_log.shouldLog(Log.DEBUG)) _log.debug("mode = local"); break; case FLAG_MODE_DESTINATION: if (_destinationHash == null) throw new IllegalStateException("Destination hash is not set"); System.arraycopy(_destinationHash.getData(), 0, rv, offset, Hash.HASH_LENGTH); offset += Hash.HASH_LENGTH; if (_log.shouldLog(Log.DEBUG)) _log.debug("mode = destination, hash = " + _destinationHash); break; case FLAG_MODE_ROUTER: if (_routerHash == null) throw new IllegalStateException("Router hash is not set"); System.arraycopy(_routerHash.getData(), 0, rv, offset, Hash.HASH_LENGTH); offset += Hash.HASH_LENGTH; if (_log.shouldLog(Log.DEBUG)) _log.debug("mode = router, routerHash = " + _routerHash); break; case FLAG_MODE_TUNNEL: if ( (_routerHash == null) || (_tunnelId == null) ) throw new IllegalStateException("Router hash or tunnel ID is not set"); System.arraycopy(_routerHash.getData(), 0, rv, offset, Hash.HASH_LENGTH); offset += Hash.HASH_LENGTH; DataHelper.toLong(rv, offset, 4, _tunnelId.getTunnelId()); offset += 4; if (_log.shouldLog(Log.DEBUG)) _log.debug("mode = tunnel, tunnelId = " + _tunnelId.getTunnelId() + ", routerHash = " + _routerHash); break; } if (getDelayRequested()) { if (_log.shouldLog(Log.DEBUG)) _log.debug("delay requested: " + getDelaySeconds()); DataHelper.toLong(rv, offset, 4, getDelaySeconds()); offset += 4; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("delay NOT requested"); } return offset - origOffset; } public void writeBytes(OutputStream out) throws DataFormatException, IOException { if ( (_deliveryMode < 0) || (_deliveryMode > FLAG_MODE_TUNNEL) ) throw new DataFormatException("Invalid data: mode = " + _deliveryMode); long flags = getFlags(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Write flags: " + flags + " mode: " + getDeliveryMode() + " =?= " + flagMode(flags)); byte additionalInfo[] = getAdditionalInfo(); DataHelper.writeLong(out, 1, flags); if (additionalInfo != null) { out.write(additionalInfo); out.flush(); } } /** * return the number of bytes written to the target */ public int writeBytes(byte target[], int offset) { if ( (_deliveryMode < 0) || (_deliveryMode > FLAG_MODE_TUNNEL) ) throw new IllegalStateException("Invalid data: mode = " + _deliveryMode); long flags = getFlags(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Write flags: " + flags + " mode: " + getDeliveryMode() + " =?= " + flagMode(flags)); int origOffset = offset; DataHelper.toLong(target, offset, 1, flags); offset++; offset += getAdditionalInfo(target, offset); return offset - origOffset; } public int getSize() { return 1 // flags + getAdditionalInfoSize(); } @Override public boolean equals(Object obj) { if ( (obj == null) || !(obj instanceof DeliveryInstructions)) return false; DeliveryInstructions instr = (DeliveryInstructions)obj; return (getDelayRequested() == instr.getDelayRequested()) && (getDelaySeconds() == instr.getDelaySeconds()) && (getDeliveryMode() == instr.getDeliveryMode()) && (getEncrypted() == instr.getEncrypted()) && DataHelper.eq(getDestination(), instr.getDestination()) && DataHelper.eq(getEncryptionKey(), instr.getEncryptionKey()) && DataHelper.eq(getRouter(), instr.getRouter()) && DataHelper.eq(getTunnelId(), instr.getTunnelId()); } @Override public int hashCode() { return (int)getDelaySeconds() + getDeliveryMode() + DataHelper.hashCode(getDestination()) + DataHelper.hashCode(getEncryptionKey()) + DataHelper.hashCode(getRouter()) + DataHelper.hashCode(getTunnelId()); } @Override public String toString() { StringBuffer buf = new StringBuffer(128); buf.append("[DeliveryInstructions: "); buf.append("\n\tDelivery mode: "); switch (getDeliveryMode()) { case DELIVERY_MODE_LOCAL: buf.append("local"); break; case DELIVERY_MODE_DESTINATION: buf.append("destination"); break; case DELIVERY_MODE_ROUTER: buf.append("router"); break; case DELIVERY_MODE_TUNNEL: buf.append("tunnel"); break; } buf.append("\n\tDelay requested: ").append(getDelayRequested()); buf.append("\n\tDelay seconds: ").append(getDelaySeconds()); buf.append("\n\tDestination: ").append(getDestination()); buf.append("\n\tEncrypted: ").append(getEncrypted()); buf.append("\n\tEncryption key: ").append(getEncryptionKey()); buf.append("\n\tRouter: ").append(getRouter()); buf.append("\n\tTunnelId: ").append(getTunnelId()); return buf.toString(); } }