diff --git a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_000_authentication/SampleLogonAuthentication.java b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_000_authentication/SampleLogonAuthentication.java index cbf87c82..2f9818ff 100644 --- a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_000_authentication/SampleLogonAuthentication.java +++ b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_000_authentication/SampleLogonAuthentication.java @@ -66,12 +66,30 @@ public static void main(String[] args) { @Override public void run() { - // // If any configuration needs to be set; such as connection protocol api key, etc., you can configure it like so. - // var configuration = SteamConfiguration.create(config -> { + // If any configuration needs to be set; such as connection protocol api key, etc., you can configure it like so. + // var config = SteamConfiguration.create(config -> { // config.withProtocolTypes(ProtocolTypes.WEB_SOCKET); // }); - // // create our steamclient instance with custom configuration. - // steamClient = new SteamClient(configuration); + + // You can also create custom connection classes if you have specific networking requirements. + // var config = SteamConfiguration.create(builder -> { + // builder.withProtocolTypes(EnumSet.of(ProtocolTypes.TCP, ProtocolTypes.UDP)); // Declare desired protocol types. + // IConnectionFactory connectionFactory = (configuration, protocol) -> { + // if (protocol.contains(ProtocolTypes.TCP)) { + // return new CustomTCPConnection(); + // } else if (protocol.contains(ProtocolTypes.UDP)) { + // // We ask for TCP and UDP above, so this condition should handle UDP. + // return CustomUDPConnection(); + // } else { + // // Fallback: 'thenResolve` will fallback to default connection types. + // return null; + // } + // }; + // builder.withConnectionFactory(connectionFactory.thenResolve(IConnectionFactory.DEFAULT)); + // }); + + // create our steamclient instance with custom configuration. + // steamClient = new SteamClient(config); // create our steamclient instance using default configuration steamClient = new SteamClient(); diff --git a/src/main/java/in/dragonbra/javasteam/networking/steam3/Connection.java b/src/main/java/in/dragonbra/javasteam/networking/steam3/Connection.java index 9a8b24f9..0f6c7b2b 100644 --- a/src/main/java/in/dragonbra/javasteam/networking/steam3/Connection.java +++ b/src/main/java/in/dragonbra/javasteam/networking/steam3/Connection.java @@ -15,27 +15,27 @@ public abstract class Connection { /** * Occurs when a net message is received over the network. */ - final Event netMsgReceived = new Event<>(); + protected final Event netMsgReceived = new Event<>(); /** * Occurs when the physical connection is established. */ - final Event connected = new Event<>(); + protected final Event connected = new Event<>(); /** * Occurs when the physical connection is broken. */ - final Event disconnected = new Event<>(); + protected final Event disconnected = new Event<>(); - void onNetMsgReceived(NetMsgEventArgs e) { + protected void onNetMsgReceived(NetMsgEventArgs e) { netMsgReceived.handleEvent(this, e); } - void onConnected() { + protected void onConnected() { connected.handleEvent(this, null); } - void onDisconnected(boolean e) { + protected void onDisconnected(boolean e) { disconnected.handleEvent(this, new DisconnectedEventArgs(e)); } diff --git a/src/main/java/in/dragonbra/javasteam/networking/steam3/IConnectionFactory.java b/src/main/java/in/dragonbra/javasteam/networking/steam3/IConnectionFactory.java new file mode 100644 index 00000000..42adb273 --- /dev/null +++ b/src/main/java/in/dragonbra/javasteam/networking/steam3/IConnectionFactory.java @@ -0,0 +1,43 @@ +package in.dragonbra.javasteam.networking.steam3; + +import in.dragonbra.javasteam.steam.steamclient.configuration.SteamConfiguration; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumSet; +import java.util.Objects; + +@FunctionalInterface +public interface IConnectionFactory { + + IConnectionFactory DEFAULT = (configuration, protocol) -> { + if (protocol.contains(ProtocolTypes.WEB_SOCKET)) { + return new WebSocketConnection(); + } + if (protocol.contains(ProtocolTypes.TCP)) { + return new EnvelopeEncryptedConnection(new TcpConnection(), configuration.getUniverse()); + } + if (protocol.contains(ProtocolTypes.UDP)) { + return new EnvelopeEncryptedConnection(new UdpConnection(), configuration.getUniverse()); + } + return null; + }; + + /** + * If the final method returns null, an exception will be thrown. + */ + @Nullable Connection createConnection(SteamConfiguration configuration, EnumSet protocol); + + /** + * If this method returns null, the subConnectionFactory will be used. + */ + default IConnectionFactory thenResolve(IConnectionFactory subConnectionFactory) { + Objects.requireNonNull(subConnectionFactory); + return (configuration, protocol) -> { + Connection connection = createConnection(configuration, protocol); + if (connection == null) { + return subConnectionFactory.createConnection(configuration, protocol); + } + return connection; + }; + } +} diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index 4e25a36e..1351b746 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -9,8 +9,8 @@ import in.dragonbra.javasteam.networking.steam3.*; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesBase.CMsgMulti; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserver.CMsgClientSessionToken; -import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientHello; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientHeartBeat; +import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientHello; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientLoggedOff; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientLogonResponse; import in.dragonbra.javasteam.steam.discovery.ServerQuality; @@ -35,7 +35,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.util.*; +import java.util.EnumSet; import java.util.zip.GZIPInputStream; /** @@ -189,6 +189,8 @@ public void connect(ServerRecord cmServer) { connection.getNetMsgReceived().addEventHandler(netMsgReceived); connection.getConnected().addEventHandler(connected); connection.getDisconnected().addEventHandler(disconnected); + logger.debug(String.format("Connecting to %s with protocol %s, and with connection impl %s", + cmServer.getEndpoint(), cmServer.getProtocolTypes(), connection.getClass().getSimpleName())); connection.connect(cmServer.getEndpoint()); } catch (Exception e) { logger.debug("Failed to connect to Steam network", e); @@ -312,15 +314,13 @@ protected void onClientDisconnected(boolean userInitiated) { } private Connection createConnection(EnumSet protocol) { - if (protocol.contains(ProtocolTypes.WEB_SOCKET)) { - return new WebSocketConnection(); - } else if (protocol.contains(ProtocolTypes.TCP)) { - return new EnvelopeEncryptedConnection(new TcpConnection(), getUniverse()); - } else if (protocol.contains(ProtocolTypes.UDP)) { - return new EnvelopeEncryptedConnection(new UdpConnection(), getUniverse()); + IConnectionFactory connectionFactory = configuration.getConnectionFactory(); + Connection connection = connectionFactory.createConnection(configuration, protocol); + if (connection == null) { + logger.error(String.format("Connection factory returned null connection for protocols %s", protocol)); + throw new IllegalArgumentException("Connection factory returned null connection."); } - - throw new IllegalArgumentException("Protocol bitmask has no supported protocols set."); + return connection; } public static IPacketMsg getPacketMsg(byte[] data) { diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/ISteamConfigurationBuilder.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/ISteamConfigurationBuilder.kt index d596aa2d..0bb0a2af 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/ISteamConfigurationBuilder.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/ISteamConfigurationBuilder.kt @@ -2,6 +2,7 @@ package `in`.dragonbra.javasteam.steam.steamclient.configuration import `in`.dragonbra.javasteam.enums.EClientPersonaStateFlag import `in`.dragonbra.javasteam.enums.EUniverse +import `in`.dragonbra.javasteam.networking.steam3.IConnectionFactory import `in`.dragonbra.javasteam.networking.steam3.ProtocolTypes import `in`.dragonbra.javasteam.steam.contentdownloader.IManifestProvider import `in`.dragonbra.javasteam.steam.discovery.IServerListProvider @@ -14,6 +15,15 @@ import java.util.* */ @Suppress("unused") interface ISteamConfigurationBuilder { + + /** + * Configures this [SteamConfiguration] to use the provided [IConnectionFactory]. + * + * @param connectionFactory The [IConnectionFactory] to use. + * @return A builder with modified configuration. + */ + fun withConnectionFactory(connectionFactory: IConnectionFactory): ISteamConfigurationBuilder + /** * Configures this [SteamConfiguration] for a particular Steam cell. * diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfiguration.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfiguration.kt index 775b70bf..13212928 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfiguration.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfiguration.kt @@ -2,6 +2,7 @@ package `in`.dragonbra.javasteam.steam.steamclient.configuration import `in`.dragonbra.javasteam.enums.EClientPersonaStateFlag import `in`.dragonbra.javasteam.enums.EUniverse +import `in`.dragonbra.javasteam.networking.steam3.IConnectionFactory import `in`.dragonbra.javasteam.networking.steam3.ProtocolTypes import `in`.dragonbra.javasteam.steam.contentdownloader.IManifestProvider import `in`.dragonbra.javasteam.steam.discovery.IServerListProvider @@ -19,6 +20,23 @@ import java.util.* @Suppress("MemberVisibilityCanBePrivate") class SteamConfiguration internal constructor(private val state: SteamConfigurationState) { + /** + * Builds the underlying [in.dragonbra.javasteam.networking.steam3.Connection] used for connecting to stream. + * + * ```java + * steamClient = new SteamClient(SteamConfiguration.create(builder -> { + * IConnectionFactory connectionFactory = (configuration, protocol) -> { + * return null;//custom connection or null to resolve fallback + * }; + * builder.withConnectionFactory( + * connectionFactory.thenResolve(IConnectionFactory.DEFAULT)); + * })); + * ``` + * + */ + val connectionFactory: IConnectionFactory + get() = state.connectionFactory + /** * Whether to use the Steam Directory to discover available servers. */ diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationBuilder.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationBuilder.kt index bd04cd55..6ce754d2 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationBuilder.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationBuilder.kt @@ -2,6 +2,7 @@ package `in`.dragonbra.javasteam.steam.steamclient.configuration import `in`.dragonbra.javasteam.enums.EClientPersonaStateFlag import `in`.dragonbra.javasteam.enums.EUniverse +import `in`.dragonbra.javasteam.networking.steam3.IConnectionFactory import `in`.dragonbra.javasteam.networking.steam3.ProtocolTypes import `in`.dragonbra.javasteam.steam.contentdownloader.IManifestProvider import `in`.dragonbra.javasteam.steam.contentdownloader.MemoryManifestProvider @@ -21,6 +22,11 @@ class SteamConfigurationBuilder : ISteamConfigurationBuilder { fun build(): SteamConfiguration = SteamConfiguration(state) + override fun withConnectionFactory(connectionFactory: IConnectionFactory): ISteamConfigurationBuilder { + state.connectionFactory = connectionFactory + return this + } + override fun withCellID(cellID: Int): ISteamConfigurationBuilder { state.cellID = cellID return this @@ -89,6 +95,7 @@ class SteamConfigurationBuilder : ISteamConfigurationBuilder { companion object { @JvmStatic fun createDefaultState(): SteamConfigurationState = SteamConfigurationState( + connectionFactory = IConnectionFactory.DEFAULT, isAllowDirectoryFetch = true, connectionTimeout = 5000L, defaultPersonaStateFlags = EnumSet.of( diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationState.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationState.kt index e4810ab1..fbce399a 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationState.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/configuration/SteamConfigurationState.kt @@ -2,17 +2,19 @@ package `in`.dragonbra.javasteam.steam.steamclient.configuration import `in`.dragonbra.javasteam.enums.EClientPersonaStateFlag import `in`.dragonbra.javasteam.enums.EUniverse +import `in`.dragonbra.javasteam.networking.steam3.IConnectionFactory import `in`.dragonbra.javasteam.networking.steam3.ProtocolTypes import `in`.dragonbra.javasteam.steam.contentdownloader.IManifestProvider import `in`.dragonbra.javasteam.steam.discovery.IServerListProvider import okhttp3.OkHttpClient -import java.util.EnumSet +import java.util.* /** * @author lngtr * @since 2018-02-20 */ data class SteamConfigurationState( + var connectionFactory: IConnectionFactory, var isAllowDirectoryFetch: Boolean, var cellID: Int, var connectionTimeout: Long,