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()); } }