package openmods.utils.io;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.UnsignedBytes;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
public class PacketChunker {
private byte packetId = 0;
private final Map<Byte, byte[][]> chunks = Maps.newHashMap();
public static final int MAX_CHUNK_SIZE = Short.MAX_VALUE - 100;
public static final int PACKET_SIZE_S3F = 0x001FFFF0;
public static final int PACKET_SIZE_C17 = 0x00007FFF;
/***
* Split a byte array into one or more chunks with headers
*
* @param data
* @return the list of chunks
*/
public byte[][] splitIntoChunks(byte[] data, int maxChunkSize) {
final int numChunks = (data.length + maxChunkSize - 1) / maxChunkSize;
Preconditions.checkArgument(numChunks < 256, "%s chunks? Way too much data, man.", numChunks);
byte[][] result = new byte[numChunks][];
int chunkOffset = 0;
for (int chunkIndex = 0; chunkIndex < numChunks; chunkIndex++) {
// size of the current chunk
int chunkSize = Math.min(data.length - chunkOffset, maxChunkSize);
ByteArrayDataOutput buf = ByteStreams.newDataOutput(maxChunkSize);
buf.writeByte(numChunks);
if (numChunks > 1) {
buf.writeByte(chunkIndex);
buf.writeByte(packetId);
}
buf.write(data, chunkOffset, chunkSize);
result[chunkIndex] = buf.toByteArray();
chunkOffset += chunkSize;
}
packetId++;
return result;
}
public byte[] consumeChunk(byte[] payload) throws IOException {
return consumeChunk(ByteStreams.newDataInput(payload), payload.length);
}
public byte[] consumeChunk(InputStream stream, int payloadLength) throws IOException {
DataInput data = new DataInputStream(stream);
return consumeChunk(data, payloadLength);
}
/***
* Get the bytes from the packet. If the total packet is not yet complete
* (and we're waiting for more to complete the sequence), we return null.
* Otherwise we return the full byte array
*
* @param payload
* one of the chunks
* @return the full byte array or null if not complete
*/
public synchronized byte[] consumeChunk(DataInput input, int payloadLength) throws IOException {
int numChunks = UnsignedBytes.toInt(input.readByte());
if (numChunks == 1) {
byte[] payload = new byte[payloadLength - 1];
input.readFully(payload);
return payload;
}
int chunkIndex = UnsignedBytes.toInt(input.readByte());
byte incomingPacketId = input.readByte();
byte[][] alreadyReceived = chunks.get(incomingPacketId);
if (alreadyReceived == null) {
alreadyReceived = new byte[numChunks][];
chunks.put(incomingPacketId, alreadyReceived);
}
byte[] chunkBytes = new byte[payloadLength - 3];
input.readFully(chunkBytes);
alreadyReceived[chunkIndex] = chunkBytes;
for (byte[] s : alreadyReceived)
if (s == null) return null; // not completed yet
ByteArrayDataOutput fullPacket = ByteStreams.newDataOutput();
for (short i = 0; i < numChunks; i++) {
byte[] chunkPart = alreadyReceived[i];
fullPacket.write(chunkPart);
}
chunks.remove(incomingPacketId);
return fullPacket.toByteArray();
}
}