package org.limewire.rudp.messages.impl; import java.io.IOException; import java.nio.ByteBuffer; import org.limewire.io.ByteBufferInputStream; import org.limewire.rudp.messages.AckMessage; import org.limewire.rudp.messages.DataMessage; import org.limewire.rudp.messages.FinMessage; import org.limewire.rudp.messages.KeepAliveMessage; import org.limewire.rudp.messages.RUDPMessageFactory; import org.limewire.rudp.messages.MessageFormatException; import org.limewire.rudp.messages.RUDPMessage; import org.limewire.rudp.messages.SynMessage; import org.limewire.rudp.messages.RUDPMessage.OpCode; import org.limewire.rudp.messages.SynMessage.Role; import org.limewire.service.ErrorService; import org.limewire.util.ByteUtils; import com.google.inject.Singleton; @Singleton public class DefaultMessageFactory implements RUDPMessageFactory { private static final MessageFormatException NO_MATCH = new MessageFormatException("No matching RUDPMessage"); /** * Constructs a message from the given network data. * If the ByteBuffers are divided correctly, the data is referenced * as opposed to copied, so do not pass mutable buffers. */ public RUDPMessage createMessage(ByteBuffer... data) throws MessageFormatException { ByteBufferInputStream in = new ByteBufferInputStream(data); if(in.available() < 23) throw new MessageFormatException("not enough data for header!"); // 012345678901234567890123... // ABCCDDDDDDDDDDDDEEEFFFFGGGG // Explanations: // A - The connection id of the remote side // B - The first four bits are the opcode, // The second four bits are the length of // data stored in the Ds. // C - The sequence number of the message. // D - Up to 12 bytes of data (more data is stored in Gs) // E - Unused. May be any value, preferably 0. // F - The length of the rest of the message. // G - The rest of the data. byte connectionID = (byte)in.read(); // A byte b = (byte)in.read(); // B OpCode opcode = OpCode.valueOf((b & 0xF0) >> 4); // b1 long sequenceNumber = ((long)in.read() & 0xFF) << 8 | ((long)in.read() & 0xFF); // C int data1Length = b & 0xF; // b2 if(data1Length > RUDPMessageImpl.MAX_DATA1_SIZE) throw new MessageFormatException("data1Length too big: " + data1Length); ByteBuffer data1 = ByteBuffer.allocate(RUDPMessageImpl.MAX_DATA1_SIZE); in.read(data1); // D data1.flip(); // only limit data1 buffer to read length for DATA packets, see spec if (opcode == OpCode.OP_DATA) { data1.limit(data1Length); } in.skip(3); // E // Assert that the int in F is the number of bytes remaining. int remaining = -1; try { remaining = ByteUtils.leb2int(in); } catch(IOException impossible) { ErrorService.error(impossible); } if(remaining != in.available()) throw new MessageFormatException("inconsistent message size. expected: " + remaining + ", was: " + in.available()); // Return a reference to the remaining data if possible. ByteBuffer data2 = in.bufferFor(remaining); // G assert in.available() == 0; switch (opcode) { case OP_SYN: return createSynMessage(connectionID, sequenceNumber, data1, data2); case OP_ACK: return new AckMessageImpl(connectionID, sequenceNumber, data1, data2); case OP_KEEPALIVE: return new KeepAliveMessageImpl(connectionID, sequenceNumber, data1, data2); case OP_DATA: return new DataMessageImpl(connectionID, sequenceNumber, data1, data2); case OP_FIN: return new FinMessageImpl(connectionID, sequenceNumber, data1, data2); } throw NO_MATCH; } public DataMessage createDataMessage(byte connectionID, long sequenceNumber, ByteBuffer chunk) { return new DataMessageImpl(connectionID, sequenceNumber, chunk); } public AckMessage createAckMessage(byte connectionID, long sequenceNumber, long windowStart, int windowSpace) { return new AckMessageImpl(connectionID, sequenceNumber, windowStart, windowSpace); } public FinMessage createFinMessage(byte connectionID, long sequenceNumber, byte reasonCode) { return new FinMessageImpl(connectionID, sequenceNumber, reasonCode); } public KeepAliveMessage createKeepAliveMessage(byte connectionID, long windowStart, int windowSpace) { return new KeepAliveMessageImpl(connectionID, windowStart, windowSpace); } public SynMessage createSynMessage(byte connectionID, Role role) { return new SynMessageImpl(connectionID, role); } public SynMessage createSynMessage(byte connectionID, byte theirConnectionID, Role role) { return new SynMessageImpl(connectionID, theirConnectionID, role); } /** * Creates syn message from data read from the network, stubbed out here, so the factory * can be subclassed to test old message versions. */ protected SynMessage createSynMessage(byte connectionID, long sequenceNumber, ByteBuffer data1, ByteBuffer data2) throws MessageFormatException { return new SynMessageImpl(connectionID, sequenceNumber, data1, data2); } }