commit 96c9864092e0d38b8f814df2d71c82052262aff9 Author: Captain ALM Date: Tue Jun 14 04:11:10 2022 +0100 Initial commit. Note, In Progress: Adding all fragment data is verified and sent correctly. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6420f34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Built files +out/ + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..1c2fda5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c14dbda --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9901788 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7fbe392 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, Captain ALM +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..65c9ef5 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Captain ALM Network Library (Java) + +This is the java network library with support for SSL, standard cipher encryption (.net Compatible!), fragmentation, packet object and network stream support. + +This library contains streams that can wrap both Socket and DatagramSocket objects. +Sending large amounts over UDP is also do-able using the fragmentation part of the library. + +This library targets Java 8. + +(C) Captain ALM 2022 - Under the BSD 3-Clause License diff --git a/calmnetlib.iml b/calmnetlib.iml new file mode 100644 index 0000000..c340b0a --- /dev/null +++ b/calmnetlib.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/captainalm/lib/calmnet/SSLUtilities.java b/src/com/captainalm/lib/calmnet/SSLUtilities.java new file mode 100644 index 0000000..c7d73c6 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/SSLUtilities.java @@ -0,0 +1,266 @@ +package com.captainalm.lib.calmnet; + +import javax.net.ssl.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * This class provides SSL utilities to create {@link SSLContext} and {@link SSLSocket} + * objects using JKS files (Or other supported formats). + * + * @author Captain ALM + */ +public class SSLUtilities { + protected static final X509TrustManager jreTrustManager = getJreTrustManager(); + protected static final String defaultSSLContextProtocol = "TLSv1"; + + /** + * Loads a Keystore of a certain type from a file given the password. + * + * @param type The type of keystore (pass null for the default type). + * @param file The file to load the keystore from. + * @param password The password of the keystore (Can be null). + * @return The keystore. + * @throws NullPointerException file is null. + * @throws SSLUtilityException An Exception has occurred. + */ + public static KeyStore loadKeyStore(String type, File file, String password) throws SSLUtilityException { + if (file == null) throw new NullPointerException("file is null"); + if (password == null || password.equals("")) password = "changeit"; + try (FileInputStream inputStream = new FileInputStream(file)) { + KeyStore toret = KeyStore.getInstance((type == null) ? KeyStore.getDefaultType() : type); + toret.load(inputStream, password.toCharArray()); + return toret; + } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { + throw new SSLUtilityException(e); + } + } + + /** + * Gets the SSL context without the JRE Trust Store using a unified {@link KeyStore}. + * + * @param algorithmName The name of the context protocol or null for the JRE Default (TLSv1). + * @param unifiedKeyStore The keystore for use with the private and trust stores. + * @param keyStorePassword The password of the key store or null (Use "changeit" for JKS keystore defaults). + * @return The SSLContext. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLContext getSSLContextNoJRETrust(String algorithmName, KeyStore unifiedKeyStore, char[] keyStorePassword) throws SSLUtilityException { + return getSSLContextNoJRETrust(algorithmName, unifiedKeyStore, keyStorePassword, unifiedKeyStore); + } + + /** + * Gets the SSL context without the JRE Trust Store using separate private and trust {@link KeyStore}s. + * @param algorithmName The name of the context protocol or null for the JRE Default (TLSv1). + * @param privateKeyStore The keystore for use with the private store. + * @param privateKeyStorePassword The password of the private key store or null (Use "changeit" for JKS keystore defaults). + * @param trustKeyStore The keystore for use with the trust store. + * @return The SSLContext. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLContext getSSLContextNoJRETrust(String algorithmName, KeyStore privateKeyStore, char[] privateKeyStorePassword, KeyStore trustKeyStore) throws SSLUtilityException { + try { + SSLContext toret = SSLContext.getInstance((algorithmName == null) ? defaultSSLContextProtocol : algorithmName); + if (algorithmName == null || !algorithmName.equalsIgnoreCase("Default")) { + KeyManagerFactory kmanagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmanagerFactory.init(privateKeyStore, privateKeyStorePassword); + TrustManagerFactory tmanagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmanagerFactory.init(trustKeyStore); + toret.init(kmanagerFactory.getKeyManagers(), buildTrustManagerArray(getX509TrustManager(tmanagerFactory)), null); + } + return toret; + } catch (RuntimeException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException | KeyManagementException e) { + throw new SSLUtilityException(e); + } + } + + /** + * Gets the SSL context merged with the JRE Trust Store using a unified {@link KeyStore}. + * + * @param algorithmName The name of the context protocol or null for the JRE Default (TLSv1). + * @param unifiedKeyStore The keystore for use with the private and trust stores. + * @param keyStorePassword The password of the key store or null (Use "changeit" for JKS keystore defaults). + * @return The SSLContext. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLContext getSSLContext(String algorithmName, KeyStore unifiedKeyStore, char[] keyStorePassword) throws SSLUtilityException { + return getSSLContext(algorithmName, unifiedKeyStore, keyStorePassword, unifiedKeyStore); + } + + /** + * Gets the SSL context merged with the JRE Trust Store using separate private and trust {@link KeyStore}s. + * @param algorithmName The name of the context protocol or null for the JRE Default (TLSv1). + * @param privateKeyStore The keystore for use with the private store. + * @param privateKeyStorePassword The password of the private key store or null (Use "changeit" for JKS keystore defaults). + * @param trustKeyStore The keystore for use with the trust store. + * @return The SSLContext. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLContext getSSLContext(String algorithmName, KeyStore privateKeyStore, char[] privateKeyStorePassword, KeyStore trustKeyStore) throws SSLUtilityException { + try { + SSLContext toret = SSLContext.getInstance((algorithmName == null) ? defaultSSLContextProtocol : algorithmName); + if (algorithmName == null || !algorithmName.equalsIgnoreCase("Default")) { + KeyManagerFactory kmanagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmanagerFactory.init(privateKeyStore, privateKeyStorePassword); + toret.init(kmanagerFactory.getKeyManagers(), buildTrustManagerArray(new MergedX509TrustManager(trustKeyStore)), null); + } + return toret; + } catch (RuntimeException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException | KeyManagementException e) { + throw new SSLUtilityException(e); + } + } + + /** + * Creates a new {@link SSLSocket} using the specified {@link SSLContext}, host and port. + * + * @param sslContext The SSL Context to create the socket from. + * @param host The host to connect to. + * @param port The port to connect to. + * @return The SSLSocket. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLSocket getSSLClientSocket(SSLContext sslContext, String host, int port) throws SSLUtilityException { + try { + return (SSLSocket) sslContext.getSocketFactory().createSocket(host, port); + } catch (RuntimeException | IOException e) { + throw new SSLUtilityException(e); + } + } + + /** + * Upgrades an existing {@link Socket} to an {@link SSLSocket} using the specified {@link SSLContext}, + * {@link Socket}, host, port and if the specified socket should be closed when the returned socket is closed. + * This socket is in client mode (Upgrade for client side). + * + * @param sslContext The SSL Context to create the socket from. + * @param socket The socket to wrap. + * @param host The host to "connect" to. + * @param port The port to "connect" to. + * @param autoClose If the underlying socket should be closed when the returned socket is closed. + * @return The SSLSocket. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLSocket upgradeClientSocketToSSL(SSLContext sslContext, Socket socket, String host, int port, boolean autoClose) throws SSLUtilityException { + return upgradeClientSocketToSSL(sslContext, socket, host, port, autoClose, true); + } + + /** + * Upgrades an existing {@link Socket} to an {@link SSLSocket} using the specified {@link SSLContext}, + * {@link Socket}, host, port and if the specified socket should be closed when the returned socket is closed. + * + * @param sslContext The SSL Context to create the socket from. + * @param socket The socket to wrap. + * @param host The host to "connect" to. + * @param port The port to "connect" to. + * @param autoClose If the underlying socket should be closed when the returned socket is closed. + * @param onClient Is this being called on the client side. + * @return The SSLSocket. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLSocket upgradeClientSocketToSSL(SSLContext sslContext, Socket socket, String host, int port, boolean autoClose, boolean onClient) throws SSLUtilityException { + try { + SSLSocket toret = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + toret.setUseClientMode(onClient); + return toret; + } catch (RuntimeException | IOException e) { + throw new SSLUtilityException(e); + } + } + + /** + * Gets the SSL Server socket for the specified {@link SSLContext}, port, backlog and {@link InetAddress}. + * + * @param sslContext The SSL Context to create the socket from. + * @param port The port to listen on. + * @param backlog The number of connections that can be queued. + * @param ifAddress The network interface to listen on (null means listen on all network interfaces). + * @return The SSLServerSocket. + * @throws SSLUtilityException An Exception has occurred. + */ + public static SSLServerSocket getSSLServerSocket(SSLContext sslContext, int port, int backlog, InetAddress ifAddress) throws SSLUtilityException { + try { + return (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(port, backlog, ifAddress); + } catch (RuntimeException | IOException e) { + throw new SSLUtilityException(e); + } + } + + protected static X509TrustManager getJreTrustManager() { + try { + return getX509TrustManager((KeyStore) null); + } catch (RuntimeException | KeyStoreException | NoSuchAlgorithmException e) { + return null; + } + } + + protected static X509TrustManager getX509TrustManager(KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException { + TrustManagerFactory tmanagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmanagerFactory.init(keyStore); + return getX509TrustManager(tmanagerFactory); + } + + protected static X509TrustManager getX509TrustManager(TrustManagerFactory tmanagerFactory) { + for (TrustManager c : tmanagerFactory.getTrustManagers()) if (c instanceof X509TrustManager) return (X509TrustManager) c; + return null; + } + + protected static TrustManager[] buildTrustManagerArray(TrustManager trustManager) { + if (trustManager == null) return null; else return new TrustManager[] {trustManager}; + } + + /** + * This class provides a merging of a loaded trust manager and the JRE default trust manager. + * (Useful for clients) + * + * @author Captain ALM + */ + private static class MergedX509TrustManager implements X509TrustManager { + protected final X509TrustManager loadedTrustManager; + + public MergedX509TrustManager(KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException { + loadedTrustManager = getX509TrustManager(keyStore); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + loadedTrustManager.checkClientTrusted(chain, authType); + } catch (CertificateException e) { + if (jreTrustManager == null) throw new CertificateException(new NullPointerException("jreTrustManager is null")); + jreTrustManager.checkClientTrusted(chain, authType); + } + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + loadedTrustManager.checkServerTrusted(chain, authType); + } catch (CertificateException e) { + if (jreTrustManager == null) throw new CertificateException(new NullPointerException("jreTrustManager is null")); + jreTrustManager.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + if (jreTrustManager == null) { + if (loadedTrustManager == null) return new X509Certificate[0]; + return loadedTrustManager.getAcceptedIssuers(); + } else { + X509Certificate[] jre = jreTrustManager.getAcceptedIssuers(); + X509Certificate[] lod = loadedTrustManager.getAcceptedIssuers(); + X509Certificate[] toret = new X509Certificate[jre.length + lod.length]; + int index = 0; + for (X509Certificate c : jre) toret[index++] = c; + for (X509Certificate c : lod) toret[index++] = c; + return toret; + } + } + } +} diff --git a/src/com/captainalm/lib/calmnet/SSLUtilityException.java b/src/com/captainalm/lib/calmnet/SSLUtilityException.java new file mode 100644 index 0000000..6df0e8b --- /dev/null +++ b/src/com/captainalm/lib/calmnet/SSLUtilityException.java @@ -0,0 +1,60 @@ +package com.captainalm.lib.calmnet; + +import java.security.PrivilegedActionException; + +/** + * This class provides the SSL Utility exception wrapper class. + * See {@link SSLUtilityException#getCause()} to find out the underlying exception. + * + * @author Captain ALM + */ +public class SSLUtilityException extends Exception { + + /** + * Constructs a new exception with the specified detail message. The + * cause is not initialized, and may subsequently be initialized by + * a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public SSLUtilityException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public SSLUtilityException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail + * message of (cause==null ? null : cause.toString()) (which + * typically contains the class and detail message of cause). + * This constructor is useful for exceptions that are little more than + * wrappers for other throwables (for example, {@link + * PrivilegedActionException}). + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public SSLUtilityException(Throwable cause) { + super(cause); + } +} diff --git a/src/com/captainalm/lib/calmnet/package-info.java b/src/com/captainalm/lib/calmnet/package-info.java new file mode 100644 index 0000000..eb69e0a --- /dev/null +++ b/src/com/captainalm/lib/calmnet/package-info.java @@ -0,0 +1,8 @@ +/** + * This library contains the network socket system for calmclientandserver. + * + * @since 0.0 + * @author Captain ALM + * @version 0.0 + */ +package com.captainalm.lib.calmnet; \ No newline at end of file diff --git a/src/com/captainalm/lib/calmnet/packet/CALMNETPacketFactory.java b/src/com/captainalm/lib/calmnet/packet/CALMNETPacketFactory.java new file mode 100644 index 0000000..d6a38a4 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/CALMNETPacketFactory.java @@ -0,0 +1,146 @@ +package com.captainalm.lib.calmnet.packet; + +import com.captainalm.lib.calmnet.packet.core.*; +import com.captainalm.lib.calmnet.packet.fragment.*; +import com.captainalm.lib.stdcrypt.encryption.ICipherFactory; + +/** + * This class provides a standard extensible {@link IPacketFactory} for calmnet packets. + * + * @author Captain ALM + */ +public class CALMNETPacketFactory implements IPacketFactory { + protected IPacketFactory factoryToUse; + protected PacketLoader loaderToUse; + protected ICipherFactory cipherToUse; + protected boolean streamPreferred; + + /** + * Constructs a new Instance of CALMNETPacketFactory with if {@link IStreamedPacket}s are preferred and the specified {@link PacketLoader}. + * + * @param preferStreamPackets If streamed packets are preferred for construction. + * @param loader The packet loader to use. + * @throws NullPointerException loader is null. + */ + public CALMNETPacketFactory(boolean preferStreamPackets, PacketLoader loader) { + this(preferStreamPackets ,loader, null); + } + + /** + * Constructs a new Instance of CALMNETPacketFactory with if {@link IStreamedPacket}s are preferred, the specified {@link PacketLoader} and the {@link IPacketFactory}. + * + * @param preferStreamPackets If streamed packets are preferred for construction. + * @param loader The packet loader to use. + * @param factory The packet factory to use or null (null signifies to use the same instance). + * @throws NullPointerException loader is null. + */ + public CALMNETPacketFactory(boolean preferStreamPackets, PacketLoader loader, IPacketFactory factory) { + streamPreferred = preferStreamPackets; + setPacketLoader(loader); + setPacketFactory(factory); + } + + /** + * Constructs a {@link IPacket} of the protocol specified by the passed {@link PacketProtocolInformation} instance. + * + * @param information The protocol information to use. + * @return The constructed packet or null. + * @throws NullPointerException The information is null. + */ + @Override + public IPacket getPacket(PacketProtocolInformation information) { + if (information == null) throw new NullPointerException("information is null"); + + if (information.equals(Base64Packet.getTheProtocol())) return new Base64Packet(factoryToUse, loaderToUse); + if (information.equals(EncryptedPacket.getTheProtocol()) && cipherToUse != null) return new EncryptedPacket(factoryToUse, loaderToUse, cipherToUse); + if (information.equals(NetworkEncryptionUpgradePacket.getTheProtocol())) return new NetworkEncryptionUpgradePacket(null, false, false, cipherToUse); + if (information.equals(NetworkIdentifierPacket.getTheProtocol())) return new NetworkIdentifierPacket(null); + if (information.equals(NetworkSSLUpgradePacket.getTheProtocol())) return new NetworkSSLUpgradePacket(null); + + if (information.equals(FragmentAllocatePacket.getTheProtocol())) return new FragmentAllocatePacket(null, null); + if (information.equals(FragmentAllocationPacket.getTheProtocol())) return new FragmentAllocationPacket(null, null, null); + if (information.equals(FragmentMessagePacket.getTheProtocol())) return new FragmentMessagePacket(null, null, null); + if (information.equals(FragmentMessageResponsePacket.getTheProtocol())) return new FragmentMessageResponsePacket(null, null, null); + if (information.equals(FragmentRetrySendPacket.getTheProtocol())) return new FragmentRetrySendPacket(null, null); + if (information.equals(FragmentSendCompletePacket.getTheProtocol())) return new FragmentSendCompletePacket(null, null); + if (information.equals(FragmentSendStopPacket.getTheProtocol())) return new FragmentSendStopPacket(null); + + return null; + } + + /** + * Gets if {@link #getPacket(PacketProtocolInformation)} prefers returning {@link IStreamedPacket}s if possible. + * + * @return If streamed packets are preferred for construction. + */ + @Override + public boolean areStreamPacketsPreferred() { + return streamPreferred; + } + + /** + * Sets if {@link #getPacket(PacketProtocolInformation)} prefers returning {@link IStreamedPacket}s if possible. + * + * @param preferred If streamed packets are preferred for construction. + */ + @Override + public void setStreamPacketsPreferred(boolean preferred) { + streamPreferred = preferred; + } + + /** + * Gets the {@link IPacketFactory} in use (Could be the same instance). + * + * @return The packet factory in use. + */ + public IPacketFactory getPacketFactory() { + return factoryToUse; + } + + /** + * Sets the {@link IPacketFactory} in use (null signifies to use the same instance). + * + * @param factory The packet factory to use or null. + */ + public void setPacketFactory(IPacketFactory factory) { + if (factory == null) factoryToUse = this; else factoryToUse = factory; + } + + /** + * Gets the {@link PacketLoader} in use. + * + * @return The packet loader in use. + */ + public PacketLoader getPacketLoader() { + return loaderToUse; + } + + /** + * Sets the {@link PacketLoader} to use. + * + * @param loader The packet loader to use. + * @throws NullPointerException loader is null. + */ + public void setPacketLoader(PacketLoader loader) { + if (loader == null) throw new NullPointerException("loader is null"); + loaderToUse = loader; + } + + /** + * Gets the {@link ICipherFactory} in use (Could be the same instance). + * + * @return The cipher factory in use. + */ + public ICipherFactory getCipherFactory() { + return cipherToUse; + } + + /** + * Sets the {@link ICipherFactory} in use. + * + * @param factory The cipher factory to use or null. + */ + public void setCipherFactory(ICipherFactory factory) { + cipherToUse = factory; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/CALMNETPacketFactoryWithPacket.java b/src/com/captainalm/lib/calmnet/packet/CALMNETPacketFactoryWithPacket.java new file mode 100644 index 0000000..50fd2d6 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/CALMNETPacketFactoryWithPacket.java @@ -0,0 +1,71 @@ +package com.captainalm.lib.calmnet.packet; + +import com.captainalm.lib.calmnet.packet.core.*; + +/** + * This class provides a standard extensible {@link IPacketFactory} for calmnet packets + * with the ability to set the {@link IPacket} of supporting packets. + * + * @author Captain ALM + */ +public class CALMNETPacketFactoryWithPacket extends CALMNETPacketFactory { + protected IPacket packetToUse; + + /** + * Constructs a new Instance of CALMNETPacketFactoryWithPacket with if {@link IStreamedPacket}s are preferred and the specified {@link PacketLoader}. + * + * @param preferStreamPackets If streamed packets are preferred for construction. + * @param loader The packet loader to use. + * @throws NullPointerException loader is null. + */ + public CALMNETPacketFactoryWithPacket(boolean preferStreamPackets, PacketLoader loader) { + this(preferStreamPackets, loader, null); + } + + /** + * Constructs a new Instance of CALMNETPacketFactoryWithPacket with if {@link IStreamedPacket}s are preferred, the specified {@link PacketLoader} and the {@link IPacketFactory}. + * + * @param preferStreamPackets If streamed packets are preferred for construction. + * @param loader The packet loader to use. + * @param factory The packet factory to use or null (null signifies to use the same instance). + * @throws NullPointerException loader is null. + */ + public CALMNETPacketFactoryWithPacket(boolean preferStreamPackets, PacketLoader loader, IPacketFactory factory) { + super(preferStreamPackets, loader, factory); + } + + /** + * Constructs a {@link IPacket} of the protocol specified by the passed {@link PacketProtocolInformation} instance. + * + * @param information The protocol information to use. + * @return The constructed packet or null. + * @throws NullPointerException The information is null. + */ + @Override + public IPacket getPacket(PacketProtocolInformation information) { + if (information == null) throw new NullPointerException("information is null"); + + if (information.equals(Base64Packet.getTheProtocol())) return new Base64Packet(factoryToUse, loaderToUse, packetToUse); + if (information.equals(EncryptedPacket.getTheProtocol()) && cipherToUse != null) return new EncryptedPacket(factoryToUse, loaderToUse, cipherToUse, packetToUse); + + return super.getPacket(information); + } + + /** + * Gets the {@link IPacket} in use (Could be the same instance). + * + * @return The packet in use. + */ + public IPacket getPacket() { + return packetToUse; + } + + /** + * Sets the {@link IPacket} in use. + * + * @param packet The packet to use or null. + */ + public void setPacket(IPacket packet) { + packetToUse = packet; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/FragmentReceiver.java b/src/com/captainalm/lib/calmnet/packet/FragmentReceiver.java new file mode 100644 index 0000000..b8a2476 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/FragmentReceiver.java @@ -0,0 +1,387 @@ +package com.captainalm.lib.calmnet.packet; + +import com.captainalm.lib.calmnet.packet.fragment.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; + +/** + * This class provides the ability to re-construct packets from {@link com.captainalm.lib.calmnet.packet.fragment}. + * + * @author Captain ALM + */ +public final class FragmentReceiver { + private final Queue outputQueue = new LinkedList<>(); + private final Queue finishedIDs = new LinkedList<>(); + private final Queue allocated = new LinkedList<>(); + private final Queue forceStopIDs = new LinkedList<>(); + private final HashMap registry = new HashMap<>(); + private final Object slock = new Object(); + private final Object slockqueue = new Object(); + private final Object slockfinish = new Object(); + private int numberOfEmptySendsTillForced = 2; + private int IID = 0; + private PacketLoader packetLoader; + private IPacketFactory packetFactory; + private boolean verifyResponses = false; + + /** + * Constructs a new FragmentReceiver with the specified {@link PacketLoader} and {@link IPacketFactory}. + * + * @param loader The packet loader to use. + * @param factory The packet factory to use. + * @throws NullPointerException loader or factory is null. + */ + public FragmentReceiver(PacketLoader loader, IPacketFactory factory) { + setPacketLoader(loader); + setPacketFactory(factory); + } + + /** + * Receives a {@link IPacket} from the FragmentReceiver. + * This method blocks until a packet can be received. + * + * @return The received packet. + * @throws InterruptedException The Thread was Interrupted. + */ + public IPacket receivePacket() throws InterruptedException { + synchronized (slockqueue) { + while (outputQueue.size() < 1) slockqueue.wait(); + return outputQueue.poll(); + } + } + + /** + * Sends the current {@link IPacket}s from the FragmentReceiver. + * + * @return The packets to send. + * @throws PacketException A Packet Exception has occurred. + */ + public IPacket[] sendPacket() throws PacketException { + synchronized (slock) { + ArrayList toret = new ArrayList<>(); + while (allocated.size() > 0) toret.add(allocated.poll().getAllocation()); + ArrayList toRemove = new ArrayList<>(); + for (int key : registry.keySet()) { + IPacket packet = registry.get(key).getSendPacket(); + if (packet != null) { + toret.add(packet); + if (packet instanceof FragmentSendCompletePacket) { + toRemove.add(key); + synchronized (slockfinish) { + finishedIDs.add(key); + slockfinish.notify(); + } + } + } + IPacket wasConsumed = registry.get(key).consume(); + if (wasConsumed != null) { + synchronized (slockqueue) { + outputQueue.add(wasConsumed); + slockqueue.notify(); + } + } + } + for (int i : toRemove) registry.remove(i); + while (forceStopIDs.size() > 0) toret.add(new FragmentSendStopPacket(forceStopIDs.poll())); + return toret.toArray(new IPacket[0]); + } + } + + /** + * Receives a {@link IPacket} into the FragmentReceiver. + * + * @param packetIn The packet to receive. + * @throws PacketException A Packet Exception has occurred. + */ + public void receivePacket(IPacket packetIn) throws PacketException { + if (packetIn == null || !packetIn.isValid()) return; + if (packetIn instanceof FragmentPIDPacket) { + synchronized (slock) { + FragmentInput fragmentInput = registry.get(((FragmentPIDPacket) packetIn).getPacketID()); + if (fragmentInput != null) fragmentInput.receivePacket(packetIn); + } + } else if (packetIn instanceof FragmentAllocatePacket && ((FragmentAllocatePacket)packetIn).getFragmentCount() > 0) { + synchronized (slock) { + if (!containsAllocationID(((FragmentAllocatePacket) packetIn).getAllocationID())) { + int currentID = getCurrentID(); + UUID aid = ((FragmentAllocatePacket) packetIn).getAllocationID(); + if (currentID >= 0) { + registry.put(currentID, new FragmentInput(currentID, ((FragmentAllocatePacket) packetIn).getFragmentCount(), aid)); + allocated.add(new AllocationOutput(currentID, aid, true)); + } else { + allocated.add(new AllocationOutput(0, aid, false)); + } + } + } + } + } + + private int getCurrentID() { + if (registry.containsKey(IID)) while (IID >= 0 && registry.containsKey(IID)) IID++; + if (IID < 0) IID = -1; + return IID; + } + + private boolean containsAllocationID(UUID aid) { + for (int c : registry.keySet()) if (registry.get(c).allocationID.equals(aid)) return true; + return false; + } + + /** + * Gets whether packets are waiting to be received. + * + * @return If packets are waiting to be received. + */ + public boolean arePacketsWaiting() { + synchronized (slockqueue) { + return outputQueue.size() > 0; + } + } + + /** + * Clears the currently waiting packets. + */ + public void clearWaitingPackets() { + synchronized (slockqueue) { + outputQueue.clear(); + } + } + + /** + * Deletes a packet from the registry and requests the sender to stop sending. + * + * @param id The ID of the packet to remove. + */ + public void deletePacketFromRegistry(int id) { + synchronized (slock) { + forceStopIDs.add(id); + registry.remove(id); + } + } + + /** + * Clears the registry (And requests the sender to stop sending). + * NOTE: Do NOT do this unless you are finished with the FragmentReceiver. + */ + public void clearRegistry() { + synchronized (slock) { + forceStopIDs.addAll(registry.keySet()); + registry.clear(); + } + } + + /** + * Gets whether finished IDs are waiting for obtaining. + * + * @return If finished IDs are waiting for obtaining. + */ + public boolean areFinishedIDsWaiting() { + synchronized (slockfinish) { + return finishedIDs.size() > 0; + } + } + + /** + * Gets the last finished packet ID. + * This method blocks until a packet finishes processing. + * + * @return The last finished packet ID. + * @throws InterruptedException The Thread was Interrupted. + */ + public int getLastIDFinished() throws InterruptedException { + synchronized (slockfinish) { + while (finishedIDs.size() < 1) slockfinish.wait(); + Integer polled = finishedIDs.poll(); + return (polled == null) ? -1 : polled; + } + } + + /** + * Clears all the last finished packet IDs. + */ + public void clearLastIDFinished() { + synchronized (slockfinish) { + finishedIDs.clear(); + } + } + + /** + * Gets the {@link IPacketFactory} in use. + * + * @return The PacketFactory in use. + */ + public IPacketFactory getPacketFactory() { + return packetFactory; + } + + /** + * Sets the {@link IPacketFactory} to use. + * + * @param factory The packet factory to use. + * @throws NullPointerException factory is null. + */ + public void setPacketFactory(IPacketFactory factory) { + if (factory == null) throw new NullPointerException("factory is null"); + synchronized (slock) { + packetFactory = factory; + } + } + + /** + * Gets the {@link PacketLoader} in use. + * + * @return The PacketLoader in use. + */ + public PacketLoader getPacketLoader() { + return packetLoader; + } + + /** + * Sets the {@link PacketLoader} to use. + * + * @param loader The packet loader to use. + * @throws NullPointerException loader is null. + */ + public void setPacketLoader(PacketLoader loader) { + if (loader == null) throw new NullPointerException("loader is null"); + synchronized (slock) { + packetLoader = loader; + } + } + + /** + * Gets whether responses should be verified. + * + * @return Should responses be verified. + */ + public boolean shouldVerifyResponses() { + return verifyResponses; + } + + /** + * Sets whether responses should be verified. + * + * @param state If responses should be verified. + */ + public void setResponseVerification(boolean state) { + synchronized (slock) { + verifyResponses = state; + } + } + + /** + * Gets the number of {@link #sendPacket()} calls, that return null, to a registry entry are made before + * the {@link FragmentSendCompletePacket} or {@link FragmentRetrySendPacket} packets are sent. + * A {@link FragmentSendCompletePacket} is sent if completely received and a + * {@link FragmentRetrySendPacket} is sent if not completely received. + * + * @return The number of send packet calls before a completion or restart is forced. + */ + public int getNumberOfEmptySendsTillForcedCompleteOrResend() { + return numberOfEmptySendsTillForced; + } + + /** + * Sets the number of {@link #sendPacket()} calls, that return null, to a registry entry are made before + * the {@link FragmentSendCompletePacket} or {@link FragmentRetrySendPacket} packets are sent. + * A {@link FragmentSendCompletePacket} is sent if completely received and a + * {@link FragmentRetrySendPacket} is sent if not completely received. + * + * @param numberOfEmptySends The number of empty sends to allow. + * @throws IllegalArgumentException numberOfEmptySends is less than 1. + */ + public void setNumberOfEmptySendsTillForcedCompleteOrResend(int numberOfEmptySends) { + if (numberOfEmptySends < 1) throw new IllegalArgumentException("numberOfEmptySends is less than 1"); + numberOfEmptySendsTillForced = numberOfEmptySends; + } + + /** + * This class provides the ability to store allocated responses to be sent back. + * + * @author Captain ALM + */ + private static final class AllocationOutput { + public final int packetID; + public final UUID allocationID; + public final boolean success; + + public AllocationOutput(int packetID, UUID allocationID, boolean success) { + this.packetID = packetID; + this.allocationID = allocationID; + this.success = success; + } + + public FragmentAllocationPacket getAllocation() { + return new FragmentAllocationPacket(packetID, allocationID, success); + } + } + + /** + * This class provides the ability to store the received fragments and get the next packet to send on the protocol. + * The next packet for sending will change as signalling packets come through. + * + * @author Captain ALM + */ + private final class FragmentInput { + public final int packetID; + public final UUID allocationID; + private final ArrayList idsToReceive = new ArrayList<>(); + private final ArrayList idsToAKN = new ArrayList<>(); + private int msgPacketIndex = 0; + private boolean consumeDone = false; + private int sendsTillCompleteForced; + private boolean fsendActive = false; + private final FragmentMessagePacket[] messagePackets; + + public FragmentInput(int id, int count, UUID aid) { + packetID = id; + messagePackets = new FragmentMessagePacket[count]; + allocationID = aid; + sendsTillCompleteForced = numberOfEmptySendsTillForced + 1; + for (int i = 0; i < count; i++) idsToReceive.add(i); + } + + public IPacket getSendPacket() { + if (msgPacketIndex < idsToAKN.size() && msgPacketIndex >= 0) { + int pindex = idsToAKN.get(msgPacketIndex++); + if (msgPacketIndex >= idsToAKN.size()) { + idsToAKN.clear(); + msgPacketIndex = 0; + } + return new FragmentMessageResponsePacket(packetID, pindex, (verifyResponses) ? messagePackets[pindex].getFragmentMessage() : null); + } + if (fsendActive) { + if (sendsTillCompleteForced > 0) sendsTillCompleteForced--; + } else fsendActive = true; + if (sendsTillCompleteForced == 0) return (idsToReceive.size() < 1) ? new FragmentSendCompletePacket(packetID, true) : new FragmentRetrySendPacket(packetID, false); + return null; + } + + public void receivePacket(IPacket packetIn) { + if ((packetIn instanceof FragmentSendCompletePacket && !((FragmentSendCompletePacket) packetIn).isAcknowledgement())) sendsTillCompleteForced = 0; + if ((packetIn instanceof FragmentRetrySendPacket && ((FragmentRetrySendPacket) packetIn).isAcknowledgement())) sendsTillCompleteForced = numberOfEmptySendsTillForced + 1; + if (packetIn instanceof FragmentMessagePacket) { + FragmentMessagePacket messagePacket = (FragmentMessagePacket) packetIn; + messagePackets[messagePacket.getFragmentID()] = messagePacket; + idsToReceive.remove(messagePacket.getFragmentID()); + idsToAKN.add(messagePacket.getFragmentID()); + } + } + + public IPacket consume() throws PacketException { + if (consumeDone || idsToReceive.size() > 0 || messagePackets.length < 1) return null; + ByteArrayOutputStream packetStream = new ByteArrayOutputStream(messagePackets[0].getFragmentMessage().length); + for (FragmentMessagePacket messagePacket : messagePackets) { + try { + packetStream.write(messagePacket.getFragmentMessage()); + } catch (IOException e) { + throw new PacketException(e); + } + } + consumeDone = true; + return packetLoader.readPacketNoDigest(packetStream.toByteArray(), packetFactory, null); + } + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/FragmentSender.java b/src/com/captainalm/lib/calmnet/packet/FragmentSender.java new file mode 100644 index 0000000..9a29518 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/FragmentSender.java @@ -0,0 +1,320 @@ +package com.captainalm.lib.calmnet.packet; + +import com.captainalm.lib.calmnet.packet.fragment.*; + +import java.util.*; + +/** + * This class provides the ability to create packets for {@link com.captainalm.lib.calmnet.packet.fragment}. + * + * @author Captain ALM + */ +public final class FragmentSender { + private final Queue inputQueue = new LinkedList<>(); + private final Queue finishedIDs = new LinkedList<>(); + private final HashMap allocationInputs = new HashMap<>(); + private final HashMap registry = new HashMap<>(); + private final Object slock = new Object(); + private final Object slockfinish = new Object(); + private int splitSize = 496; + private PacketLoader packetLoader; + private boolean verifyResponses = false; + + /** + * Constructs a new FragmentSender with the specified {@link PacketLoader}. + * + * @param loader The packet loader to use. + * @throws NullPointerException loader is null. + */ + public FragmentSender(PacketLoader loader) { + setPacketLoader(loader); + } + + /** + * Constructs a new FragmentSender with the specified {@link PacketLoader} + * and packet split size in bytes. + * + * @param loader The packet loader to use. + * @param newSize The new split size. + * @throws NullPointerException loader is null. + * @throws IllegalArgumentException newSize is less than 1. + */ + public FragmentSender(PacketLoader loader, int newSize) { + this(loader); + setSplitSize(newSize); + } + + /** + * Sends a {@link IPacket} using this FragmentSender. + * + * @param packetIn The packet to fragment and send. + */ + public void sendPacket(IPacket packetIn) { + if (packetIn == null) throw new NullPointerException("packetIn is null"); + synchronized (slock) { + inputQueue.add(packetIn); + } + } + + /** + * Sends the current {@link IPacket}s from the FragmentSender. + * + * @return The packets to send. + * @throws PacketException A Packet Exception has occurred. + */ + public IPacket[] sendPacket() throws PacketException { + synchronized (slock) { + ArrayList toret = new ArrayList<>(); + while (inputQueue.size() > 0) allocationInputs.put(UUID.randomUUID(), packetLoader.writePacketNoDigest(inputQueue.poll(), true)); + for (UUID c : allocationInputs.keySet()) { + toret.add(new FragmentAllocatePacket(getNumberOfFragments(allocationInputs.get(c)), c)); + } + for (int key : registry.keySet()) { + IPacket packet = registry.get(key).getSendPacket(); + if (packet != null) toret.add(packet); + } + return toret.toArray(new IPacket[0]); + } + } + + /** + * Receives a {@link IPacket} into the FragmentSender. + * + * @param packetIn The packet to receive. + * @throws PacketException A Packet Exception has occurred. + */ + public void receivePacket(IPacket packetIn) throws PacketException { + if (packetIn == null || !packetIn.isValid()) return; + if (packetIn instanceof FragmentPIDPacket) { + int currentID = ((FragmentPIDPacket) packetIn).getPacketID(); + synchronized (slock) { + if (packetIn instanceof FragmentAllocationPacket && allocationInputs.containsKey(((FragmentAllocationPacket) packetIn).getAllocationID()) && ((FragmentAllocationPacket) packetIn).allocationSuccessful()) { + registry.put(currentID, new FragmentOutput(currentID, allocationInputs.get(((FragmentAllocationPacket) packetIn).getAllocationID()))); + allocationInputs.remove(((FragmentAllocationPacket) packetIn).getAllocationID()); + } else { + FragmentOutput fragmentOutput = registry.get(currentID); + if (fragmentOutput != null && fragmentOutput.shouldBeRemovedReceivePacket(packetIn)) { + registry.remove(currentID); + synchronized (slockfinish) { + finishedIDs.add(currentID); + slockfinish.notify(); + } + } + } + } + } + } + + /** + * Gets whether packets are waiting for allocation. + * + * @return If packets are waiting for allocation. + */ + public boolean arePacketsWaiting() { + synchronized (slock) { + return inputQueue.size() > 0; + } + } + + /** + * Clears the currently waiting packets. + */ + public void clearWaitingPackets() { + synchronized (slock) { + inputQueue.clear(); + allocationInputs.clear(); + } + } + + /** + * Deletes a packet from the registry. + * + * @param id The ID of the packet to remove. + */ + public void deletePacketFromRegistry(int id) { + synchronized (slock) { + registry.remove(id); + } + } + + /** + * Clears the registry. + * NOTE: Do NOT do this unless you are finished with the FragmentSender. + */ + public void clearRegistry() { + synchronized (slock) { + registry.clear(); + } + } + + /** + * Gets whether finished IDs are waiting for obtaining. + * + * @return If finished IDs are waiting for obtaining. + */ + public boolean areFinishedIDsWaiting() { + synchronized (slockfinish) { + return finishedIDs.size() > 0; + } + } + + /** + * Gets the last finished packet ID. + * This method blocks until a packet finishes processing. + * + * @return The last finished packet ID. + * @throws InterruptedException The Thread was Interrupted. + */ + public int getLastIDFinished() throws InterruptedException { + synchronized (slockfinish) { + while (finishedIDs.size() < 1) slockfinish.wait(); + Integer polled = finishedIDs.poll(); + return (polled == null) ? -1 : polled; + } + } + + /** + * Clears all the last finished packet IDs. + */ + public void clearLastIDFinished() { + synchronized (slockfinish) { + finishedIDs.clear(); + } + } + + private int getNumberOfFragments(byte[] toSplit) { + return (int) Math.ceil((double) toSplit.length / (double) splitSize); + } + + /** + * Gets the current packet split size in bytes. + * + * @return The current packet split size. + */ + public int getSplitSize() { + return splitSize; + } + + /** + * Sets the packet split size in bytes. + * + * @param newSize The new packet split size. + * @throws IllegalArgumentException newSize is less than 1. + */ + public void setSplitSize(int newSize) { + if (newSize < 1) throw new IllegalArgumentException("newSize is less than 1"); + synchronized (slock) { + splitSize = newSize; + } + } + + /** + * Gets the {@link PacketLoader} in use. + * + * @return The PacketLoader in use. + */ + public PacketLoader getPacketLoader() { + return packetLoader; + } + + /** + * Sets the {@link PacketLoader} to use. + * + * @param loader The packet loader to use. + * @throws NullPointerException loader is null. + */ + public void setPacketLoader(PacketLoader loader) { + if (loader == null) throw new NullPointerException("loader is null"); + synchronized (slock) { + packetLoader = loader; + } + } + + /** + * Gets whether responses should be verified. + * + * @return Should responses be verified. + */ + public boolean shouldVerifyResponses() { + return verifyResponses; + } + + /** + * Sets whether responses should be verified. + * + * @param state If responses should be verified. + */ + public void setResponseVerification(boolean state) { + synchronized (slock) { + verifyResponses = state; + } + } + + /** + * This class provides the ability to get the next fragment packet information for sending. + * The next packet for sending will change as signalling packets come through. + * + * @author Captain ALM + */ + private final class FragmentOutput { + public final int packetID; + private final FragmentMessagePacket[] messagePackets; + private final ArrayList msgToResend = new ArrayList<>(); + private final ArrayList msgToResendCurrent = new ArrayList<>(); + private int msgPacketIndex = 0; + public boolean isResending = false; + + public FragmentOutput(int id, byte[] toSplit) { + packetID = id; + messagePackets = new FragmentMessagePacket[getNumberOfFragments(toSplit)]; + int index = 0; + for (int i = 0; i < messagePackets.length; i++) { + byte[] toInput = new byte[(index + splitSize > toSplit.length) ? toSplit.length - index : splitSize]; + System.arraycopy(toSplit, index, toInput, 0, toInput.length); + messagePackets[i] = new FragmentMessagePacket(packetID, i, toInput); + msgToResend.add(i); + index += toInput.length; + } + } + + public IPacket getSendPacket() { + if (msgPacketIndex < 0) { + msgPacketIndex = 0; + return new FragmentRetrySendPacket(packetID, true); + } + if (isResending) { + if (msgPacketIndex < msgToResendCurrent.size()) return messagePackets[msgToResendCurrent.get(msgPacketIndex++)]; + } else { + if (msgPacketIndex < messagePackets.length) return messagePackets[msgPacketIndex++]; + } + return new FragmentSendCompletePacket(packetID, false); + } + + public boolean shouldBeRemovedReceivePacket(IPacket packetIn) { + if (packetIn instanceof FragmentSendStopPacket || (packetIn instanceof FragmentSendCompletePacket && ((FragmentSendCompletePacket) packetIn).isAcknowledgement())) { + msgPacketIndex = messagePackets.length; + return true; + } + if (packetIn instanceof FragmentMessageResponsePacket) { + FragmentMessageResponsePacket responsePacket = (FragmentMessageResponsePacket)packetIn; + if (!verifyResponses || compareData(responsePacket.getFragmentMessage(), messagePackets[responsePacket.getFragmentID()].getFragmentMessage())) + msgToResend.remove(responsePacket.getFragmentID()); + } + if (packetIn instanceof FragmentRetrySendPacket && !((FragmentRetrySendPacket) packetIn).isAcknowledgement()) { + msgPacketIndex = -1; + isResending = true; + msgToResendCurrent.clear(); + msgToResendCurrent.addAll(msgToResend); + } + return false; + } + + private boolean compareData(byte[] data1, byte[] data2) { + if ((data1 == null && data2 != null) || (data1 != null && data2 == null)) return false; + if (data1 == data2) return true; + if (data1.length != data2.length) return false; + for (int i = 0; i < data1.length; i++) if (data1[i] != data2[i]) return false; + return true; + } + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/IAcknowledgement.java b/src/com/captainalm/lib/calmnet/packet/IAcknowledgement.java new file mode 100644 index 0000000..d49a6d5 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/IAcknowledgement.java @@ -0,0 +1,15 @@ +package com.captainalm.lib.calmnet.packet; + +/** + * This interface allows obtaining if the class instance is an Acknowledgement. + * + * @author Captain ALM + */ +public interface IAcknowledgement { + /** + * Gets if the class instance is an Acknowledgement. + * + * @return If the class instance is an Acknowledgement. + */ + boolean isAcknowledgement(); +} diff --git a/src/com/captainalm/lib/calmnet/packet/IPacket.java b/src/com/captainalm/lib/calmnet/packet/IPacket.java new file mode 100644 index 0000000..40d7120 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/IPacket.java @@ -0,0 +1,39 @@ +package com.captainalm.lib.calmnet.packet; + +/** + * This interface provides the packet methods. + * + * @author Captain ALM + */ +public interface IPacket { + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + boolean isValid(); + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + PacketProtocolInformation getProtocol(); + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + byte[] savePayload() throws PacketException; + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + void loadPayload(byte[] packetData) throws PacketException; +} diff --git a/src/com/captainalm/lib/calmnet/packet/IPacketFactory.java b/src/com/captainalm/lib/calmnet/packet/IPacketFactory.java new file mode 100644 index 0000000..b902dc5 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/IPacketFactory.java @@ -0,0 +1,31 @@ +package com.captainalm.lib.calmnet.packet; + +/** + * This interface provides the ability to construct {@link IPacket}s given their {@link PacketProtocolInformation}. + * + * @author Captain ALM + */ +public interface IPacketFactory { + /** + * Constructs a {@link IPacket} of the protocol specified by the passed {@link PacketProtocolInformation} instance. + * + * @param information The protocol information to use. + * @throws NullPointerException The information is null. + * @return The constructed packet or null. + */ + IPacket getPacket(PacketProtocolInformation information); + + /** + * Gets if {@link #getPacket(PacketProtocolInformation)} prefers returning {@link IStreamedPacket}s if possible. + * + * @return If streamed packets are preferred for construction. + */ + boolean areStreamPacketsPreferred(); + + /** + * Sets if {@link #getPacket(PacketProtocolInformation)} prefers returning {@link IStreamedPacket}s if possible. + * + * @param preferred If streamed packets are preferred for construction. + */ + void setStreamPacketsPreferred(boolean preferred); +} diff --git a/src/com/captainalm/lib/calmnet/packet/IStreamedPacket.java b/src/com/captainalm/lib/calmnet/packet/IStreamedPacket.java new file mode 100644 index 0000000..1fb91b2 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/IStreamedPacket.java @@ -0,0 +1,42 @@ +package com.captainalm.lib.calmnet.packet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This interface provides the streaming packet methods. + * + * @author Captain ALM + */ +public interface IStreamedPacket extends IPacket { + /** + * Reads payload data to an {@link OutputStream}. + * + * @param outputStream The output stream to read data to. + * @throws NullPointerException outputStream is null. + * @throws IOException An IO Exception has occurred. + * @throws PacketException An Exception has occurred. + */ + void readData(OutputStream outputStream) throws IOException, PacketException; + + /** + * Writes payload data from an {@link InputStream}. + * + * @param inputStream The input stream to write data from. + * @param size The size of the input payload in bytes. + * @throws NullPointerException inputStream is null. + * @throws IllegalArgumentException size is less than 0. + * @throws IOException An IO Exception has occurred. + * @throws PacketException An Exception has occurred. + */ + void writeData(InputStream inputStream, int size) throws IOException, PacketException; + + /** + * Gets the size of the output data. + * + * @throws PacketException An Exception has occurred. + * @return The size of the output data in bytes. + */ + int getSize() throws PacketException; +} diff --git a/src/com/captainalm/lib/calmnet/packet/PacketException.java b/src/com/captainalm/lib/calmnet/packet/PacketException.java new file mode 100644 index 0000000..0ed5d50 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/PacketException.java @@ -0,0 +1,60 @@ +package com.captainalm.lib.calmnet.packet; + +import java.security.PrivilegedActionException; + +/** + * This class provides the packet exception wrapper class. + * See {@link PacketException#getCause()} to find out the underlying exception. + * + * @author Captain ALM + */ +public class PacketException extends Exception { + + /** + * Constructs a new exception with the specified detail message. The + * cause is not initialized, and may subsequently be initialized by + * a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public PacketException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public PacketException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail + * message of (cause==null ? null : cause.toString()) (which + * typically contains the class and detail message of cause). + * This constructor is useful for exceptions that are little more than + * wrappers for other throwables (for example, {@link + * PrivilegedActionException}). + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public PacketException(Throwable cause) { + super(cause); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/PacketLoader.java b/src/com/captainalm/lib/calmnet/packet/PacketLoader.java new file mode 100644 index 0000000..e4d601a --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/PacketLoader.java @@ -0,0 +1,422 @@ +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()); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/PacketProtocolInformation.java b/src/com/captainalm/lib/calmnet/packet/PacketProtocolInformation.java new file mode 100644 index 0000000..99bccc7 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/PacketProtocolInformation.java @@ -0,0 +1,116 @@ +package com.captainalm.lib.calmnet.packet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Objects; + +/** + * This class provides the packet protocol information for {@link IPacket}. + * + * @author Captain ALM + */ +public class PacketProtocolInformation { + + /** + * Constructs a new instance of PacketProtocolInformation. + * This is set to 0 Major, 0 Minor. + */ + public PacketProtocolInformation(){ + major = 0; + minor = 0; + } + + /** + * Constructs a new instance of PacketProtocolInformation. + * + * @param major The major version. + * @param minor The minor version. + */ + public PacketProtocolInformation(byte major, byte minor) { + this.major = major; + this.minor = minor; + } + + /** + * The major protocol of the packet. + */ + protected final byte major; + + /** + * The minor protocol of the packet. + */ + protected final byte minor; + + /** + * Gets the major protocol for the packet. + * + * @return The major protocol. + */ + public int getMajor() { + return major & 0xFF; + } + + /** + * Gets the minor protocol for the packet. + * + * @return The minor protocol. + */ + public int getMinor() { + return minor & 0xFF; + } + + /** + * Gets the {@link PacketProtocolInformation} of the packet. + * + * @param inputStream The input stream for reading. + * @return The protocol information. + * @throws NullPointerException The inputStream is null. + * @throws IOException A stream exception occurs. + */ + public static PacketProtocolInformation getProtocolInformation(InputStream inputStream) throws IOException { + if (inputStream == null) throw new NullPointerException("inputStream is null"); + byte major = PacketLoader.readByteFromInputStream(inputStream); + byte minor = PacketLoader.readByteFromInputStream(inputStream); + return new PacketProtocolInformation(major, minor); + } + + /** + * Saves the {@link PacketProtocolInformation} of the packet. + * + * @param outputStream The output stream for writing. + * @param information The protocol information. + * @throws NullPointerException A parameter is null. + * @throws IOException A stream exception occurs. + */ + public static void savePacketProtocolInformation(OutputStream outputStream, PacketProtocolInformation information) throws IOException { + if (outputStream == null) throw new NullPointerException("outputStream is null"); + if (information == null) throw new NullPointerException("information is null"); + outputStream.write(information.getMajor()); + outputStream.write(information.getMinor()); + } + + /** + * Gets whether this object equals the passed object. + * + * @param o The object to check. + * @return If the objects are equivalent. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PacketProtocolInformation)) return false; + PacketProtocolInformation that = (PacketProtocolInformation) o; + return major == that.major && minor == that.minor; + } + + /** + * Gets the hash code of the object. + * + * @return The hash code of the object. + */ + @Override + public int hashCode() { + return Objects.hash(major, minor); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/core/Base64Packet.java b/src/com/captainalm/lib/calmnet/packet/core/Base64Packet.java new file mode 100644 index 0000000..2881761 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/core/Base64Packet.java @@ -0,0 +1,243 @@ +package com.captainalm.lib.calmnet.packet.core; + +import com.captainalm.lib.calmnet.packet.*; +import com.captainalm.lib.calmnet.stream.LengthClampedInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Base64; + +/** + * This class provides a base64 encrypted packet that can hold an {@link IPacket}. + *

+ * Major ID: 255 + * Minor ID: 251 + *

+ * + * @author Captain ALM + */ +public class Base64Packet implements IStreamedPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 255, (byte) 251); + + protected final Object slock = new Object(); + protected PacketLoader loader; + protected IPacketFactory factory; + protected IPacket held; + protected byte[] encryptedCache; + + /** + * Constructs a new Base64Packet with the specified {@link IPacketFactory} and {@link PacketLoader}. + * + * @param factory The packet factory to use. + * @param loader The Packet Loader to use. + * @throws NullPointerException factory or loader is null. + */ + public Base64Packet(IPacketFactory factory, PacketLoader loader) { + this(factory, loader, null); + } + + /** + * Constructs a new Base64Packet with the specified {@link IPacketFactory}, {@link PacketLoader} and {@link IPacket}. + * + * @param factory The packet factory to use. + * @param loader The Packet Loader to use. + * @param packet The packet to store or null. + * @throws NullPointerException factory or loader is null. + */ + public Base64Packet(IPacketFactory factory, PacketLoader loader, IPacket packet) { + if (factory == null) throw new NullPointerException("factory is null"); + if (loader == null) throw new NullPointerException("loader is null"); + this.factory = factory; + held = packet; + this.loader = loader; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (held != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + protected void processEncryptedCache() throws PacketException { + if (encryptedCache == null) { + if (held == null) throw new PacketException("no data"); + encryptedCache = Base64.getEncoder().encode(loader.writePacketNoDigest(held, true)); + } + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + synchronized (slock) { + processEncryptedCache(); + return encryptedCache; + } + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + synchronized (slock) { + encryptedCache = packetData; + try { + byte[] payload = Base64.getDecoder().decode(encryptedCache); + held = loader.readPacketNoDigest(payload, factory, null); + } catch (IllegalArgumentException e) { + encryptedCache = null; + throw new PacketException(e); + } + } + } + + /** + * Reads payload data to an {@link OutputStream}. + * + * @param outputStream The output stream to read data to. + * @throws NullPointerException outputStream is null. + * @throws IOException An IO Exception has occurred. + * @throws PacketException An Exception has occurred. + */ + @Override + public void readData(OutputStream outputStream) throws IOException, PacketException { + if (outputStream == null) throw new NullPointerException("outputStream is null"); + synchronized (slock) { + processEncryptedCache(); + outputStream.write(encryptedCache); + } + } + + /** + * Writes payload data from an {@link InputStream}. + * + * @param inputStream The input stream to write data from. + * @param size The size of the input payload in bytes. + * @throws NullPointerException inputStream is null. + * @throws IllegalArgumentException size is less than 0. + * @throws IOException An IO Exception has occurred. + * @throws PacketException An Exception has occurred. + */ + @Override + public void writeData(InputStream inputStream, int size) throws IOException, PacketException { + if (inputStream == null) throw new NullPointerException("inputStream is null"); + if (size < 0) throw new IllegalArgumentException("size is less than 0"); + synchronized (slock) { + encryptedCache = null; + held = loader.readStreamedPacketNoDigest(Base64.getDecoder().wrap(new LengthClampedInputStream(inputStream, size)), factory, null); + } + } + + /** + * Gets the size of the output data. + * + * @return The size of the output data in bytes. + * @throws PacketException An Exception has occurred. + */ + @Override + public int getSize() throws PacketException { + synchronized (slock) { + processEncryptedCache(); + return encryptedCache.length; + } + } + + /** + * Gets the {@link PacketLoader} in use. + * + * @return The Packet Loader in use. + */ + public PacketLoader getPacketLoader() { + return loader; + } + + /** + * Sets the {@link PacketLoader} to use. + * + * @param loader The Packet Loader to use. + * @throws NullPointerException loader is null. + */ + public void setPacketLoader(PacketLoader loader) { + if (loader == null) throw new NullPointerException("loader is null"); + synchronized (slock) { + this.loader = loader; + } + } + + /** + * Gets the {@link IPacketFactory} in use. + * + * @return The Packet Factory in use. + */ + public IPacketFactory getFactory() { + return factory; + } + + /** + * Sets the {@link IPacketFactory} to use. + * + * @param factory The Packet Factory to use. + * @throws NullPointerException factory is null. + */ + public void setFactory(IPacketFactory factory) { + if (factory == null) throw new NullPointerException("factory is null"); + synchronized (slock) { + this.factory = factory; + } + } + + /** + * Gets the held packet or null. + * + * @return The packet or null. + */ + public IPacket getHeldPacket() { + return held; + } + + /** + * Sets the held packet. + * + * @param packet The new packet or null. + */ + public void setHeldPacket(IPacket packet) { + synchronized (slock) { + encryptedCache = null; + held = packet; + } + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/core/EncryptedPacket.java b/src/com/captainalm/lib/calmnet/packet/core/EncryptedPacket.java new file mode 100644 index 0000000..cee0e27 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/core/EncryptedPacket.java @@ -0,0 +1,458 @@ +package com.captainalm.lib.calmnet.packet.core; + +import com.captainalm.lib.calmnet.packet.*; +import com.captainalm.lib.calmnet.stream.LengthClampedInputStream; +import com.captainalm.lib.stdcrypt.encryption.CipherException; +import com.captainalm.lib.stdcrypt.encryption.ICipherFactory; + +import javax.crypto.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.readByteFromInputStream; + +/** + * This class provides an encrypted packet that can hold an {@link IPacket}. + *

+ * Major ID: 255 + * Minor ID: 252 + *

+ * + * @author Captain ALM + */ +public class EncryptedPacket implements IStreamedPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 255, (byte) 252); + + protected final Object slock = new Object(); + protected PacketLoader loader; + protected IPacketFactory factory; + protected IPacket held; + protected byte[] encryptedCache; + protected int trailingArrayLengthCache; + + protected Cipher cipher; + protected ICipherFactory cipherFactory; + + protected String trailingPassword; + + /** + * Constructs a new EncryptedPacket with the specified {@link IPacketFactory}, {@link PacketLoader} and {@link ICipherFactory}. + * + * @param factory The packet factory to use. + * @param loader The Packet Loader to use. + * @param cipherFactory The cipher factory to use. + * @throws NullPointerException factory, loader or cipherFactory is null. + */ + public EncryptedPacket(IPacketFactory factory, PacketLoader loader, ICipherFactory cipherFactory) { + this(factory, loader, cipherFactory, null); + } + + /** + * Constructs a new EncryptedPacket with the specified {@link IPacketFactory}, {@link PacketLoader}, {@link ICipherFactory} and {@link IPacket}. + * + * @param factory The packet factory to use. + * @param loader The Packet Loader to use. + * @param cipherFactory The cipher factory to use. + * @throws NullPointerException factory, loader or cipherFactory is null. + */ + public EncryptedPacket(IPacketFactory factory, PacketLoader loader, ICipherFactory cipherFactory, IPacket packet) { + if (factory == null) throw new NullPointerException("factory is null"); + if (loader == null) throw new NullPointerException("loader is null"); + if (cipherFactory == null) throw new NullPointerException("cipherFactory is null"); + this.factory = factory; + this.loader = loader; + this.cipher = null; + held = packet; + } + + protected void generateCipher(int opmode) throws PacketException { + if (cipher != null) { + try { + cipher.doFinal(); + } catch (BadPaddingException | IllegalBlockSizeException e) { + } + cipher = null; + } + try { + cipher = cipherFactory.getCipher(opmode); + } catch (CipherException e) { + throw new PacketException(e); + } + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (held != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + protected void processEncryptedCache() throws PacketException { + if (encryptedCache == null || cipherFactory.cipherAttributesModified()) { + if (held == null) throw new PacketException("no data"); + + generateCipher(Cipher.ENCRYPT_MODE); + + byte[] savedArray = loader.writePacketNoDigest(held, true); + byte[] trailingArray = (trailingPassword == null || trailingPassword.length() < 1) ? new byte[0] : trailingPassword.getBytes(StandardCharsets.UTF_8); + trailingArrayLengthCache = trailingArray.length; + byte[] toEncrypt = new byte[savedArray.length + trailingArray.length]; + + System.arraycopy(savedArray, 0, toEncrypt, 0, savedArray.length); + + if (trailingArray.length > 0) System.arraycopy(trailingArray, 0, toEncrypt, savedArray.length, trailingArray.length); + try { + encryptedCache = cipher.doFinal(toEncrypt); + } catch (BadPaddingException | IllegalBlockSizeException e) { + throw new PacketException(e); + } + } + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + synchronized (slock) { + processEncryptedCache(); + + int cipherLen = cipherFactory.getSettingsNoSecretsLength(); + byte[] toret = new byte[5 + cipherLen + ((trailingArrayLengthCache == 0) ? 0 : 4) + encryptedCache.length]; + + int index = 0; + toret[index++] = (byte) ((trailingPassword != null && trailingPassword.length() > 0) ? 1 : 0); + + int length = cipherLen; + + toret[index++] = (byte) (length / 16777216); + length %= 16777216; + toret[index++] = (byte) (length / 65536); + length %= 65536; + toret[index++] = (byte) (length / 256); + length %= 256; + toret[index++] = (byte) (length); + + System.arraycopy(cipherFactory.getSettingsNoSecrets(), 0, toret, index, cipherLen); index += cipherLen; + + if (trailingArrayLengthCache > 0) { + length = trailingArrayLengthCache; + + toret[index++] = (byte) (length / 16777216); + length %= 16777216; + toret[index++] = (byte) (length / 65536); + length %= 65536; + toret[index++] = (byte) (length / 256); + length %= 256; + toret[index++] = (byte) (length); + } + + System.arraycopy(encryptedCache, 0, toret, index, encryptedCache.length); + + return toret; + } + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + synchronized (slock) { + int index = 1; + + int cipherLenCache = (packetData[index++] & 0xff) * 16777216; + cipherLenCache += (packetData[index++] & 0xff) * 65536; + cipherLenCache += (packetData[index++] & 0xff) * 256; + cipherLenCache += (packetData[index++] & 0xff); + if (cipherLenCache < 1) throw new PacketException("cipher length less than 1"); + + byte[] cipherSettingsCache = new byte[cipherLenCache]; + System.arraycopy(packetData, index, cipherSettingsCache, 0, cipherLenCache); index += cipherLenCache; + try { + cipherFactory.setSettings(cipherSettingsCache); + } catch (CipherException e) { + throw new PacketException(e); + } + + generateCipher(Cipher.DECRYPT_MODE); + + trailingArrayLengthCache = 0; + if ((packetData[0] & 1) == 1) { + trailingArrayLengthCache = (packetData[index++] & 0xff) * 16777216; + trailingArrayLengthCache += (packetData[index++] & 0xff) * 65536; + trailingArrayLengthCache += (packetData[index++] & 0xff) * 256; + trailingArrayLengthCache += (packetData[index++] & 0xff); + if (trailingArrayLengthCache < 1) throw new PacketException("trailer length less than 1"); + } + + encryptedCache = new byte[packetData.length - index]; + System.arraycopy(packetData, index, encryptedCache, 0, encryptedCache.length); + + try { + byte[] decrypted = cipher.doFinal(encryptedCache); + byte[] thePacket = new byte[decrypted.length - trailingArrayLengthCache]; + + System.arraycopy(decrypted, 0, thePacket, 0, thePacket.length); + + if (trailingArrayLengthCache > 0) { + byte[] theTrailer = new byte[trailingArrayLengthCache]; + System.arraycopy(decrypted, thePacket.length, theTrailer, 0, trailingArrayLengthCache); + trailingPassword = new String(theTrailer, StandardCharsets.UTF_8); + } + + held = loader.readPacketNoDigest(thePacket, factory, null); + } catch (BadPaddingException | IllegalBlockSizeException e) { + throw new PacketException(e); + } + } + } + + /** + * Reads payload data to an {@link OutputStream}. + * + * @param outputStream The output stream to read data to. + * @throws NullPointerException outputStream is null. + * @throws IOException An IO Exception has occurred. + * @throws PacketException An Exception has occurred. + */ + @Override + public void readData(OutputStream outputStream) throws IOException, PacketException { + if (outputStream == null) throw new NullPointerException("outputStream is null"); + synchronized (slock) { + processEncryptedCache(); + + outputStream.write((trailingPassword != null && trailingPassword.length() > 0) ? 1 : 0); + + int length = cipherFactory.getSettingsNoSecretsLength(); + + PacketLoader.writeInteger(outputStream, length); + + outputStream.write(cipherFactory.getSettingsNoSecrets()); + + if (trailingArrayLengthCache > 0) PacketLoader.writeInteger(outputStream, trailingArrayLengthCache); + + outputStream.write(encryptedCache); + } + } + + /** + * Writes payload data from an {@link InputStream}. + * + * @param inputStream The input stream to write data from. + * @param size The size of the input payload in bytes. + * @throws NullPointerException inputStream is null. + * @throws IllegalArgumentException size is less than 0. + * @throws IOException An IO Exception has occurred. + * @throws PacketException An Exception has occurred. + */ + @Override + public void writeData(InputStream inputStream, int size) throws IOException, PacketException { + if (inputStream == null) throw new NullPointerException("inputStream is null"); + if (size < 0) throw new IllegalArgumentException("size is less than 0"); + synchronized (slock) { + if (size < 1) throw new IOException("inputStream end of stream"); + int flag = readByteFromInputStream(inputStream) & 0xff; + + if (size < 5) throw new IOException("inputStream end of stream"); + int cipherLenCache = (readByteFromInputStream(inputStream) & 0xff) * 16777216; + cipherLenCache += (readByteFromInputStream(inputStream) & 0xff) * 65536; + cipherLenCache += (readByteFromInputStream(inputStream) & 0xff) * 256; + cipherLenCache += (readByteFromInputStream(inputStream) & 0xff); + if (cipherLenCache < 1) throw new PacketException("cipher length less than 1"); + + if (size < 5 + cipherLenCache) throw new IOException("inputStream end of stream"); + byte[] cipherSettingsCache = new byte[cipherLenCache]; + int offset = 0; + int slen; + while (offset < cipherLenCache) if ((slen = inputStream.read(cipherSettingsCache, offset, size - cipherLenCache)) != -1) offset += slen; else throw new IOException("inputStream end of stream"); + try { + cipherFactory.setSettings(cipherSettingsCache); + } catch (CipherException e) { + throw new PacketException(e); + } + + generateCipher(Cipher.DECRYPT_MODE); + + trailingArrayLengthCache = 0; + if ((flag & 1) == 1) { + if (size < 9 + cipherLenCache) throw new IOException("inputStream end of stream"); + trailingArrayLengthCache = (readByteFromInputStream(inputStream) & 0xff) * 16777216; + trailingArrayLengthCache += (readByteFromInputStream(inputStream) & 0xff) * 65536; + trailingArrayLengthCache += (readByteFromInputStream(inputStream) & 0xff) * 256; + trailingArrayLengthCache += (readByteFromInputStream(inputStream) & 0xff); + if (trailingArrayLengthCache < 1) throw new PacketException("trailer length less than 1"); + } + + encryptedCache = null; + CipherInputStream cipherInputStream = new CipherInputStream(new LengthClampedInputStream(inputStream, size - 5 - cipherLenCache - (((flag & 1) == 1) ? 4 + trailingArrayLengthCache : 0)), cipher); + + held = loader.readStreamedPacketNoDigest(cipherInputStream, factory, null); + + if (trailingArrayLengthCache > 0) { + byte[] theTrailer = PacketLoader.readArrayFromInputStream(cipherInputStream, trailingArrayLengthCache); + trailingPassword = new String(theTrailer, StandardCharsets.UTF_8); + } + + try { + cipher.doFinal(); + } + catch (BadPaddingException | IllegalBlockSizeException e) { + } + } + } + + /** + * Gets the size of the output data. + * + * @return The size of the output data in bytes. + * @throws PacketException An Exception has occurred. + */ + @Override + public int getSize() throws PacketException { + synchronized (slock) { + processEncryptedCache(); + return encryptedCache.length + 5 + cipherFactory.getSettingsNoSecretsLength() + ((trailingArrayLengthCache == 0) ? 0 : 4); + } + } + + /** + * Gets the {@link PacketLoader} in use. + * + * @return The Packet Loader in use. + */ + public PacketLoader getPacketLoader() { + return loader; + } + + /** + * Sets the {@link PacketLoader} to use. + * + * @param loader The Packet Loader to use. + * @throws NullPointerException loader is null. + */ + public void setPacketLoader(PacketLoader loader) { + if (loader == null) throw new NullPointerException("loader is null"); + synchronized (slock) { + this.loader = loader; + } + } + + /** + * Gets the {@link IPacketFactory} in use. + * + * @return The Packet Factory in use. + */ + public IPacketFactory getFactory() { + return factory; + } + + /** + * Sets the {@link IPacketFactory} to use. + * + * @param factory The Packet Factory to use. + * @throws NullPointerException factory is null. + */ + public void setFactory(IPacketFactory factory) { + if (factory == null) throw new NullPointerException("factory is null"); + synchronized (slock) { + this.factory = factory; + } + } + + /** + * Gets the {@link ICipherFactory} being used. + * + * @return The Cipher Factory. + */ + public ICipherFactory getCipherFactory() { + return cipherFactory; + } + + /** + * Sets the {@link ICipherFactory} being used. + * + * @param cipherFactory The Cipher Factory. + * @throws NullPointerException cipherFactory is null. + */ + public void setCipherFactory(ICipherFactory cipherFactory) { + if (cipherFactory == null) throw new NullPointerException("cipherFactory is null"); + synchronized (slock) { + this.cipherFactory = cipherFactory; + } + } + + /** + * Gets the trailing password (Or null if no trailing password). + * + * @return The trailing password or null. + */ + public String getTrailingPassword() { + return trailingPassword; + } + + /** + * Sets the trailing password (Use null for no trailing password). + * + * @param trailingPassword The new trailing password or null. + */ + public void setTrailingPassword(String trailingPassword) { + synchronized (slock) { + encryptedCache = null; + this.trailingPassword = trailingPassword; + } + } + + /** + * Gets the held packet or null. + * + * @return The packet or null. + */ + public IPacket getHeldPacket() { + return held; + } + + /** + * Sets the held packet. + * + * @param packet The new packet or null. + */ + public void setHeldPacket(IPacket packet) { + synchronized (slock) { + encryptedCache = null; + held = packet; + } + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/core/NetworkEncryptionUpgradePacket.java b/src/com/captainalm/lib/calmnet/packet/core/NetworkEncryptionUpgradePacket.java new file mode 100644 index 0000000..d4d1b16 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/core/NetworkEncryptionUpgradePacket.java @@ -0,0 +1,199 @@ +package com.captainalm.lib.calmnet.packet.core; + +import com.captainalm.lib.calmnet.packet.IAcknowledgement; +import com.captainalm.lib.stdcrypt.encryption.CipherException; +import com.captainalm.lib.stdcrypt.encryption.ICipherFactory; +import com.captainalm.lib.calmnet.packet.IPacket; +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +/** + * This class provides the ability for supporting streams to upgrade to using password encrypted and / or base64 connections. + * This class can also signal the use changes for {@link Base64Packet}s or {@link EncryptedPacket}s. + *

+ * Major ID: 255 + * Minor ID: 253 + *

+ * + * @author Captain ALM + */ +public class NetworkEncryptionUpgradePacket implements IPacket, IAcknowledgement { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 255, (byte) 253); + + protected Boolean acknowledgement; + protected boolean upgrade; + protected boolean base64ed; + protected ICipherFactory cipherFactory; + + protected final Object slock = new Object(); + + /** + * Constructs a new NetworkEncryptionUpgradePacket with the specified acknowledgement value, upgrade value, base 64 value and {@link ICipherFactory}. + * + * @param acknowledgement The acknowledgement value to use (Can be null). + * @param upgrade Is the packet treated as a stream upgrade, See: {@link #isUpgrade()}. + * @param base64ed Is the packet signalling base 64 to be used. + * @param cipherFactory The cipherFactory to signal for use. + */ + public NetworkEncryptionUpgradePacket(Boolean acknowledgement, boolean upgrade, boolean base64ed, ICipherFactory cipherFactory) { + this.acknowledgement = acknowledgement; + this.upgrade = upgrade; + this.base64ed = base64ed; + this.cipherFactory = cipherFactory; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (acknowledgement != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + synchronized (slock) { + if (acknowledgement == null) throw new PacketException("no data"); + + byte[] cipherBytes = (cipherFactory == null) ? null : cipherFactory.getSettings(); + byte[] toret = new byte[2 + ((cipherBytes == null) ? 0 : cipherBytes.length)]; + toret[0] = (acknowledgement) ? (byte) 1 : (byte) 0; + toret[1] = (byte) (((upgrade) ? 1 : 0) + ((base64ed) ? 2 : 0)); + + if (cipherBytes != null) System.arraycopy(cipherBytes, 0, toret, 2, cipherBytes.length); + + return toret; + } + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length < 2) throw new PacketException("no data"); + synchronized (slock) { + acknowledgement = (packetData[0] == 1); + if (!acknowledgement && packetData[0] != 0) acknowledgement = null; + + upgrade = ((packetData[1] & 1) == 1); + base64ed = ((packetData[1] & 2) == 2); + + if (cipherFactory != null && packetData.length > 2) { + byte[] cipherBytes = new byte[packetData.length - 2]; + System.arraycopy(packetData, 2, cipherBytes, 0, cipherBytes.length); + try { + cipherFactory.setSettings(cipherBytes); + } catch (CipherException e) { + throw new PacketException(e); + } + } + } + } + + /** + * Gets if the packet is treated as a stream upgrade or + * a change in packet use for {@link EncryptedPacket} and {@link Base64Packet}. + * + * @return If the packet is a stream upgrade. + */ + public boolean isUpgrade() { + return upgrade; + } + + /** + * Sets if the packet is treated as a stream upgrade or + * a change in packet use for {@link EncryptedPacket} and {@link Base64Packet}. + * + * @param upgrade If the packet is a stream upgrade. + */ + public void setUpgrade(boolean upgrade) { + synchronized (slock) { + this.upgrade = upgrade; + } + } + + /** + * Gets if base 64 is used. + * (This is not for this packet, it is part of the upgrade attributes.) + * + * @return If base 64 is used. + */ + public boolean isBase64ed() { + return base64ed; + } + + /** + * Sets if base64 is used. + * (This is not for this packet, it is part of the upgrade attributes.) + * + * @param base64ed If base 64 is to be used. + */ + public void setBase64ed(boolean base64ed) { + synchronized (slock) { + this.base64ed = base64ed; + } + } + + /** + * Gets the {@link ICipherFactory} being used or null. + * + * @return The Cipher Factory or null. + */ + public ICipherFactory getCipherFactory() { + return cipherFactory; + } + + /** + * Sets the {@link ICipherFactory} being used. + * + * @param cipherFactory The Cipher Factory or null. + */ + public void setCipherFactory(ICipherFactory cipherFactory) { + synchronized (slock) { + this.cipherFactory = cipherFactory; + } + } + + /** + * Gets if the class instance is an Acknowledgement. + * + * @return If the class instance is an Acknowledgement. + */ + @Override + public boolean isAcknowledgement() { + return (acknowledgement != null && acknowledgement); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/core/NetworkIdentifierPacket.java b/src/com/captainalm/lib/calmnet/packet/core/NetworkIdentifierPacket.java new file mode 100644 index 0000000..32d8eee --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/core/NetworkIdentifierPacket.java @@ -0,0 +1,84 @@ +package com.captainalm.lib.calmnet.packet.core; + +import com.captainalm.lib.calmnet.packet.IPacket; +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +import java.nio.charset.StandardCharsets; + +/** + * This class provides a packet that is used to identify the type of network client is using this library. + *

+ * Major ID: 255 + * Minor ID: 255 + *

+ * + * @author Captain ALM + */ +public class NetworkIdentifierPacket implements IPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 255, (byte) 255); + protected String id; + + /** + * Constructs a new instance of NetworkIdentifierPacket with the specified ID. + * + * @param id The network ID of the client. + */ + public NetworkIdentifierPacket(String id) { + this.id = id; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (id != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (id == null) throw new PacketException("no data"); + return id.getBytes(StandardCharsets.UTF_8); + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + id = new String(packetData, StandardCharsets.UTF_8); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/core/NetworkSSLUpgradePacket.java b/src/com/captainalm/lib/calmnet/packet/core/NetworkSSLUpgradePacket.java new file mode 100644 index 0000000..ff1a551 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/core/NetworkSSLUpgradePacket.java @@ -0,0 +1,96 @@ +package com.captainalm.lib.calmnet.packet.core; + +import com.captainalm.lib.calmnet.packet.IAcknowledgement; +import com.captainalm.lib.calmnet.packet.IPacket; +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +/** + * This class provides the ability for supporting streams to upgrade to using SSL connections. + *

+ * Major ID: 255 + * Minor ID: 254 + *

+ * + * @author Captain ALM + */ +public class NetworkSSLUpgradePacket implements IPacket, IAcknowledgement { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 255, (byte) 254); + + protected Boolean acknowledgement; + + /** + * Constructs a new NetworkSSLUpgrade packet with the specified acknowledgement value. + * + * @param acknowledgement The acknowledgement value to use (Can be null). + */ + public NetworkSSLUpgradePacket(Boolean acknowledgement) { + this.acknowledgement = acknowledgement; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (acknowledgement != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (acknowledgement == null) throw new PacketException("no data"); + return new byte[] {((acknowledgement) ? (byte) 1 : (byte) 0)}; + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length != 1) throw new PacketException("no data"); + acknowledgement = (packetData[0] == 1); + if (!acknowledgement && packetData[0] != 0) acknowledgement = null; + } + + /** + * Gets if the class instance is an Acknowledgement. + * + * @return If the class instance is an Acknowledgement. + */ + @Override + public boolean isAcknowledgement() { + return (acknowledgement != null && acknowledgement); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/core/package-info.java b/src/com/captainalm/lib/calmnet/packet/core/package-info.java new file mode 100644 index 0000000..d6e7d13 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/core/package-info.java @@ -0,0 +1,7 @@ +/** + * This package contains the core network packets. + * Major ID: 255 + * + * @author Captain ALM + */ +package com.captainalm.lib.calmnet.packet.core; \ No newline at end of file diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentAllocatePacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentAllocatePacket.java new file mode 100644 index 0000000..7fa8b82 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentAllocatePacket.java @@ -0,0 +1,123 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.IPacket; +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.getByteArrayFromInteger; +import static com.captainalm.lib.calmnet.packet.PacketLoader.getIntegerFromByteArray; + +/** + * This class provides a packet for fragment allocation requesting. + * The response packet is: {@link FragmentAllocationPacket}. + *

+ * Major ID: 254 + * Minor ID: 1 + *

+ * + * @author Captain ALM + */ +public class FragmentAllocatePacket implements IPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 1); + + protected Integer fragmentCount; + protected UUID allocationID; + + /** + * Constructs a new FragmentAllocatePacket given the fragment count and allocation UUID. + * + * @param fragmentCount The number of fragments to allocate. + * @param allocationID The allocation ID. + * @throws IllegalArgumentException fragmentCount is less than 1. + */ + public FragmentAllocatePacket(Integer fragmentCount, UUID allocationID) { + if (fragmentCount != null && fragmentCount < 1) throw new IllegalArgumentException("fragmentCount is less than 1"); + this.fragmentCount = fragmentCount; + this.allocationID = (allocationID == null) ? UUID.randomUUID() : allocationID; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (fragmentCount != null) && (allocationID != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (fragmentCount == null || allocationID == null) throw new PacketException("no data"); + ByteBuffer buffer = ByteBuffer.wrap(new byte[20]); + buffer.put(getByteArrayFromInteger(fragmentCount)); + buffer.putLong(allocationID.getMostSignificantBits()); + buffer.putLong(allocationID.getLeastSignificantBits()); + return buffer.array(); + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length != 20) throw new PacketException("packet length is not 20"); + ByteBuffer buffer = ByteBuffer.wrap(packetData); + byte[] toProcess = new byte[4]; + buffer.get(toProcess); + fragmentCount = getIntegerFromByteArray(toProcess); + long mostSig = buffer.getLong(); + allocationID = new UUID(mostSig, buffer.getLong()); + } + + /** + * Gets the number of fragments or null. + * + * @return The number of fragments or null. + */ + public Integer getFragmentCount() { + return fragmentCount; + } + + /** + * Gets the allocation ID or null. + * + * @return The allocation ID or null. + */ + public UUID getAllocationID() { + return allocationID; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentAllocationPacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentAllocationPacket.java new file mode 100644 index 0000000..61478be --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentAllocationPacket.java @@ -0,0 +1,126 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.getByteArrayFromInteger; +import static com.captainalm.lib.calmnet.packet.PacketLoader.getIntegerFromByteArray; + +/** + * This class provides a packet for giving the allocated packetID + * as a response for {@link FragmentAllocatePacket}. + *

+ * Major ID: 254 + * Minor ID: 2 + *

+ * + * @author Captain ALM + */ +public class FragmentAllocationPacket extends FragmentPIDPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 2); + + protected Boolean success; + protected UUID allocationID; + + /** + * Constructs a new FragmentAllocationPacket given the packet ID, allocation ID and if it's successful. + * + * @param packetID The packet ID. + * @param allocationID The allocation ID. + * @param success The allocation was successful. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentAllocationPacket(Integer packetID, UUID allocationID, Boolean success) { + super(packetID); + this.success = success; + this.allocationID = allocationID; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return super.isValid() && (success != null) && (allocationID != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (packetID == null || success == null || allocationID == null) throw new PacketException("no data"); + ByteBuffer buffer = ByteBuffer.wrap(new byte[21]); + buffer.put(getByteArrayFromInteger(packetID)); + buffer.put((success) ? (byte) 1 : (byte) 0); + buffer.putLong(allocationID.getMostSignificantBits()); + buffer.putLong(allocationID.getLeastSignificantBits()); + return buffer.array(); + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length != 21) throw new PacketException("packet length is not 21"); + ByteBuffer buffer = ByteBuffer.wrap(packetData); + byte[] toProcess = new byte[4]; + buffer.get(toProcess); + packetID = getIntegerFromByteArray(toProcess); + success = (buffer.get() == 1); + if (!success && packetData[4] != 0) success = null; + long mostSig = buffer.getLong(); + allocationID = new UUID(mostSig, buffer.getLong()); + } + + /** + * Checks if the allocation is successful. + * + * @return If the allocation was successful. + */ + public boolean allocationSuccessful() { + return (success != null && success); + } + + /** + * Gets the allocation ID or null. + * + * @return The allocation ID or null. + */ + public UUID getAllocationID() { + return allocationID; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentMessagePacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentMessagePacket.java new file mode 100644 index 0000000..d644d9c --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentMessagePacket.java @@ -0,0 +1,71 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +/** + * This class provides a packet for sending a payload with a packetID and fragmentID. + * The response packet is: {@link FragmentMessageResponsePacket}. + *

+ * Major ID: 254 + * Minor ID: 3 + *

+ * + * @author Captain ALM + */ +public class FragmentMessagePacket extends FragmentPIDMSGPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 3); + + /** + * Constructs a new FragmentMessagePacket given the packet ID, fragment ID and payload. + * + * @param packetID The packet ID. + * @param fragmentID The fragment ID. + * @param payload The payload to store. + * @throws IllegalArgumentException packetID or fragmentID is less than 0. + */ + public FragmentMessagePacket(Integer packetID, Integer fragmentID, byte[] payload) { + super(packetID, fragmentID, payload); + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return super.isValid() && (payload != null); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (payload == null) throw new PacketException("no data"); + return super.savePayload(); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentMessageResponsePacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentMessageResponsePacket.java new file mode 100644 index 0000000..dcd4244 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentMessageResponsePacket.java @@ -0,0 +1,48 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +/** + * This class provides a packet for sending an optional payload with a packetID and fragmentID + * as a response for {@link FragmentMessagePacket}. + *

+ * Major ID: 254 + * Minor ID: 4 + *

+ * + * @author Captain ALM + */ +public class FragmentMessageResponsePacket extends FragmentPIDMSGPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 4); + + /** + * Constructs a new FragmentMessageResponsePacket given the packet ID, fragment ID and payload. + * + * @param packetID The packet ID. + * @param fragmentID The fragment ID. + * @param payload The payload to store. + * @throws IllegalArgumentException packetID or fragmentID is less than 0. + */ + public FragmentMessageResponsePacket(Integer packetID, Integer fragmentID, byte[] payload) { + super(packetID, fragmentID, payload); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDAKNPacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDAKNPacket.java new file mode 100644 index 0000000..589a782 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDAKNPacket.java @@ -0,0 +1,81 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.IAcknowledgement; +import com.captainalm.lib.calmnet.packet.PacketException; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.getByteArrayFromInteger; +import static com.captainalm.lib.calmnet.packet.PacketLoader.getIntegerFromByteArray; + +/** + * This abstract base class provides the ability for packets to contain an ID and if it is an Acknowledgement. + * + * @author Captain ALM + */ +public abstract class FragmentPIDAKNPacket extends FragmentPIDPacket implements IAcknowledgement { + protected Boolean acknowledgement; + + /** + * Constructs a new FragmentPIDPacket given the packet ID and the acknowledgement value. + * + * @param packetID The packet ID. + * @param acknowledgement The acknowledgement value to use. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentPIDAKNPacket(Integer packetID, Boolean acknowledgement) { + super(packetID); + this.acknowledgement = acknowledgement; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return super.isValid() && (acknowledgement != null); + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (packetID == null || acknowledgement == null) throw new PacketException("no data"); + byte[] toret = new byte[5]; + System.arraycopy(getByteArrayFromInteger(packetID), 0, toret, 0, 4); + toret[4] = (acknowledgement) ? (byte) 1 : (byte) 0; + return toret; + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length != 5) throw new PacketException("packet length is not 5"); + byte[] toProcess = new byte[4]; + System.arraycopy(packetData, 0, toProcess, 0, 4); + packetID = getIntegerFromByteArray(toProcess); + acknowledgement = (packetData[4] == 1); + if (!acknowledgement && packetData[4] != 0) acknowledgement = null; + } + + /** + * Gets if the class instance is an Acknowledgement. + * + * @return If the class instance is an Acknowledgement. + */ + @Override + public boolean isAcknowledgement() { + return (acknowledgement != null && acknowledgement); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDMSGPacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDMSGPacket.java new file mode 100644 index 0000000..7f7e03e --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDMSGPacket.java @@ -0,0 +1,96 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketException; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.getByteArrayFromInteger; +import static com.captainalm.lib.calmnet.packet.PacketLoader.getIntegerFromByteArray; + +/** + * This abstract base class provides the ability for packets to contain an ID, a Fragment ID and a payload. + * + * @author Captain ALM + */ +public abstract class FragmentPIDMSGPacket extends FragmentPIDPacket { + protected Integer fragmentID; + protected byte[] payload; + + /** + * Constructs a new FragmentPIDMSGPacket given the packet ID, fragment ID and payload. + * + * @param packetID The packet ID. + * @param fragmentID The fragment ID. + * @param payload The payload to store. + * @throws IllegalArgumentException packetID or fragmentID is less than 0. + */ + public FragmentPIDMSGPacket(Integer packetID, Integer fragmentID, byte[] payload) { + super(packetID); + if (fragmentID != null && fragmentID < 0) throw new IllegalArgumentException("fragmentID is less than 0"); + this.fragmentID = fragmentID; + this.payload = payload; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return super.isValid() && (fragmentID != null); + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (packetID == null || fragmentID == null) throw new PacketException("no data"); + byte[] localPayload = (payload == null) ? new byte[0] : payload; + byte[] toret = new byte[8 + localPayload.length]; + System.arraycopy(getByteArrayFromInteger(packetID), 0, toret, 0, 4); + System.arraycopy(getByteArrayFromInteger(fragmentID), 0, toret, 4, 4); + System.arraycopy(localPayload, 0, toret, 8, localPayload.length); + return toret; + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length < 8) throw new PacketException("packet length is less than 8"); + byte[] toProcess = new byte[4]; + System.arraycopy(packetData, 0, toProcess, 0, 4); + packetID = getIntegerFromByteArray(toProcess); + System.arraycopy(packetData, 4, toProcess, 0, 4); + fragmentID = getIntegerFromByteArray(toProcess); + payload = new byte[packetData.length - 8]; + System.arraycopy(packetData, 8, payload, 0, payload.length); + } + + /** + * Gets the fragment message byte array or null. + * + * @return The byte array or null. + */ + public byte[] getFragmentMessage() { + return payload; + } + + /** + * Gets the fragment ID or null. + * + * @return The fragment ID or null. + */ + public Integer getFragmentID() { + return fragmentID; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDPacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDPacket.java new file mode 100644 index 0000000..c49f9ad --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentPIDPacket.java @@ -0,0 +1,42 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.IPacket; + +/** + * This abstract base class provides the ability for packets to return an ID. + * + * @author Captain ALM + */ +public abstract class FragmentPIDPacket implements IPacket { + protected Integer packetID; + + /** + * Constructs a new FragmentPIDPacket given the packet ID. + * + * @param packetID The packet ID. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentPIDPacket(Integer packetID) { + if (packetID != null && packetID < 0) throw new IllegalArgumentException("packetID is less than 0"); + this.packetID = packetID; + } + + /** + * Gets if the packet is valid. + * + * @return Is the packet valid? + */ + @Override + public boolean isValid() { + return (packetID != null); + } + + /** + * Gets the packet ID or null. + * + * @return The packet ID or null. + */ + public Integer getPacketID() { + return packetID; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentRetrySendPacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentRetrySendPacket.java new file mode 100644 index 0000000..64ee1cd --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentRetrySendPacket.java @@ -0,0 +1,47 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +/** + * This class provides a packet for signalling that the sending end + * should start re-sending un acknowledged fragment packets. + *

+ * Major ID: 254 + * Minor ID: 6 + *

+ * + * @author Captain ALM + */ +public class FragmentRetrySendPacket extends FragmentPIDAKNPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 6); + + /** + * Constructs a new FragmentRetrySendPacket given the packet ID and the acknowledgement value. + * + * @param packetID The packet ID. + * @param acknowledgement The acknowledgement value to use. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentRetrySendPacket(Integer packetID, Boolean acknowledgement) { + super(packetID, acknowledgement); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendCompletePacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendCompletePacket.java new file mode 100644 index 0000000..5b2f664 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendCompletePacket.java @@ -0,0 +1,47 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +/** + * This class provides a packet for signalling that all fragments for a packet have been sent or that sending has been successfully cancelled. + *

+ * Major ID: 254 + * Minor ID: 5 + *

+ * + * @author Captain ALM + */ +public class FragmentSendCompletePacket extends FragmentPIDAKNPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 5); + + /** + * Constructs a new FragmentSendCompletePacket given the packet ID and the acknowledgement value. + * + * @param packetID The packet ID. + * @param acknowledgement The acknowledgement value to use. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentSendCompletePacket(Integer packetID, Boolean acknowledgement) { + super(packetID, acknowledgement); + } + + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendStopPacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendStopPacket.java new file mode 100644 index 0000000..3203cff --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendStopPacket.java @@ -0,0 +1,76 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.FragmentSender; +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.getByteArrayFromInteger; +import static com.captainalm.lib.calmnet.packet.PacketLoader.getIntegerFromByteArray; + +/** + * This class provides a packet for stopping the remote {@link FragmentSender}. + *

+ * Major ID: 254 + * Minor ID: 7 + *

+ * + * @author Captain ALM + */ +public class FragmentSendStopPacket extends FragmentPIDPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 7); + + /** + * Constructs a new FragmentSendStopPacket given the packet ID. + * + * @param packetID The packet ID. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentSendStopPacket(Integer packetID) { + super(packetID); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (packetID == null) throw new PacketException("no data"); + return getByteArrayFromInteger(packetID); + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length != 4) throw new PacketException("packet length is not 4"); + packetID = getIntegerFromByteArray(packetData); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendVerifyCompletePacket.java b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendVerifyCompletePacket.java new file mode 100644 index 0000000..d5d925a --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/FragmentSendVerifyCompletePacket.java @@ -0,0 +1,75 @@ +package com.captainalm.lib.calmnet.packet.fragment; + +import com.captainalm.lib.calmnet.packet.PacketException; +import com.captainalm.lib.calmnet.packet.PacketProtocolInformation; + +import static com.captainalm.lib.calmnet.packet.PacketLoader.getByteArrayFromInteger; +import static com.captainalm.lib.calmnet.packet.PacketLoader.getIntegerFromByteArray; + +/** + * This class provides a packet for stating that all packets have been successfully sent and verified. + *

+ * Major ID: 254 + * Minor ID: 8 + *

+ * + * @author Captain ALM + */ +public class FragmentSendVerifyCompletePacket extends FragmentPIDPacket { + private static final PacketProtocolInformation protocol = new PacketProtocolInformation((byte) 254, (byte) 8); + + /** + * Constructs a new FragmentSendVerifyCompletePacket given the packet ID. + * + * @param packetID The packet ID. + * @throws IllegalArgumentException packetID is less than 0. + */ + public FragmentSendVerifyCompletePacket(Integer packetID) { + super(packetID); + } + + /** + * Gets the protocol information. + * + * @return The protocol information. + */ + @Override + public PacketProtocolInformation getProtocol() { + return protocol; + } + + /** + * Gets the protocol information statically. + * + * @return The protocol information. + */ + public static PacketProtocolInformation getTheProtocol() { + return protocol; + } + + /** + * Saves the packet payload to a byte array. + * + * @return The packet payload data. + * @throws PacketException An Exception has occurred. + */ + @Override + public byte[] savePayload() throws PacketException { + if (packetID == null) throw new PacketException("no data"); + return getByteArrayFromInteger(packetID); + } + + /** + * Loads the packet payload from save data. + * + * @param packetData The packet payload data. + * @throws NullPointerException The new store data is null. + * @throws PacketException An Exception has occurred. + */ + @Override + public void loadPayload(byte[] packetData) throws PacketException { + if (packetData == null) throw new NullPointerException("packetData is null"); + if (packetData.length != 4) throw new PacketException("packet length is not 4"); + packetID = getIntegerFromByteArray(packetData); + } +} diff --git a/src/com/captainalm/lib/calmnet/packet/fragment/package-info.java b/src/com/captainalm/lib/calmnet/packet/fragment/package-info.java new file mode 100644 index 0000000..f178eb5 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/fragment/package-info.java @@ -0,0 +1,7 @@ +/** + * This package contains the fragment network packets. + * Major ID: 254 + * + * @author Captain ALM + */ +package com.captainalm.lib.calmnet.packet.fragment; \ No newline at end of file diff --git a/src/com/captainalm/lib/calmnet/packet/package-info.java b/src/com/captainalm/lib/calmnet/packet/package-info.java new file mode 100644 index 0000000..f3c90fa --- /dev/null +++ b/src/com/captainalm/lib/calmnet/packet/package-info.java @@ -0,0 +1,6 @@ +/** + * This package contains the network packets and handling code. + * + * @author Captain ALM + */ +package com.captainalm.lib.calmnet.packet; \ No newline at end of file diff --git a/src/com/captainalm/lib/calmnet/stream/LengthClampedInputStream.java b/src/com/captainalm/lib/calmnet/stream/LengthClampedInputStream.java new file mode 100644 index 0000000..c99eb20 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/stream/LengthClampedInputStream.java @@ -0,0 +1,133 @@ +package com.captainalm.lib.calmnet.stream; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * This class provides the ability to limit the number of bytes read from the underlying stream. + * When the limit is reached, this class considers that state as end of stream. + * + * @author Captain ALM + */ +public class LengthClampedInputStream extends FilterInputStream { + protected boolean closed; + protected int clampedLength; + protected int markReadLimit; + protected int markResetLength; + + /** + * Creates a LengthClampedInputStream with the specified {@link InputStream} + * and the maximum number of bytes that can be read from the stream. + * + * @param inputStream The input stream to clamp. + * @param length The maximum number of bytes that can be read before end of stream is reached. + * @throws NullPointerException inputStream is null. + * @throws IllegalArgumentException length is less than 0. + */ + public LengthClampedInputStream(InputStream inputStream, int length) { + super(inputStream); + if (inputStream == null) throw new NullPointerException("inputStream is null"); + if (length < 0) throw new IllegalArgumentException("length is less than 0"); + clampedLength = length; + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + if (closed) throw new IOException("stream closed"); + if (clampedLength > 0) clampedLength--; else return -1; + decrementMarkResetLength(); + return super.read(); + } + + protected synchronized void decrementMarkResetLength() { + if (markResetLength >= 0) markResetLength--; + } + + /** + * Returns an estimate of the number of bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * caller of a method for this input stream. The next caller might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking. + * @exception IOException if an I/O error occurs. + */ + @Override + public int available() throws IOException { + if (closed) throw new IOException("stream closed"); + return Math.min(super.available(), clampedLength); + } + + /** + * Marks the current position in this input stream. A subsequent + * call to the reset method repositions this stream at + * the last marked position so that subsequent reads re-read the same bytes. + *

+ * The readlimit argument tells this input stream to + * allow that many bytes to be read before the mark position gets + * invalidated. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + */ + @Override + public synchronized void mark(int readlimit) { + if (super.markSupported()) { + super.mark(readlimit); + markReadLimit = readlimit; + markResetLength = readlimit; + } + } + + /** + * Repositions this stream to the position at the time the + * mark method was last called on this input stream. + *

+ * Stream marks are intended to be used in + * situations where you need to read ahead a little to see what's in + * the stream. Often this is most easily done by invoking some + * general parser. If the stream is of the type handled by the + * parse, it just chugs along happily. If the stream is not of + * that type, the parser should toss an exception when it fails. + * If this happens within readlimit bytes, it allows the outer + * code to reset the stream and try another parser. + * + * @exception IOException if the stream has not been marked, if the + * mark has been invalidated or marking is not supported. + */ + @Override + public synchronized void reset() throws IOException { + if (closed) return; + super.reset(); + if (markResetLength >= 0) clampedLength += (markReadLimit - markResetLength); + markResetLength = -1; + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * + * @exception IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + if (!closed) closed = true; + super.close(); + } +} diff --git a/src/com/captainalm/lib/calmnet/stream/NetworkInputStream.java b/src/com/captainalm/lib/calmnet/stream/NetworkInputStream.java new file mode 100644 index 0000000..2c0a47b --- /dev/null +++ b/src/com/captainalm/lib/calmnet/stream/NetworkInputStream.java @@ -0,0 +1,239 @@ +package com.captainalm.lib.calmnet.stream; + +import java.io.IOException; +import java.io.InputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Socket; + +/** + * This class provides a Network Input stream for either {@link Socket}s or {@link DatagramSocket}s. + * + * @author Captain ALM + */ +public class NetworkInputStream extends InputStream { + protected boolean closed = false; + protected Socket socket; + protected InputStream socketStream; + protected DatagramSocket dsocket; + protected DatagramPacket dsocketPacket; + protected int dsocketPacketIndex = 0; + protected InetAddress dAddress; + protected int dPort = -1; + protected int dlen = 0; + + /** + * Constructs a new NetworkInputStream with the specified {@link Socket}. + * + * @param socketIn The socket to use. + * @throws NullPointerException socketIn is null. + */ + public NetworkInputStream(Socket socketIn) { + if (socketIn == null) throw new NullPointerException("socketIn is null"); + socket = socketIn; + } + + /** + * Constructs a new NetworkInputStream with the specified {@link DatagramSocket}. + * + * @param socketIn The datagram socket to use. + * @throws NullPointerException socketIn is null. + */ + public NetworkInputStream(DatagramSocket socketIn) { + if (socketIn == null) throw new NullPointerException("socketIn is null"); + dsocket = socketIn; + } + + protected void assureDSocketPacket() throws IOException { + if (dsocketPacket == null) { + dsocketPacket = new DatagramPacket(new byte[65535], 65535); + dsocket.receive(dsocketPacket); + dAddress = dsocketPacket.getAddress(); + dPort = dsocketPacket.getPort(); + dsocketPacketIndex = 0; + dlen = dsocketPacket.getLength(); + if (dlen < 1) dsocketPacket = null; + } + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. This method + * blocks until input data is available, the end of the stream is detected, + * or an exception is thrown. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @throws IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + if (closed) throw new IOException("stream closed"); + if (socket == null) { + int toret = -1; + assureDSocketPacket(); + if (dsocketPacket != null) { + toret = dsocketPacket.getData()[dsocketPacketIndex++] & 0xff; + if (dsocketPacketIndex >= dlen) dsocketPacket = null; + } + return toret; + } else { + if (socketStream == null) socketStream = socket.getInputStream(); + return socketStream.read(); + } + } + + /** + * Gets the current {@link InetAddress} of the stream. + * Can be null. + * + * @return The address. + */ + public InetAddress getAddress() { + if (dsocket == null) { + return socket.getInetAddress(); + } else { + return (dAddress == null) ? dsocket.getInetAddress() : dAddress; + } + } + + /** + * Gets the current port of the stream. + * Can be -1. + * + * @return The current port. + */ + public Integer getPort() { + if (dsocket == null) { + return socket.getPort(); + } else { + return (dPort == -1) ? dsocket.getPort() : dPort; + } + } + + /** + * Gets the local {@link InetAddress} of the stream. + * Can be null. + * + * @return The local address. + */ + public InetAddress getLocalAddress() { + if (dsocket == null) { + return socket.getLocalAddress(); + } else { + return dsocket.getLocalAddress(); + } + } + + /** + * Gets the local port of the stream. + * Can be -1. + * + * @return The local port. + */ + public Integer getLocalPort() { + if (dsocket == null) { + return socket.getPort(); + } else { + return dsocket.getPort(); + } + } + + /** + * Gets the socket in use or null. + * + * @return The socket in use or null. + */ + public Socket getSocket() { + return socket; + } + + /** + * Sets the socket in use. + * + * @param socketIn The socket to now use. + * @throws NullPointerException socketIn is null. + * @throws IOException stream closed or not using a socket. + */ + public void setSocket(Socket socketIn) throws IOException { + if (closed) throw new IOException("stream closed"); + if (socket == null) throw new IOException("not using a socket"); + if (socketIn == null) throw new NullPointerException("socketIn is null"); + socket = socketIn; + socketStream = null; + } + + /** + * Gets the datagram socket in use or null. + * + * @return The datagram socket in use or null. + */ + public DatagramSocket getDatagramSocket() { + return dsocket; + } + + /** + * Sets the datagram socket in use. + * + * @param socketIn The datagram socket to now use. + * @throws NullPointerException socketIn is null. + * @throws IOException stream closed or not using a datagram socket. + */ + public void setDatagramSocket(DatagramSocket socketIn) throws IOException { + if (closed) throw new IOException("stream closed"); + if (dsocket == null) throw new IOException("not using a datagram socket"); + if (socketIn == null) throw new NullPointerException("socketIn is null"); + dsocket = socketIn; + } + + /** + * Returns an estimate of the number of bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * invocation of a method for this input stream. The next invocation + * might be the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking or {@code 0} when + * it reaches the end of the input stream. + * @throws IOException if an I/O error occurs. + */ + @Override + public int available() throws IOException { + if (closed) throw new IOException("stream closed"); + if (dsocket == null) { + if (socketStream == null) socketStream = socket.getInputStream(); + return socketStream.available(); + } else { + assureDSocketPacket(); + return (dsocketPacket == null) ? 0 : dlen - dsocketPacketIndex; + } + } + + /** + * Closes this input stream and releases any system resources associated + * with the stream. The underlying socket is closed. + * + * @exception IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (socket == null) { + dsocketPacket = null; + dAddress = null; + dPort = -1; + dsocket.close(); + dsocket = null; + } else { + socketStream = null; + socket.close(); + socket = null; + } + } + } +} diff --git a/src/com/captainalm/lib/calmnet/stream/NetworkOutputStream.java b/src/com/captainalm/lib/calmnet/stream/NetworkOutputStream.java new file mode 100644 index 0000000..1b10783 --- /dev/null +++ b/src/com/captainalm/lib/calmnet/stream/NetworkOutputStream.java @@ -0,0 +1,315 @@ +package com.captainalm.lib.calmnet.stream; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Socket; + +/** + * This class provides a Network Output stream for either {@link Socket}s or {@link DatagramSocket}s. + * + * @author Captain ALM + */ +public class NetworkOutputStream extends OutputStream { + protected boolean closed = false; + protected Socket socket; + protected OutputStream socketStream; + protected DatagramSocket dsocket; + protected byte[] dsocketBuffer; + protected int dsocketBufferIndex = 0; + protected InetAddress dAddress; + protected int dPort = -1; + + /** + * Constructs a new NetworkOutputStream with the specified {@link Socket}. + * + * @param socketIn The socket to use. + * @throws NullPointerException socketIn is null. + */ + public NetworkOutputStream(Socket socketIn) { + if (socketIn == null) throw new NullPointerException("socketIn is null"); + socket = socketIn; + } + + /** + * Constructs a new NetworkOutputStream with the specified {@link DatagramSocket}. + * + * @param socketIn The datagram socket to use. + * @throws NullPointerException socketIn is null. + */ + public NetworkOutputStream(DatagramSocket socketIn) { + if (socketIn == null) throw new NullPointerException("socketIn is null"); + dsocket = socketIn; + } + + /** + * Constructs a new NetworkOutputStream with the specified {@link DatagramSocket} and datagram buffer size. + * + * @param socketIn The datagram socket to use. + * @param size The size of the buffer. + * @throws NullPointerException socketIn is null. + * @throws IllegalArgumentException size is less than 1 or greater than 65535. + */ + public NetworkOutputStream(DatagramSocket socketIn, int size) { + this(socketIn); + try { + setDatagramBufferSize(size); + } catch (IOException e) { + } + } + + /** + * Constructs a new NetworkOutputStream with the specified {@link DatagramSocket}, datagram buffer size, {@link InetAddress} target and port target. + * + * @param socketIn The datagram socket to use. + * @param size The size of the buffer. + * @param address The target address to set to. + * @param port The target port to set to. + * @throws NullPointerException socketIn or address is null. + * @throws IllegalArgumentException size is less than 1 or greater than 65535 or port is less than 0 or greater than 65535. + */ + public NetworkOutputStream(DatagramSocket socketIn, int size, InetAddress address, int port) { + this(socketIn, size); + try { + setDatagramTarget(address, port); + } catch (IOException e) { + } + } + + /** + * Gets if the {@link #setDatagramBufferSize(int)} can be used. + * + * @return If the datagram buffer size can be set. + */ + public boolean canDatagramBufferBeSet() { + return dsocket != null && dsocketBufferIndex == 0; + } + + /** + * Sets the buffer size for sending datagrams. + * + * @param size The size to set to. + * @throws IllegalArgumentException size is less than 1 or greater than 65535. + * @throws IOException a datagram socket is not in use, buffer index is not null or the stream is closed. + */ + public void setDatagramBufferSize(int size) throws IOException { + if (closed) throw new IOException("stream closed"); + if (dsocket == null) throw new IOException("not using a datagram socket"); + if (size < 1) throw new IllegalArgumentException("size is less than 1"); + if (size > 65535) throw new IllegalArgumentException("size is greater than 65535"); + if (dsocketBufferIndex != 0) throw new IOException("buffer index is not 0"); + dsocketBuffer = new byte[size]; + } + + /** + * Sets the datagram target {@link InetAddress} and port. + * + * @param address The address to set to. + * @param port The port to set to. + * @throws NullPointerException address is null. + * @throws IllegalArgumentException port is less than 0 or greater than 65535. + * @throws IOException a datagram socket is not in use or the stream is closed. + */ + public void setDatagramTarget(InetAddress address, int port) throws IOException { + if (closed) throw new IOException("stream closed"); + if (dsocket == null) throw new IOException("not using a datagram socket"); + if (address == null) throw new NullPointerException("address is null"); + if (port < 0) throw new IllegalArgumentException("port is less than 0"); + if (port > 65535) throw new IllegalArgumentException("port is greater than 65535"); + dAddress = address; + dPort = port; + } + + /** + * Writes the specified byte to this output stream. The general + * contract for write is that one byte is written + * to the output stream. The byte to be written is the eight + * low-order bits of the argument b. The 24 + * high-order bits of b are ignored. + * + * @param b the byte. + * @throws IOException if an I/O error occurs. In particular, + * an IOException will be thrown if the + * output stream has been closed. + */ + @Override + public void write(int b) throws IOException { + if (closed) throw new IOException("stream closed"); + if (socket == null) { + if (dsocketBuffer != null) { + dsocketBuffer[dsocketBufferIndex++] = (byte) b; + if (dsocketBufferIndex >= dsocketBuffer.length) { + sendDatagramData(dsocketBuffer, dsocketBuffer.length); + dsocketBufferIndex = 0; + dsocketBuffer = new byte[dsocketBuffer.length]; + } + } else { + throw new IOException("null datagram buffer"); + } + } else { + if (socketStream == null) socketStream = socket.getOutputStream(); + socketStream.write(b); + } + } + + /** + * Gets the current {@link InetAddress} of the stream. + * Can be null. + * + * @return The address. + */ + public InetAddress getAddress() { + if (dsocket == null) { + return socket.getInetAddress(); + } else { + return (dAddress == null) ? dsocket.getInetAddress() : dAddress; + } + } + + /** + * Gets the current port of the stream. + * Can be -1. + * + * @return The current port. + */ + public Integer getPort() { + if (dsocket == null) { + return socket.getPort(); + } else { + return (dPort == -1) ? dsocket.getPort() : dPort; + } + } + + /** + * Gets the local {@link InetAddress} of the stream. + * Can be null. + * + * @return The local address. + */ + public InetAddress getLocalAddress() { + if (dsocket == null) { + return socket.getLocalAddress(); + } else { + return dsocket.getLocalAddress(); + } + } + + /** + * Gets the local port of the stream. + * Can be -1. + * + * @return The local port. + */ + public Integer getLocalPort() { + if (dsocket == null) { + return socket.getPort(); + } else { + return dsocket.getPort(); + } + } + + /** + * Gets the socket in use or null. + * + * @return The socket in use or null. + */ + public Socket getSocket() { + return socket; + } + + /** + * Sets the socket in use. + * + * @param socketIn The socket to now use. + * @throws NullPointerException socketIn is null. + * @throws IOException stream closed or not using a socket. + */ + public void setSocket(Socket socketIn) throws IOException { + if (closed) throw new IOException("stream closed"); + if (socket == null) throw new IOException("not using a socket"); + if (socketIn == null) throw new NullPointerException("socketIn is null"); + socket = socketIn; + socketStream = null; + } + + /** + * Gets the datagram socket in use or null. + * + * @return The datagram socket in use or null. + */ + public DatagramSocket getDatagramSocket() { + return dsocket; + } + + /** + * Sets the datagram socket in use. + * + * @param socketIn The datagram socket to now use. + * @throws NullPointerException socketIn is null. + * @throws IOException stream closed or not using a datagram socket. + */ + public void setDatagramSocket(DatagramSocket socketIn) throws IOException { + if (closed) throw new IOException("stream closed"); + if (dsocket == null) throw new IOException("not using a datagram socket"); + if (socketIn == null) throw new NullPointerException("socketIn is null"); + dsocket = socketIn; + } + + /** + * Flushes this output stream and forces any buffered output bytes + * to be written out. The general contract of flush is + * that calling it is an indication that, if any bytes previously + * written have been buffered by the implementation of the output + * stream, such bytes should immediately be written to their + * intended destination. + * + * @exception IOException if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + if (closed) throw new IOException("stream closed"); + if (dsocket == null) { + if (socketStream == null) socketStream = socket.getOutputStream(); + socketStream.flush(); + } else { + sendDatagramData(dsocketBuffer, dsocketBufferIndex); + dsocketBufferIndex = 0; + dsocketBuffer = (dsocketBuffer == null) ? null : new byte[dsocketBuffer.length]; + } + } + + protected void sendDatagramData(byte[] data, int length) throws IOException { + if (data == null || length < 1) return; + if (dAddress == null || dPort < 0 || dPort > 65535) throw new IOException("no datagram target parameters set"); + DatagramPacket packet = new DatagramPacket(data, length, dAddress, dPort); + dsocket.send(packet); + } + + /** + * Closes this output stream and releases any system resources + * associated with this stream. The general contract of close + * is that it closes the output stream. A closed stream cannot perform + * output operations and cannot be reopened. The underlying socket is closed. + * + * @exception IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (socket == null) { + dsocketBuffer = null; + dAddress = null; + dPort = -1; + dsocket.close(); + dsocket = null; + } else { + socketStream = null; + socket.close(); + socket = null; + } + } + } +} diff --git a/src/com/captainalm/lib/calmnet/stream/package-info.java b/src/com/captainalm/lib/calmnet/stream/package-info.java new file mode 100644 index 0000000..3dce4ee --- /dev/null +++ b/src/com/captainalm/lib/calmnet/stream/package-info.java @@ -0,0 +1,6 @@ +/** + * This package provides streams for network packet streaming. + * + * @author Captain ALM + */ +package com.captainalm.lib.calmnet.stream; \ No newline at end of file