423 lines
19 KiB
Java
423 lines
19 KiB
Java
package com.captainalm.lib.calmnet.packet;
|
|
|
|
import com.captainalm.lib.stdcrypt.digest.DigestComparer;
|
|
import com.captainalm.lib.stdcrypt.digest.DigestProvider;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.security.DigestInputStream;
|
|
import java.security.DigestOutputStream;
|
|
|
|
import static com.captainalm.lib.calmnet.packet.PacketProtocolInformation.getProtocolInformation;
|
|
import static com.captainalm.lib.calmnet.packet.PacketProtocolInformation.savePacketProtocolInformation;
|
|
|
|
/**
|
|
* This class provides the ability to load and save {@link IPacket}
|
|
* to {@link java.io.InputStream} and {@link java.io.OutputStream}.
|
|
* Packets can have contents checking support using {@link DigestProvider}.
|
|
*
|
|
* @author Captain ALM
|
|
*/
|
|
public class PacketLoader {
|
|
protected boolean allowInvalidPackets;
|
|
|
|
/**
|
|
* Constructs a new Packet loader instance.
|
|
* If using a digest provider, use {@link #PacketLoader(DigestProvider)}
|
|
*/
|
|
public PacketLoader() {
|
|
this(null);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new Packet loader instance with the specified {@link DigestProvider}.
|
|
* If using a digest provider, make sure all endpoints use the same algorithm;
|
|
* if null, no trailer is created or expected;
|
|
* this is ignored if saving / loading packets from byte arrays.
|
|
*
|
|
* @param provider The digest provider or null.
|
|
*/
|
|
public PacketLoader(DigestProvider provider) {
|
|
hashProvider = provider;
|
|
}
|
|
|
|
protected DigestProvider hashProvider;
|
|
|
|
/**
|
|
* This field provides the {@link DigestProvider} to use for the payload of the packets on the trailer.
|
|
*
|
|
* @return The digest provider in use or null.
|
|
*/
|
|
public DigestProvider getHashProvider() {
|
|
return hashProvider;
|
|
}
|
|
|
|
/**
|
|
* Gets whether invalid packets are allowed to be read and written.
|
|
*
|
|
* @return If invalid packets can be processed.
|
|
*/
|
|
public boolean areInvalidPacketsAllowed() {
|
|
return allowInvalidPackets;
|
|
}
|
|
|
|
/**
|
|
* This sets whether invalid packets are allowed to be read and written.
|
|
*
|
|
* @param allowInvalidPackets If invalid packets can be processed.
|
|
*/
|
|
public void setAllowInvalidPackets(boolean allowInvalidPackets) {
|
|
this.allowInvalidPackets = allowInvalidPackets;
|
|
}
|
|
|
|
protected boolean isPacketInvalid(IPacket packetIn) {
|
|
return (packetIn == null || !packetIn.isValid()) && !allowInvalidPackets;
|
|
}
|
|
|
|
/**
|
|
* Reads a {@link IPacket} from a byte array (No digest support).
|
|
* If the information parameter is null, this is obtained as part of the reading.
|
|
* NOTE: The {@link #getHashProvider()} for digests is NOT supported and no digest is expected for these packets.
|
|
*
|
|
* @param arrayIn The byte array for reading.
|
|
* @param factory The {@link IPacketFactory} to use to generate packets.
|
|
* @param information The protocol information or null.
|
|
* @return The loaded packet or null.
|
|
* @throws NullPointerException The arrayIn or the factory is null.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public IPacket readPacketNoDigest(byte[] arrayIn, IPacketFactory factory, PacketProtocolInformation information) throws PacketException {
|
|
if (arrayIn == null) throw new NullPointerException("arrayIn is null");
|
|
if (factory == null) throw new NullPointerException("factory is null");
|
|
|
|
if (information == null) {
|
|
if (arrayIn.length < 2) throw new PacketException("arrayIn does not have an information header.");
|
|
information = new PacketProtocolInformation(arrayIn[0], arrayIn[1]);
|
|
}
|
|
|
|
IPacket toret = factory.getPacket(information);
|
|
|
|
if (toret != null) {
|
|
if (arrayIn.length < 6) throw new PacketException("arrayIn does not have a length header.");
|
|
int length = (arrayIn[2] & 0xff) * 16777216 + (arrayIn[3] & 0xff) * 65536 + (arrayIn[4] & 0xff) * 256 + (arrayIn[5] & 0xff);
|
|
byte[] loadArray = new byte[length];
|
|
System.arraycopy(arrayIn, 6, loadArray, 0, arrayIn.length - 6);
|
|
toret.loadPayload(loadArray);
|
|
if (isPacketInvalid(toret)) toret = null;
|
|
}
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Reads a {@link IPacket} from an input stream.
|
|
* If the information parameter is null, this is obtained as part of the reading.
|
|
*
|
|
* @param inputStream The input stream for reading.
|
|
* @param factory The {@link IPacketFactory} to use to generate packets.
|
|
* @param information The protocol information or null.
|
|
* @return The loaded packet or null.
|
|
* @throws NullPointerException The inputStream or the factory is null.
|
|
* @throws IOException A stream exception occurs.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public IPacket readPacket(InputStream inputStream, IPacketFactory factory, PacketProtocolInformation information) throws IOException, PacketException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
if (factory == null) throw new NullPointerException("factory is null");
|
|
|
|
if (information == null) information = getProtocolInformation(inputStream);
|
|
|
|
IPacket toret = factory.getPacket(information);
|
|
|
|
if (toret != null) {
|
|
InputStream lIS = (hashProvider == null) ? inputStream : hashProvider.getDigestInputStream(inputStream);
|
|
byte[] loadArray = readArrayFromInputStream(lIS, readInteger(inputStream));
|
|
if (hashProvider == null || DigestComparer.compareDigests(inputStream, ((DigestInputStream) lIS).getMessageDigest().digest())) toret.loadPayload(loadArray);
|
|
if (isPacketInvalid(toret)) toret = null;
|
|
}
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Reads a {@link IPacket} from an input stream (No digest support).
|
|
* If the information parameter is null, this is obtained as part of the reading.
|
|
* NOTE: The {@link #getHashProvider()} for digests is NOT supported and no digest is expected for these packets.
|
|
*
|
|
* @param inputStream The input stream for reading.
|
|
* @param factory The {@link IPacketFactory} to use to generate packets.
|
|
* @param information The protocol information or null.
|
|
* @return The loaded packet or null.
|
|
* @throws NullPointerException The inputStream or the factory is null.
|
|
* @throws IOException A stream exception occurs.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public IPacket readPacketNoDigest(InputStream inputStream, IPacketFactory factory, PacketProtocolInformation information) throws IOException, PacketException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
if (factory == null) throw new NullPointerException("factory is null");
|
|
|
|
if (information == null) information = getProtocolInformation(inputStream);
|
|
|
|
IPacket toret = factory.getPacket(information);
|
|
|
|
if (toret != null) {
|
|
byte[] loadArray = readArrayFromInputStream(inputStream, readInteger(inputStream));
|
|
toret.loadPayload(loadArray);
|
|
if (isPacketInvalid(toret)) toret = null;
|
|
}
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Reads a {@link IStreamedPacket} from an input stream.
|
|
* If the information parameter is null, this is obtained as part of the reading.
|
|
* NOTE: The packet may be an {@link IPacket} if no stream packet is available for that protocol.
|
|
*
|
|
* @param inputStream The input stream for reading.
|
|
* @param factory The {@link IPacketFactory} to use to generate packets.
|
|
* @param information The protocol information or null.
|
|
* @return The loaded packet or null.
|
|
* @throws NullPointerException The inputStream or the factory is null.
|
|
* @throws IOException A stream exception occurs.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public IPacket readStreamedPacket(InputStream inputStream, IPacketFactory factory, PacketProtocolInformation information) throws IOException, PacketException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
if (factory == null) throw new NullPointerException("factory is null");
|
|
|
|
if (information == null) information = getProtocolInformation(inputStream);
|
|
|
|
IPacket toret = factory.getPacket(information);
|
|
|
|
if (toret instanceof IStreamedPacket) {
|
|
int length = readInteger(inputStream);
|
|
InputStream lIS = (hashProvider == null) ? inputStream : hashProvider.getDigestInputStream(inputStream);
|
|
((IStreamedPacket) toret).writeData(lIS, length);
|
|
if (hashProvider != null && !DigestComparer.compareDigests(inputStream, ((DigestInputStream) lIS).getMessageDigest().digest())) toret = null;
|
|
if (isPacketInvalid(toret)) toret = null;
|
|
} else if (toret != null) {
|
|
return readPacket(inputStream, factory, information);
|
|
}
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Reads a {@link IStreamedPacket} from an input stream (No digest support).
|
|
* If the information parameter is null, this is obtained as part of the reading.
|
|
* NOTE: The packet may be an {@link IPacket} if no stream packet is available for that protocol.
|
|
* NOTE: The {@link #getHashProvider()} for digests is NOT supported and no digest is expected for these packets.
|
|
*
|
|
* @param inputStream The input stream for reading.
|
|
* @param factory The {@link IPacketFactory} to use to generate packets.
|
|
* @param information The protocol information or null.
|
|
* @return The loaded packet or null.
|
|
* @throws NullPointerException The inputStream or the factory is null.
|
|
* @throws IOException A stream exception occurs.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public IPacket readStreamedPacketNoDigest(InputStream inputStream, IPacketFactory factory, PacketProtocolInformation information) throws IOException, PacketException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
if (factory == null) throw new NullPointerException("factory is null");
|
|
|
|
if (information == null) information = getProtocolInformation(inputStream);
|
|
|
|
IPacket toret = factory.getPacket(information);
|
|
|
|
if (toret instanceof IStreamedPacket) {
|
|
int length = readInteger(inputStream);
|
|
((IStreamedPacket) toret).writeData(inputStream, length);
|
|
if (isPacketInvalid(toret)) toret = null;
|
|
} else if (toret != null) {
|
|
return readPacketNoDigest(inputStream, factory, information);
|
|
}
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Returns a {@link IPacket} to a byte array (No digest support).
|
|
* NOTE: The {@link #getHashProvider()} for digests is NOT supported and no digest is expected for these packets.
|
|
*
|
|
* @param packet The packet to save.
|
|
* @param writeInformation Write the {@link PacketProtocolInformation} to the beginning of the array.
|
|
* @throws NullPointerException A parameter is null.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public byte[] writePacketNoDigest(IPacket packet, boolean writeInformation) throws PacketException {
|
|
if (packet == null) throw new NullPointerException("packet is null");
|
|
if (isPacketInvalid(packet)) throw new PacketException("packet is invalid");
|
|
|
|
byte[] header = (writeInformation) ? new byte[6] : new byte[4];
|
|
if (writeInformation) {
|
|
header[0] = (byte) packet.getProtocol().getMajor();
|
|
header[1] = (byte) packet.getProtocol().getMinor();
|
|
}
|
|
|
|
byte[] saveArray = packet.savePayload();
|
|
int length = saveArray.length;
|
|
header[((writeInformation) ? 2 : 0)] = (byte) (length / 16777216);
|
|
length %= 16777216;
|
|
header[((writeInformation) ? 3 : 1)] = (byte) (length / 65536);
|
|
length %= 65536;
|
|
header[((writeInformation) ? 4 : 2)] = (byte) (length / 256);
|
|
length %= 256;
|
|
header[((writeInformation) ? 5 : 3)] = (byte) (length);
|
|
|
|
byte[] toret = new byte[header.length + saveArray.length];
|
|
System.arraycopy(header, 0, toret, 0, header.length);
|
|
System.arraycopy(saveArray, 0, toret, header.length, saveArray.length);
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Writes a {@link IPacket} to an output stream.
|
|
*
|
|
* @param outputStream The output stream for writing.
|
|
* @param packet The packet to save.
|
|
* @param writeInformation Write the {@link PacketProtocolInformation} to the stream.
|
|
* @throws NullPointerException A parameter is null.
|
|
* @throws IOException A stream exception occurs.
|
|
* @throws PacketException An Exception has occurred.
|
|
*/
|
|
public void writePacket(OutputStream outputStream, IPacket packet, boolean writeInformation) throws IOException, PacketException {
|
|
if (outputStream == null) throw new NullPointerException("outputStream is null");
|
|
if (packet == null) throw new NullPointerException("packet is null");
|
|
if (isPacketInvalid(packet)) throw new PacketException("packet is invalid");
|
|
|
|
if (writeInformation) savePacketProtocolInformation(outputStream, packet.getProtocol());
|
|
|
|
if (packet instanceof IStreamedPacket) {
|
|
writeInteger(outputStream, ((IStreamedPacket) packet).getSize());
|
|
OutputStream lOS = (hashProvider == null) ? outputStream : hashProvider.getDigestOutputStream(outputStream);
|
|
((IStreamedPacket) packet).readData(lOS);
|
|
if (hashProvider != null) outputStream.write(((DigestOutputStream) lOS).getMessageDigest().digest());
|
|
} else {
|
|
byte[] saveArray = packet.savePayload();
|
|
writeInteger(outputStream, saveArray.length);
|
|
outputStream.write(saveArray);
|
|
if (hashProvider != null) outputStream.write(hashProvider.getDigestOf(saveArray));
|
|
}
|
|
outputStream.flush();
|
|
}
|
|
|
|
/**
|
|
* Reads an Integer from an {@link InputStream}.
|
|
*
|
|
* @param inputStream The input stream to use.
|
|
* @return The integer that was stored.
|
|
* @throws NullPointerException inputStream is null.
|
|
* @throws IOException An I/O error has occurred.
|
|
*/
|
|
public static int readInteger(InputStream inputStream) throws IOException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
int length = (readByteFromInputStream(inputStream) & 0xff) * 16777216;
|
|
length += (readByteFromInputStream(inputStream) & 0xff) * 65536;
|
|
length += (readByteFromInputStream(inputStream) & 0xff) * 256;
|
|
length += (readByteFromInputStream(inputStream) & 0xff);
|
|
return length;
|
|
}
|
|
|
|
/**
|
|
* Writes an Integer to the {@link OutputStream} using 4 bytes.
|
|
*
|
|
* @param outputStream The output stream to use.
|
|
* @param i The integer to store.
|
|
* @throws NullPointerException outputStream is null.
|
|
* @throws IllegalArgumentException i is less than 0.
|
|
* @throws IOException An I/O error has occurred.
|
|
*/
|
|
public static void writeInteger(OutputStream outputStream, int i) throws IOException {
|
|
if (outputStream == null) throw new NullPointerException("outputStream is null");
|
|
if (i < 0) throw new IllegalArgumentException("i is less than 0");
|
|
outputStream.write(i / 16777216);
|
|
i %= 16777216;
|
|
outputStream.write(i / 65536);
|
|
i %= 65536;
|
|
outputStream.write(i / 256);
|
|
i %= 256;
|
|
outputStream.write(i);
|
|
}
|
|
|
|
/**
|
|
* Reads a byte from an {@link InputStream}.
|
|
*
|
|
* @param inputStream The input stream to read from.
|
|
* @return The byte read.
|
|
* @throws NullPointerException inputStream is null.
|
|
* @throws IOException An I/O error has occurred or end of stream has been reached.
|
|
*/
|
|
public static byte readByteFromInputStream(InputStream inputStream) throws IOException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
int toret;
|
|
if ((toret = inputStream.read()) == -1) throw new IOException("inputStream end of stream");
|
|
return (byte) toret;
|
|
}
|
|
|
|
/**
|
|
* Reads in a byte array of a specified length from an {@link InputStream}.
|
|
*
|
|
* @param inputStream The input stream to read from.
|
|
* @param length The length of the stream.
|
|
* @return The array of read bytes.
|
|
* @throws NullPointerException inputStream is null.
|
|
* @throws IllegalArgumentException length is less than 0.
|
|
* @throws IOException An I/O error occurs or end of stream has been reached.
|
|
*/
|
|
public static byte[] readArrayFromInputStream(InputStream inputStream, int length) throws IOException {
|
|
if (inputStream == null) throw new NullPointerException("inputStream is null");
|
|
if (length < 0) throw new IllegalArgumentException("length is less than 0");
|
|
byte[] toret = new byte[length];
|
|
int offset = 0;
|
|
int slen;
|
|
while (offset < length) if ((slen = inputStream.read(toret, offset, length - offset)) != -1) offset += slen; else throw new IOException("inputStream end of stream");
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Saves an Integer into a byte array.
|
|
*
|
|
* @param i The integer to save.
|
|
* @return The byte array.
|
|
* @throws IllegalArgumentException i is less than 0.
|
|
*/
|
|
public static byte[] getByteArrayFromInteger(int i) {
|
|
if (i < 0) throw new IllegalArgumentException("i is less than 0");
|
|
byte[] toret = new byte[4];
|
|
toret[0] = (byte) (i / 16777216);
|
|
i %= 16777216;
|
|
toret[1] = (byte) (i / 65536);
|
|
i %= 65536;
|
|
toret[2] = (byte) (i / 256);
|
|
i %= 256;
|
|
toret[3] = (byte) (i);
|
|
return toret;
|
|
}
|
|
|
|
/**
|
|
* Loads an Integer from a byte array.
|
|
*
|
|
* @param bytes The byte array.
|
|
* @return The integer.
|
|
* @throws NullPointerException bytes is null.
|
|
* @throws IllegalArgumentException bytes length is not 4.
|
|
*/
|
|
public static int getIntegerFromByteArray(byte[] bytes) {
|
|
if (bytes == null) throw new NullPointerException("bytes is null");
|
|
if (bytes.length != 4) throw new IllegalArgumentException("bytes length is not 4");
|
|
return (bytes[0] & 0xff) * 16777216 + (bytes[1] & 0xff) * 65536 + (bytes[2] & 0xff) * 256 + (bytes[3] & 0xff);
|
|
}
|
|
|
|
/**
|
|
* Gets the total size of a written packet in bytes.
|
|
*
|
|
* @param packet The packet to check.
|
|
* @param includeInformation If the 2 byte information header is included.
|
|
* @return The size of the packet in bytes.
|
|
* @throws NullPointerException packet is null.
|
|
* @throws PacketException A Packet Exception has occurred.
|
|
*/
|
|
public int getPacketSize(IPacket packet, boolean includeInformation) throws PacketException {
|
|
if (packet == null) throw new NullPointerException("packet is null");
|
|
return ((includeInformation) ? 2 : 0) + ((packet instanceof IStreamedPacket) ? ((IStreamedPacket) packet).getSize() : packet.savePayload().length)
|
|
+ ((hashProvider == null) ? 0 : hashProvider.getLength());
|
|
}
|
|
}
|