/* * Dijjer - A Peer to Peer HTTP Cache * Copyright (C) 2004,2005 Change.Tv, Inc * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package freenet.io.comm; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import freenet.support.ByteBufferInputStream; import freenet.support.Fields; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Serializer; import freenet.support.ShortBuffer; import freenet.support.Logger.LogLevel; /** * A Message which can be read from and written to a DatagramPacket. * * SECURITY REDFLAG WARNING: Messages should normally be recreated rather * than passed on. Messages can contain sub-messages, these are used to * avoid having to add whole new message types every time we add one field * to a message... Passing on a message as-is means it includes the * sub-messages, which could lead to e.g. labelling, communication between * colluding nodes along a request route, and just wasting bytes. * * FIXME we should get rid of sub-messages. * * @author ian */ public class Message { public static final String VERSION = "$Id: Message.java,v 1.11 2005/09/15 18:16:04 amphibian Exp $"; private static volatile boolean logMINOR; private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } private final MessageType _spec; private final WeakReference<? extends PeerContext> _sourceRef; private final boolean _internal; private final HashMap<String, Object> _payload = new HashMap<String, Object>(8); private List<Message> _subMessages; public final long localInstantiationTime; final int _receivedByteCount; short priority; private boolean needsLoadRT; private boolean needsLoadBulk; public static Message decodeMessageFromPacket(byte[] buf, int offset, int length, PeerContext peer, int overhead) { ByteBufferInputStream bb = new ByteBufferInputStream(buf, offset, length); return decodeMessage(bb, peer, length + overhead, true, false, false); } public static Message decodeMessageLax(byte[] buf, PeerContext peer, int overhead) { ByteBufferInputStream bb = new ByteBufferInputStream(buf); return decodeMessage(bb, peer, buf.length + overhead, true, false, true); } private static Message decodeMessage(ByteBufferInputStream bb, PeerContext peer, int recvByteCount, boolean mayHaveSubMessages, boolean inSubMessage, boolean veryLax) { MessageType mspec; try { mspec = MessageType.getSpec(bb.readInt(), veryLax); } catch (IOException e1) { if (logMINOR) Logger.minor(Message.class,"Failed to read message type: "+e1, e1); return null; } if (mspec == null) { if (logMINOR) Logger.minor(Message.class, "Bogus message type"); return null; } if (mspec.isInternalOnly()) { if(logMINOR) Logger.minor(Message.class, "Internal only message"); return null; // silently discard internal-only messages } Message m = new Message(mspec, peer, recvByteCount); try { for (String name : mspec.getOrderedFields()) { Class<?> type = mspec.getFields().get(name); if (type.equals(LinkedList.class)) { // Special handling for LinkedList to deal with element type m.set(name, Serializer .readListFromDataInputStream(mspec.getLinkedListTypes().get(name), bb)); } else { m.set(name, Serializer.readFromDataInputStream(type, bb)); } } if (mayHaveSubMessages) { while (bb.remaining() > 2) { // sizeof(unsigned short) == 2 ByteBufferInputStream bb2; try { int size = bb.readUnsignedShort(); if (bb.remaining() < size) return m; bb2 = bb.slice(size); } catch (EOFException e) { if (logMINOR) Logger.minor(Message.class, "No submessages, returning: "+m); return m; } try { Message subMessage = decodeMessage(bb2, peer, 0, false, true, veryLax); if (subMessage == null) return m; if (logMINOR) Logger.minor(Message.class, "Adding submessage: "+subMessage); m.addSubMessage(subMessage); } catch (Throwable t) { Logger.error(Message.class, "Failed to read sub-message: "+t, t); } } } } catch (EOFException e) { String msg = peer.getPeer()+" sent a message packet that ends prematurely while deserialising "+mspec.getName(); if (inSubMessage) { if (logMINOR) Logger.minor(Message.class, msg+" in sub-message", e); } else Logger.error(Message.class, msg, e); return null; } catch (IOException e) { Logger.error(Message.class, "Unexpected IOException: "+e+" reading from buffer stream", e); return null; } if (logMINOR) Logger.minor(Message.class, "Returning message: "+m+" from "+m.getSource()); return m; } public Message(MessageType spec) { this(spec, null, 0); } private Message(MessageType spec, PeerContext source, int recvByteCount) { localInstantiationTime = System.currentTimeMillis(); _spec = spec; if (source == null) { _internal = true; _sourceRef = null; } else { _internal = false; _sourceRef = source.getWeakRef(); } _receivedByteCount = recvByteCount; priority = spec.getDefaultPriority(); } /** Drops sub-messages, and makes it locally originated */ private Message(Message m) { _spec = m._spec; _sourceRef = null; _internal = m._internal; _payload.putAll(m._payload); _subMessages = null; localInstantiationTime = System.currentTimeMillis(); _receivedByteCount = 0; priority = m.priority; needsLoadRT = m.needsLoadRT; needsLoadBulk = m.needsLoadBulk; } public boolean getBoolean(String key) { return (Boolean) _payload.get(key); } public byte getByte(String key) { return (Byte) _payload.get(key); } public short getShort(String key) { return (Short) _payload.get(key); } public int getInt(String key) { return (Integer) _payload.get(key); } public long getLong(String key) { return (Long) _payload.get(key); } public double getDouble(String key) { return (Double) _payload.get(key); } public float getFloat(String key) { return (Float) _payload.get(key); } public double[] getDoubleArray(String key) { return ((double[]) _payload.get(key)); } public float[] getFloatArray(String key) { return (float[]) _payload.get(key); } public String getString(String key) { return (String)_payload.get(key); } public Object getObject(String key) { return _payload.get(key); } public byte[] getShortBufferBytes(String key) { ShortBuffer buffer = (ShortBuffer) getObject(key); return buffer.getData(); } public void set(String key, boolean b) { set(key, Boolean.valueOf(b)); } public void set(String key, byte b) { set(key, Byte.valueOf(b)); } public void set(String key, short s) { set(key, Short.valueOf(s)); } public void set(String key, int i) { set(key, Integer.valueOf(i)); } public void set(String key, long l) { set(key, Long.valueOf(l)); } public void set(String key, double d) { set(key, Double.valueOf(d)); } public void set(String key, float f) { set(key, Float.valueOf(f)); } public void set(String key, Object value) { if (!_spec.checkType(key, value)) { if (value == null) { throw new IncorrectTypeException("Got null for " + key); } throw new IncorrectTypeException("Got " + value.getClass() + ", expected " + _spec.typeOf(key)); } _payload.put(key, value); } public byte[] encodeToPacket() { return encodeToPacket(true, false); } private byte[] encodeToPacket(boolean includeSubMessages, boolean isSubMessage) { if (logDEBUG) Logger.debug(this, "My spec code: "+_spec.getName().hashCode()+" for "+_spec.getName()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); try { dos.writeInt(_spec.getName().hashCode()); for (String name : _spec.getOrderedFields()) { Serializer.writeToDataOutputStream(_payload.get(name), dos); } dos.flush(); } catch (IOException e) { e.printStackTrace(); throw new IllegalStateException(e.getMessage()); } if (_subMessages != null && includeSubMessages) { for (Message _subMessage : _subMessages) { byte[] temp = _subMessage.encodeToPacket(false, true); try { dos.writeShort(temp.length); dos.write(temp); } catch (IOException e) { e.printStackTrace(); throw new IllegalStateException(e.getMessage()); } } } byte[] buf = baos.toByteArray(); if (logDEBUG) Logger.debug(this, "Length: "+buf.length+", hash: "+Fields.hashCode(buf)); return buf; } @Override public String toString() { StringBuilder ret = new StringBuilder(1000); String comma = ""; ret.append(_spec.getName()).append(" {"); for (String name : _spec.getFields().keySet()) { ret.append(comma); ret.append(name).append('=').append(_payload.get(name)); comma = ", "; } ret.append('}'); return ret.toString(); } public PeerContext getSource() { return _sourceRef == null ? null : _sourceRef.get(); } public boolean isInternal() { return _internal; } public MessageType getSpec() { return _spec; } public boolean isSet(String fieldName) { return _payload.containsKey(fieldName); } public Object getFromPayload(String fieldName) throws FieldNotSetException { Object r = _payload.get(fieldName); if (r == null) { throw new FieldNotSetException(fieldName+" not set"); } return r; } public static class FieldNotSetException extends RuntimeException { private static final long serialVersionUID = 1L; public FieldNotSetException(String message) { super(message); } } /** * Set fields for a routed-to-a-specific-node message. * @param nodeIdentity */ public void setRoutedToNodeFields(long uid, double targetLocation, short htl, byte[] nodeIdentity) { set(DMT.UID, uid); set(DMT.TARGET_LOCATION, targetLocation); set(DMT.HTL, htl); set(DMT.NODE_IDENTITY, new ShortBuffer(nodeIdentity)); } public int receivedByteCount() { return _receivedByteCount; } public void addSubMessage(Message subMessage) { if (_subMessages == null) _subMessages = new ArrayList<Message>(); _subMessages.add(subMessage); } public Message getSubMessage(MessageType t) { if (_subMessages == null) return null; for (Message m : _subMessages) { if (m.getSpec() == t) return m; } return null; } public Message grabSubMessage(MessageType t) { if (_subMessages == null) return null; for (int i=0;i<_subMessages.size();i++) { Message m = _subMessages.get(i); if (m.getSpec() == t) { _subMessages.remove(i); return m; } } return null; } public long age() { return System.currentTimeMillis() - localInstantiationTime; } public short getPriority() { return priority; } public void boostPriority() { priority--; } public boolean needsLoadRT() { return needsLoadRT; } public boolean needsLoadBulk() { return needsLoadBulk; } public void setNeedsLoadRT() { needsLoadRT = true; } public void setNeedsLoadBulk() { needsLoadBulk = true; } /** Clone the message, clear sub-messages and set originator to self. */ public Message cloneAndDropSubMessages() { return new Message(this); } }