From db7d5b461bfe24b796007d9bdf838ba560071d85 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Wed, 4 Sep 2024 00:11:38 -0500 Subject: [PATCH 1/6] Rework UnifiedService to have an easier way to use RPC messages. --- build.gradle.kts | 2 +- .../generators/rpc/parser/ProtoParser.kt | 80 +-------------- .../SampleUnifiedMessages.java | 20 ++-- .../steam/authentication/AuthSession.kt | 8 +- .../authentication/CredentialsAuthSession.kt | 6 +- .../authentication/SteamAuthentication.kt | 23 +++-- .../SteamUnifiedMessages.kt | 7 ++ .../steamunifiedmessages/UnifiedService.kt | 97 +++++++++++-------- .../javasteam/types/AsyncJobSingle.kt | 2 +- .../javasteam/rpc/UnifiedInterfaceTest.kt | 78 +++++---------- 10 files changed, 131 insertions(+), 192 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5358f1fe..2020c914 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ plugins { allprojects { group = "in.dragonbra" - version = "1.5.1" + version = "1.6.0-SNAPSHOT" } repositories { diff --git a/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt b/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt index 33a15623..395f5583 100644 --- a/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt +++ b/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt @@ -18,6 +18,7 @@ class ProtoParser(private val outputDir: File) { .addMember("%S", "KDocUnresolvedReference") // IntelliJ's seems to get confused with canonical names .addMember("%S", "RedundantVisibilityModifier") // KotlinPoet is an explicit API generator .addMember("%S", "unused") // All methods could be used. + .addMember("%S", "FunctionName") // Service messages might be case-sensitive, preserve it. .build() private val classAsyncJobSingle = ClassName( @@ -66,7 +67,6 @@ class ProtoParser(private val outputDir: File) { println("[${file.name}] - found \"${service.name}\", which has ${service.methods.size} methods") buildInterface(file, service) - buildClass(file, service) } } } @@ -117,7 +117,7 @@ class ProtoParser(private val outputDir: File) { // Make a method val funBuilder = FunSpec - .builder(method.methodName.replaceFirstChar { it.lowercase(Locale.getDefault()) }) + .builder(method.methodName) .addModifiers(KModifier.ABSTRACT) .addParameter("request", requestClassName) @@ -132,7 +132,7 @@ class ProtoParser(private val outputDir: File) { ) val kDoc = kDocReturns(requestClassName, returnClassName) funBuilder.addKdoc(kDoc) - .returns(classAsyncJobSingle.parameterizedBy(classServiceMethodResponse)) + .returns(returnClassName) } // Add the function to the interface class. @@ -145,78 +145,4 @@ class ProtoParser(private val outputDir: File) { .build() .writeTo(outputDir) } - - /** - * Build the [Service] to a class with all known RPC methods. - */ - private fun buildClass(file: File, service: Service) { - // Class Builder - val cBuilder = TypeSpec - .classBuilder(service.name) - .addAnnotation(suppressAnnotation) - .addKdoc(RpcGenTask.kDocClass) - .primaryConstructor( - FunSpec.constructorBuilder() - .addParameter( - name = "steamUnifiedMessages", - type = ClassName( - packageName = "in.dragonbra.javasteam.steam.handlers.steamunifiedmessages", - "SteamUnifiedMessages" - ) - ) - .build() - ) - .addSuperclassConstructorParameter("steamUnifiedMessages") - .superclass( - ClassName( - packageName = "in.dragonbra.javasteam.steam.handlers.steamunifiedmessages", - "UnifiedService" - ) - ) - .addSuperinterface( - ClassName( - packageName = "in.dragonbra.javasteam.rpc.interfaces", - "I${service.name}" - ) - ) - - // Iterate over found 'rpc' methods. - val protoFileName = transformProtoFileName(file.name) - service.methods.forEach { method -> - val requestClassName = ClassName( - packageName = "in.dragonbra.javasteam.protobufs.steamclient.$protoFileName", - method.requestType - ) - - // Make a method - val funBuilder = FunSpec - .builder(method.methodName.replaceFirstChar { it.lowercase(Locale.getDefault()) }) - .addModifiers(KModifier.OVERRIDE) - .addParameter("request", requestClassName) - - // Add method kDoc - // Add `AsyncJobSingle` if there is a response - if (method.responseType == "NoResponse") { - funBuilder.addKdoc(kDocNoResponse) - .addStatement("sendNotification(request, %S)", method.methodName) - } else { - val returnClassName = ClassName( - packageName = "in.dragonbra.javasteam.protobufs.steamclient.$protoFileName", - method.responseType - ) - val kDoc = kDocReturns(requestClassName, returnClassName) - funBuilder.addKdoc(kDoc) - .returns(classAsyncJobSingle.parameterizedBy(classServiceMethodResponse)) - .addStatement("return sendMessage(request, %S)", method.methodName) - } - - cBuilder.addFunction(funBuilder.build()) - } - - // Build everything together and write it - FileSpec.builder(SERVICE_PACKAGE, service.name) - .addType(cBuilder.build()) - .build() - .writeTo(outputDir) - } } diff --git a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_013_unifiedmessages/SampleUnifiedMessages.java b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_013_unifiedmessages/SampleUnifiedMessages.java index 6a88da06..a3d0cb66 100644 --- a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_013_unifiedmessages/SampleUnifiedMessages.java +++ b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_013_unifiedmessages/SampleUnifiedMessages.java @@ -4,7 +4,7 @@ import in.dragonbra.javasteam.enums.EUIMode; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesFriendmessagesSteamclient.CFriendMessages_IncomingMessage_Notification; import in.dragonbra.javasteam.protobufs.steamclient.SteammessagesPlayerSteamclient.*; -import in.dragonbra.javasteam.rpc.service.Player; +import in.dragonbra.javasteam.rpc.interfaces.IPlayer; import in.dragonbra.javasteam.steam.handlers.steamunifiedmessages.SteamUnifiedMessages; import in.dragonbra.javasteam.steam.handlers.steamunifiedmessages.callback.ServiceMethodNotification; import in.dragonbra.javasteam.steam.handlers.steamunifiedmessages.callback.ServiceMethodResponse; @@ -165,15 +165,17 @@ private void onLoggedOn(LoggedOnCallback callback) { // at this point, we'd be able to perform actions on Steam // first, build our request object, these are autogenerated and can normally be found in the in.dragonbra.javasteam.protobufs.steamclient package - CPlayer_GetFavoriteBadge_Request.Builder favoriteBadgeRequest = CPlayer_GetFavoriteBadge_Request.newBuilder(); + var favoriteBadgeRequest = CPlayer_GetFavoriteBadge_Request.newBuilder(); favoriteBadgeRequest.setSteamid(steamClient.getSteamID().convertToUInt64()); // now let's send the request, this is done by building a class based off the IPlayer interface. - Player playerService = new Player(steamUnifiedMessages); - favoriteBadge = playerService.getFavoriteBadge(favoriteBadgeRequest.build()).getJobID(); + var playerService = steamUnifiedMessages.createService(IPlayer.class); + favoriteBadge = playerService.sendMessage(api -> + api.GetFavoriteBadge(favoriteBadgeRequest.build()) + ).getJobID(); // second, build our request object, these are autogenerated and can normally be found in the in.dragonbra.javasteam.protobufs.steamclient package - CPlayer_GetGameBadgeLevels_Request.Builder badgeLevelsRequest = CPlayer_GetGameBadgeLevels_Request.newBuilder(); + var badgeLevelsRequest = CPlayer_GetGameBadgeLevels_Request.newBuilder(); badgeLevelsRequest.setAppid(440); // alternatively, the request can be made using SteamUnifiedMessages directly, but then you must build the service request name manually @@ -202,7 +204,9 @@ private void onMethodResponse(ServiceMethodResponse callback) { // for responses: CMyService_Method_Response if (callback.getJobID().equals(badgeRequest)) { - CPlayer_GetGameBadgeLevels_Response.Builder response = callback.getDeserializedResponse(CPlayer_GetGameBadgeLevels_Response.class); + CPlayer_GetGameBadgeLevels_Response.Builder response = callback.getDeserializedResponse( + CPlayer_GetGameBadgeLevels_Response.class + ); System.out.println("Our player level is " + response.getPlayerLevel()); @@ -215,7 +219,9 @@ private void onMethodResponse(ServiceMethodResponse callback) { } if (callback.getJobID().equals(favoriteBadge)) { - CPlayer_GetFavoriteBadge_Response.Builder response = callback.getDeserializedResponse(CPlayer_GetFavoriteBadge_Response.class); + CPlayer_GetFavoriteBadge_Response.Builder response = callback.getDeserializedResponse( + CPlayer_GetFavoriteBadge_Response.class + ); System.out.println( "Has favorite badge: " + response.hasHasFavoriteBadge() + diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt index 22f5e490..a84465ec 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt @@ -3,7 +3,7 @@ package `in`.dragonbra.javasteam.steam.authentication import com.google.protobuf.ByteString import `in`.dragonbra.javasteam.enums.EResult import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient -import `in`.dragonbra.javasteam.rpc.service.Authentication +import `in`.dragonbra.javasteam.rpc.interfaces.IAuthentication import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -22,7 +22,7 @@ typealias SessionGuardType = SteammessagesAuthSteamclient.EAuthSessionGuardType /** * Represents an authentication session which can be used to finish the authentication and get access tokens. * - * @param authentication Unified messages class for Authentication related messages, see [Authentication]. + * @param authentication Unified messages class for Authentication related messages, see [IAuthentication]. * @param authenticator Authenticator object which will be used to handle 2-factor authentication if necessary. * @param clientId Unique identifier of requestor, also used for routing, portion of QR code. * @param requestId Unique request ID to be presented by requestor at poll time. @@ -187,7 +187,9 @@ open class AuthSession( request.clientId = clientId request.requestId = ByteString.copyFrom(requestId) - val message = authentication.authenticationService.pollAuthSessionStatus(request.build()).runBlock() + val message = authentication.authenticationService.sendMessage { api -> + api.PollAuthSessionStatus(request.build()) + }.runBlocking() // eResult can be Expired, FileNotFound, Fail if (message.result != EResult.OK) { diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt index 0b53875a..7be1615e 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt @@ -41,9 +41,9 @@ class CredentialsAuthSession( this.codeType = codeType } - val message = authentication.authenticationService - .updateAuthSessionWithSteamGuardCode(request.build()) - .runBlock() + val message = authentication.authenticationService.sendMessage { api -> + api.UpdateAuthSessionWithSteamGuardCode(request.build()) + }.runBlocking() @Suppress("UNUSED_VARIABLE") val response: CAuthentication_UpdateAuthSessionWithSteamGuardCode_Response.Builder = diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt index e86320be..60b45527 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt @@ -12,8 +12,9 @@ import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclie import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_GetPasswordRSAPublicKey_Request import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_GetPasswordRSAPublicKey_Response import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.ETokenRenewalType -import `in`.dragonbra.javasteam.rpc.service.Authentication +import `in`.dragonbra.javasteam.rpc.interfaces.IAuthentication import `in`.dragonbra.javasteam.steam.handlers.steamunifiedmessages.SteamUnifiedMessages +import `in`.dragonbra.javasteam.steam.handlers.steamunifiedmessages.UnifiedService import `in`.dragonbra.javasteam.steam.steamclient.SteamClient import `in`.dragonbra.javasteam.types.SteamID import `in`.dragonbra.javasteam.util.crypto.CryptoHelper @@ -37,13 +38,13 @@ class SteamAuthentication(private val steamClient: SteamClient) { // private val logger = LogManager.getLogger(SteamAuthentication::class.java) } - internal val authenticationService: Authentication + internal val authenticationService: UnifiedService init { val unifiedMessages = steamClient.getHandler(SteamUnifiedMessages::class.java) ?: throw NullPointerException("Unable to get SteamUnifiedMessages handler") - authenticationService = Authentication(unifiedMessages) + authenticationService = unifiedMessages.createService(IAuthentication::class.java) } /** @@ -58,7 +59,9 @@ class SteamAuthentication(private val steamClient: SteamClient) { this.accountName = accountName } - val message = authenticationService.getPasswordRSAPublicKey(request.build()).runBlock() + val message = authenticationService.sendMessage { api -> + api.GetPasswordRSAPublicKey(request.build()) + }.runBlocking() if (message.result != EResult.OK) { throw AuthenticationException("Failed to get password public key", message.result) @@ -90,7 +93,9 @@ class SteamAuthentication(private val steamClient: SteamClient) { } } - val message = authenticationService.generateAccessTokenForApp(request.build()).runBlock() + val message = authenticationService.sendMessage { api -> + api.GenerateAccessTokenForApp(request.build()) + }.runBlocking() if (message.result != EResult.OK) { throw IllegalArgumentException("Failed to generate token ${message.result}") @@ -126,7 +131,9 @@ class SteamAuthentication(private val steamClient: SteamClient) { this.deviceDetails = deviceDetails.build() } - val message = authenticationService.beginAuthSessionViaQR(request.build()).runBlock() + val message = authenticationService.sendMessage { api -> + api.BeginAuthSessionViaQR(request.build()) + }.runBlocking() if (message.result != EResult.OK) { throw AuthenticationException("Failed to begin QR auth session", message.result) @@ -203,7 +210,9 @@ class SteamAuthentication(private val steamClient: SteamClient) { request.guardData = authSessionDetails.guardData } - val message = authenticationService.beginAuthSessionViaCredentials(request.build()).runBlock() + val message = authenticationService.sendMessage { api -> + api.BeginAuthSessionViaCredentials(request.build()) + }.runBlocking() if (message.result != EResult.OK) { throw AuthenticationException("Authentication failed", message.result) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt index 33ed1390..39abae83 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt @@ -23,6 +23,13 @@ import java.lang.reflect.Method */ class SteamUnifiedMessages : ClientMsgHandler() { + /** + * Creates a service for Unified RPC methods. + * See [in.dragonbra.javasteam.rpc.interfaces] for available interfaces. + */ + fun createService(serviceClass: Class): UnifiedService = + UnifiedService(serviceClass, this) + /** * Handles a client message. This should not be called directly. * diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt index d638090e..092cb0a5 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt @@ -3,66 +3,83 @@ package `in`.dragonbra.javasteam.steam.handlers.steamunifiedmessages import com.google.protobuf.GeneratedMessage import `in`.dragonbra.javasteam.steam.handlers.steamunifiedmessages.callback.ServiceMethodResponse import `in`.dragonbra.javasteam.types.AsyncJobSingle +import `in`.dragonbra.javasteam.util.log.LogManager +import java.lang.reflect.Proxy /** * @author Lossy * @since 2023-01-04 + * + * This wrapper is used for expression-based RPC calls using Steam Unified Messaging. + * + * var playService = steamUnifiedMessages.createService(IPlayer.class); + * favoriteBadge = playService.sendMessage(api -> + * api.GetFavoriteBadge(favoriteBadgeRequest.build()) + * ).runBlocking(); */ @Suppress("unused") -abstract class UnifiedService(private val steamUnifiedMessages: SteamUnifiedMessages) { - - private val className: String - get() = this.javaClass.simpleName +class UnifiedService( + private val serviceClass: Class, + private val steamUnifiedMessages: SteamUnifiedMessages, +) { + private companion object { + private val logger = LogManager.getLogger(UnifiedService::class.java) + } /** * Sends a message. - * * Results are returned in a [ServiceMethodResponse]. - * - * @param message The message to send. - * @param methodName The Target Job Name. - * @return The JobID of the message. This can be used to find the appropriate [ServiceMethodResponse]. + * The returned [AsyncJobSingle] can also be awaited to retrieve the callback result. + * @param TRequest The type of the protobuf object which is the request to the RPC call + * @param TResponse The type of the protobuf object which is the response to the RPC call. + * @param expr RPC call expression, e.g. x => x.SomeMethodCall(message); + * @return The JobID of the request. This can be used to find the appropriate [ServiceMethodResponse]. */ - fun sendMessage(message: GeneratedMessage, methodName: String): AsyncJobSingle { - val rpcEndpoint = getRpcEndpoint(className, methodName) - - return sendMessageOrNotification(rpcEndpoint, message, false)!! - } + fun , TResponse : GeneratedMessage> sendMessage( + expr: (TService) -> TResponse, + ): AsyncJobSingle = sendMessageOrNotification(expr, false)!! /** * Sends a notification. - * - * @param message The message to send. - * @param methodName The Target Job Name. + * @param TRequest The type of the protobuf object which is the request to the RPC call. + * @param TResponse The type of the protobuf object which is the response to the RPC call. + * @param expr RPC call expression, e.g. x => x.SomeMethodCall(message); + * @return null */ - fun sendNotification(message: GeneratedMessage, methodName: String) { - val rpcEndpoint = getRpcEndpoint(className, methodName) - - sendMessageOrNotification(rpcEndpoint, message, true) - } + fun , TResponse : GeneratedMessage> sendNotification( + expr: (TService) -> TResponse, + ): AsyncJobSingle? = sendMessageOrNotification(expr, true) - private fun sendMessageOrNotification( - rpcName: String, - message: GeneratedMessage, + private fun , TResponse : GeneratedMessage> sendMessageOrNotification( + expr: (TService) -> TResponse, isNotification: Boolean, ): AsyncJobSingle? { - if (isNotification) { - steamUnifiedMessages.sendNotification(rpcName, message) - return null - } + var methodName: String? = null + var methodArgs: GeneratedMessage? = null - return steamUnifiedMessages.sendMessage(rpcName, message) - } + @Suppress("UNCHECKED_CAST") + val proxy = Proxy.newProxyInstance( + serviceClass.classLoader, + arrayOf(serviceClass) + ) { _, method, args -> + methodName = method.name + methodArgs = args.firstOrNull() as GeneratedMessage + null + } as TService + expr(proxy) - companion object { - // val logger = LogManager.getLogger(UnifiedService.class) + val version = 1 + val serviceName = serviceClass.simpleName.removePrefix("I") + val rpcName = "$serviceName.$methodName#$version" - /** - * @param parentClassName The parent class name, ie: Player - * @param methodName The calling method name, ie: GetGameBadgeLevels - * @return The name of the RPC endpoint as formatted ServiceName.RpcName. ie: Player.GetGameBadgeLevels#1 - */ - private fun getRpcEndpoint(parentClassName: String, methodName: String): String = - String.format("%s.%s#%s", parentClassName, methodName, 1) + requireNotNull(methodArgs) { "Unable to get arguments for message" } + + logger.debug("Service Name: $rpcName / Notification: $isNotification") + return if (isNotification) { + steamUnifiedMessages.sendNotification(rpcName, methodArgs!!) + null + } else { + steamUnifiedMessages.sendMessage(rpcName, methodArgs!!) + } } } diff --git a/src/main/java/in/dragonbra/javasteam/types/AsyncJobSingle.kt b/src/main/java/in/dragonbra/javasteam/types/AsyncJobSingle.kt index 61b52fc7..492184ad 100644 --- a/src/main/java/in/dragonbra/javasteam/types/AsyncJobSingle.kt +++ b/src/main/java/in/dragonbra/javasteam/types/AsyncJobSingle.kt @@ -22,7 +22,7 @@ class AsyncJobSingle(client: SteamClient, jobId: JobID) : Async fun toDeferred(): CompletableDeferred = tcs @Throws(CancellationException::class) - fun runBlock(): T = runBlocking { toDeferred().await() } + fun runBlocking(): T = runBlocking { toDeferred().await() } override fun addResult(callback: CallbackMsg?): Boolean { requireNotNull(callback) { "callback must not be null" } diff --git a/src/test/java/in/dragonbra/javasteam/rpc/UnifiedInterfaceTest.kt b/src/test/java/in/dragonbra/javasteam/rpc/UnifiedInterfaceTest.kt index ee875853..2cf3c0d6 100644 --- a/src/test/java/in/dragonbra/javasteam/rpc/UnifiedInterfaceTest.kt +++ b/src/test/java/in/dragonbra/javasteam/rpc/UnifiedInterfaceTest.kt @@ -30,74 +30,46 @@ class UnifiedInterfaceTest { ) } - @Test - fun testServiceCount() { - val interfaceDir = File(SERVICE_PATH) - - Assertions.assertTrue( - interfaceDir.exists() && interfaceDir.isDirectory, - "${interfaceDir.name} should exist to test" - ) - - val fileCount = interfaceDir.listFiles() - - Assertions.assertNotNull(fileCount, "Couldn't count files") - - Assertions.assertTrue( - knownServiceTypes.count() == fileCount!!.size, - "Interface count doesn't match known file types! Did something change in the .proto files?" - ) - } - @Test fun testKnownInterfaces() { for (filename in knownServiceTypes) { - val file = File(INTERFACE_PATH, "I$filename") + val file = File(INTERFACE_PATH, filename) Assertions.assertTrue(file.exists() && file.isFile, "File I$filename should exist") } } - @Test - fun testKnownServices() { - for (filename in knownServiceTypes) { - val file = File(SERVICE_PATH, filename) - Assertions.assertTrue(file.exists() && file.isFile, "File $filename should exist") - } - } - private companion object { const val DIR_PATH = "build/generated/source/javasteam/main/java/in/dragonbra/javasteam/rpc/" const val INTERFACE_PATH = "$DIR_PATH/interfaces" - const val SERVICE_PATH = "$DIR_PATH/service" /** * Any changes to then number of interfaces would need to reflect here. Otherwise, the test should fail. */ val knownServiceTypes = arrayOf( - "AccountLinking.kt", - "Authentication.kt", - "AuthenticationSupport.kt", - "Chat.kt", - "ChatRoom.kt", - "ChatRoomClient.kt", - "ChatUsability.kt", - "ChatUsabilityClient.kt", - "ClanChatRooms.kt", - "CloudGaming.kt", - "ContentServerDirectory.kt", - "EmbeddedClient.kt", - "FriendMessages.kt", - "FriendMessagesClient.kt", - "Inventory.kt", - "InventoryClient.kt", - "Parental.kt", - "ParentalClient.kt", - "Player.kt", - "PlayerClient.kt", - "RemoteClient.kt", - "RemoteClientSteamClient.kt", - "TwoFactor.kt", - "UserAccount.kt", + "IAccountLinking.kt", + "IAuthentication.kt", + "IAuthenticationSupport.kt", + "IChat.kt", + "IChatRoom.kt", + "IChatRoomClient.kt", + "IChatUsability.kt", + "IChatUsabilityClient.kt", + "IClanChatRooms.kt", + "ICloudGaming.kt", + "IContentServerDirectory.kt", + "IEmbeddedClient.kt", + "IFriendMessages.kt", + "IFriendMessagesClient.kt", + "IInventory.kt", + "IInventoryClient.kt", + "IParental.kt", + "IParentalClient.kt", + "IPlayer.kt", + "IPlayerClient.kt", + "IRemoteClient.kt", + "IRemoteClientSteamClient.kt", + "ITwoFactor.kt", + "IUserAccount.kt", ) } } From a8c18879ae3fac28ba0b8d537d025a4ccab15be1 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Wed, 4 Sep 2024 16:34:16 -0500 Subject: [PATCH 2/6] kdoc tweaks --- .../steam/handlers/steamunifiedmessages/UnifiedService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt index 092cb0a5..9d890e03 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt @@ -32,7 +32,7 @@ class UnifiedService( * The returned [AsyncJobSingle] can also be awaited to retrieve the callback result. * @param TRequest The type of the protobuf object which is the request to the RPC call * @param TResponse The type of the protobuf object which is the response to the RPC call. - * @param expr RPC call expression, e.g. x => x.SomeMethodCall(message); + * @param expr RPC call expression, e.g. x -> x.SomeMethodCall(message); * @return The JobID of the request. This can be used to find the appropriate [ServiceMethodResponse]. */ fun , TResponse : GeneratedMessage> sendMessage( @@ -43,7 +43,7 @@ class UnifiedService( * Sends a notification. * @param TRequest The type of the protobuf object which is the request to the RPC call. * @param TResponse The type of the protobuf object which is the response to the RPC call. - * @param expr RPC call expression, e.g. x => x.SomeMethodCall(message); + * @param expr RPC call expression, e.g. x -> x.SomeMethodCall(message); * @return null */ fun , TResponse : GeneratedMessage> sendNotification( From d43a217d86cecaff13c2a7eebc0f8bc1c9018c2d Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 12 Sep 2024 16:57:54 -0500 Subject: [PATCH 3/6] Simplify authentication classes some more --- .../AccessTokenGenerateResult.kt | 6 +- .../steam/authentication/AuthPollResult.kt | 2 +- .../steam/authentication/AuthSession.kt | 109 ++++++++---------- .../authentication/CredentialsAuthSession.kt | 10 +- .../steam/authentication/QrAuthSession.kt | 6 +- .../authentication/SteamAuthentication.kt | 18 ++- .../callback/ServiceMethodResponse.kt | 6 +- 7 files changed, 77 insertions(+), 80 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt index 0ff3beb0..9dfafc2b 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt @@ -1,14 +1,12 @@ package `in`.dragonbra.javasteam.steam.authentication -import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_AccessToken_GenerateForApp_Response import `in`.dragonbra.javasteam.steam.handlers.steamuser.LogOnDetails /** * Represents access token generation result. */ -class AccessTokenGenerateResult( - response: SteammessagesAuthSteamclient.CAuthentication_AccessToken_GenerateForApp_Response.Builder, -) { +class AccessTokenGenerateResult(response: CAuthentication_AccessToken_GenerateForApp_Response) { /** * New refresh token. diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthPollResult.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthPollResult.kt index f67bf8ad..a905e30e 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthPollResult.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthPollResult.kt @@ -5,7 +5,7 @@ import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclie /** * Represents authentication poll result. */ -class AuthPollResult(response: CAuthentication_PollAuthSessionStatus_Response.Builder) { +class AuthPollResult(response: CAuthentication_PollAuthSessionStatus_Response) { /** * Account name of authenticating account. diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt index a84465ec..3a33d2f6 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt @@ -2,7 +2,10 @@ package `in`.dragonbra.javasteam.steam.authentication import com.google.protobuf.ByteString import `in`.dragonbra.javasteam.enums.EResult -import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_AllowedConfirmation +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Request +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Response +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.EAuthSessionGuardType import `in`.dragonbra.javasteam.rpc.interfaces.IAuthentication import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -11,21 +14,13 @@ import kotlinx.coroutines.future.await import kotlinx.coroutines.future.future import java.util.concurrent.CompletableFuture -//region Aliases -typealias AllowedConfirmations = SteammessagesAuthSteamclient.CAuthentication_AllowedConfirmation -typealias AuthSessionStatusRequest = SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Request -typealias AuthSessionStatusResponse = SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Response -typealias AuthSessionStatusResponseBuilder = SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Response.Builder -typealias SessionGuardType = SteammessagesAuthSteamclient.EAuthSessionGuardType -//endregion - /** * Represents an authentication session which can be used to finish the authentication and get access tokens. * * @param authentication Unified messages class for Authentication related messages, see [IAuthentication]. * @param authenticator Authenticator object which will be used to handle 2-factor authentication if necessary. - * @param clientId Unique identifier of requestor, also used for routing, portion of QR code. - * @param requestId Unique request ID to be presented by requestor at poll time. + * @param clientID Unique identifier of requestor, also used for routing, portion of QR code. + * @param requestID Unique request ID to be presented by requestor at poll time. * @param allowedConfirmations Confirmation types that will be able to confirm the request. * @param pollingInterval Refresh interval with which requestor should call PollAuthSessionStatus. */ @@ -33,9 +28,9 @@ typealias SessionGuardType = SteammessagesAuthSteamclient.EAuthSessionGuardType open class AuthSession( val authentication: SteamAuthentication, val authenticator: IAuthenticator?, - var clientId: Long, // Should be 'private set' - val requestId: ByteArray, - var allowedConfirmations: List, + var clientID: Long, // Should be 'private set' + val requestID: ByteArray, + var allowedConfirmations: List, val pollingInterval: Float, ) { @@ -67,14 +62,14 @@ open class AuthSession( var preferredConfirmation = allowedConfirmations.firstOrNull() ?: throw AuthenticationException("There are no allowed confirmations") - if (preferredConfirmation.confirmationType == SessionGuardType.k_EAuthSessionGuardType_Unknown) { + if (preferredConfirmation.confirmationType == EAuthSessionGuardType.k_EAuthSessionGuardType_Unknown) { throw AuthenticationException("There are no allowed confirmations") } // If an authenticator is provided and the device confirmation is available, allow consumers to choose whether they want to // simply poll until confirmation is accepted, or whether they want to fall back to the next preferred confirmation type. authenticator?.let { auth -> - if (preferredConfirmation.confirmationType == SessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation) { + if (preferredConfirmation.confirmationType == EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation) { val prefersToPollForConfirmation = auth.acceptDeviceConfirmation().await() if (!prefersToPollForConfirmation) { @@ -92,19 +87,19 @@ open class AuthSession( var pollLoop = false when (preferredConfirmation.confirmationType) { - SessionGuardType.k_EAuthSessionGuardType_None -> Unit // // No steam guard - SessionGuardType.k_EAuthSessionGuardType_EmailCode, - SessionGuardType.k_EAuthSessionGuardType_DeviceCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_None -> Unit // // No steam guard + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode, -> { // 2-factor code from the authenticator app or sent to an email handleCodeAuth(preferredConfirmation) } - SessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation -> { + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation -> { // This is a prompt that appears in the Steam mobile app pollLoop = true } - // SessionGuardType.k_EAuthSessionGuardType_EmailConfirmation -> Unit // Unknown - // SessionGuardType.k_EAuthSessionGuardType_MachineToken -> Unit // Unknown + // EAuthSessionGuardType.k_EAuthSessionGuardType_EmailConfirmation -> Unit // Unknown + // EAuthSessionGuardType.k_EAuthSessionGuardType_MachineToken -> Unit // Unknown else -> throw AuthenticationException( "Unsupported confirmation type ${preferredConfirmation.confirmationType}." ) @@ -118,7 +113,7 @@ open class AuthSession( } @Throws(AuthenticationException::class) - private suspend fun handleCodeAuth(preferredConfirmation: AllowedConfirmations) { + private suspend fun handleCodeAuth(preferredConfirmation: CAuthentication_AllowedConfirmation) { val credentialsAuthSession = this as? CredentialsAuthSession ?: throw AuthenticationException( "Got ${preferredConfirmation.confirmationType} confirmation type in a session " + @@ -132,8 +127,8 @@ open class AuthSession( } val expectedInvalidCodeResult = when (preferredConfirmation.confirmationType) { - SessionGuardType.k_EAuthSessionGuardType_EmailCode -> EResult.InvalidLoginAuthCode - SessionGuardType.k_EAuthSessionGuardType_DeviceCode -> EResult.TwoFactorCodeMismatch + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode -> EResult.InvalidLoginAuthCode + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode -> EResult.TwoFactorCodeMismatch else -> throw AuthenticationException("\'${preferredConfirmation.confirmationType}\' not implemented") } @@ -143,11 +138,11 @@ open class AuthSession( while (waitingForValidCode) { try { val task = when (preferredConfirmation.confirmationType) { - SessionGuardType.k_EAuthSessionGuardType_EmailCode -> { + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode -> { val msg = preferredConfirmation.associatedMessage authenticator.getEmailCode(msg, previousCodeWasIncorrect).await() } - SessionGuardType.k_EAuthSessionGuardType_DeviceCode -> { + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode -> { authenticator.getDeviceCode(previousCodeWasIncorrect).await() } else -> throw AuthenticationException() @@ -183,9 +178,10 @@ open class AuthSession( */ @Throws(AuthenticationException::class) fun pollAuthSessionStatus(): AuthPollResult? { - val request = AuthSessionStatusRequest.newBuilder() - request.clientId = clientId - request.requestId = ByteString.copyFrom(requestId) + val request = CAuthentication_PollAuthSessionStatus_Request.newBuilder().apply { + this.clientId = clientID + this.requestId = ByteString.copyFrom(requestID) + } val message = authentication.authenticationService.sendMessage { api -> api.PollAuthSessionStatus(request.build()) @@ -196,25 +192,22 @@ open class AuthSession( throw AuthenticationException("Failed to poll status", message.result) } - val response: AuthSessionStatusResponseBuilder = - message.getDeserializedResponse(AuthSessionStatusResponse::class.java) - - if (response.newClientId > 0) { - clientId = response.newClientId - } + val response = message.getDeserializedResponse( + CAuthentication_PollAuthSessionStatus_Response::class.java + ).build() handlePollAuthSessionStatusResponse(response) - if (response.refreshToken.isNotEmpty()) { - return AuthPollResult(response) - } - - return null + return if (response.refreshToken.isNotEmpty()) AuthPollResult(response) else null } - internal open fun handlePollAuthSessionStatusResponse(response: AuthSessionStatusResponseBuilder) { - if (response.newClientId != 0L) { - clientId = response.newClientId + /** + * Handles poll authentication session status response. + * @param response The response. + */ + internal open fun handlePollAuthSessionStatusResponse(response: CAuthentication_PollAuthSessionStatus_Response) { + if (response.hasNewClientId()) { + clientID = response.newClientId } } @@ -223,21 +216,19 @@ open class AuthSession( * @param confirmations the list of confirmations * @return a sorted list of confirmations */ - private fun sortConfirmations(confirmations: List): List { + private fun sortConfirmations( + confirmations: List, + ): List { val preferredConfirmationTypes = arrayOf( - SessionGuardType.k_EAuthSessionGuardType_None, - SessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation, - SessionGuardType.k_EAuthSessionGuardType_DeviceCode, - SessionGuardType.k_EAuthSessionGuardType_EmailCode, - SessionGuardType.k_EAuthSessionGuardType_EmailConfirmation, - SessionGuardType.k_EAuthSessionGuardType_MachineToken, - SessionGuardType.k_EAuthSessionGuardType_Unknown - ) - - val sortOrder = preferredConfirmationTypes.withIndex().associate { (index, value) -> value to index } - - return confirmations.sortedBy { x -> - sortOrder[x.confirmationType] ?: Int.MAX_VALUE - } + EAuthSessionGuardType.k_EAuthSessionGuardType_None, + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation, + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailConfirmation, + EAuthSessionGuardType.k_EAuthSessionGuardType_MachineToken, + EAuthSessionGuardType.k_EAuthSessionGuardType_Unknown, + ).withIndex().associate { it.value to it.index } + + return confirmations.sortedBy { preferredConfirmationTypes[it.confirmationType] ?: Int.MAX_VALUE } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt index 7be1615e..9f82f7c5 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt @@ -13,12 +13,12 @@ import `in`.dragonbra.javasteam.types.SteamID class CredentialsAuthSession( authentication: SteamAuthentication, authenticator: IAuthenticator?, - response: CAuthentication_BeginAuthSessionViaCredentials_Response.Builder, + response: CAuthentication_BeginAuthSessionViaCredentials_Response, ) : AuthSession( authentication = authentication, authenticator = authenticator, - clientId = response.clientId, - requestId = response.requestId.toByteArray(), + clientID = response.clientId, + requestID = response.requestId.toByteArray(), allowedConfirmations = response.allowedConfirmationsList, pollingInterval = response.interval ) { @@ -33,9 +33,9 @@ class CredentialsAuthSession( * @throws AuthenticationException . */ @Throws(AuthenticationException::class) - fun sendSteamGuardCode(code: String?, codeType: EAuthSessionGuardType?) { + fun sendSteamGuardCode(code: String, codeType: EAuthSessionGuardType) { val request = CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request.newBuilder().apply { - this.clientId = this@CredentialsAuthSession.clientId // Could rename this to clientID instead. + this.clientId = clientID this.steamid = steamID.convertToUInt64() this.code = code this.codeType = codeType diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt index 4d584179..9f0250a9 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt @@ -13,8 +13,8 @@ class QrAuthSession( ) : AuthSession( authentication = authentication, authenticator = authenticator, - clientId = response.clientId, - requestId = response.requestId.toByteArray(), + clientID = response.clientId, + requestID = response.requestId.toByteArray(), allowedConfirmations = response.allowedConfirmationsList, pollingInterval = response.interval ) { @@ -30,7 +30,7 @@ class QrAuthSession( */ var challengeUrlChanged: IChallengeUrlChanged? = null - override fun handlePollAuthSessionStatusResponse(response: CAuthentication_PollAuthSessionStatus_Response.Builder) { + override fun handlePollAuthSessionStatusResponse(response: CAuthentication_PollAuthSessionStatus_Response) { super.handlePollAuthSessionStatusResponse(response) if (response.newChallengeUrl.isNotEmpty()) { diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt index 60b45527..a7980dbe 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt @@ -32,7 +32,11 @@ import kotlin.coroutines.cancellation.CancellationException * @constructor Initializes a new instance of the [SteamAuthentication] class. * @param steamClient this instance will be associated with. */ -class SteamAuthentication(private val steamClient: SteamClient) { +class SteamAuthentication +@Throws(NullPointerException::class) +constructor( + private val steamClient: SteamClient, +) { companion object { // private val logger = LogManager.getLogger(SteamAuthentication::class.java) @@ -54,7 +58,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { * @throws AuthenticationException if getting the public key failed. */ @Throws(AuthenticationException::class) - private fun getPasswordRSAPublicKey(accountName: String?): CAuthentication_GetPasswordRSAPublicKey_Response.Builder { + private fun getPasswordRSAPublicKey(accountName: String?): CAuthentication_GetPasswordRSAPublicKey_Response { val request = CAuthentication_GetPasswordRSAPublicKey_Request.newBuilder().apply { this.accountName = accountName } @@ -67,7 +71,9 @@ class SteamAuthentication(private val steamClient: SteamClient) { throw AuthenticationException("Failed to get password public key", message.result) } - return message.getDeserializedResponse(CAuthentication_GetPasswordRSAPublicKey_Response::class.java) + return message.getDeserializedResponse( + CAuthentication_GetPasswordRSAPublicKey_Response::class.java + ).build() } /** @@ -77,7 +83,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { * @param allowRenewal If true, allow renewing the token. * @return A [AccessTokenGenerateResult] containing the new token */ - @Throws(IllegalArgumentException::class, IllegalArgumentException::class) + @Throws(IllegalArgumentException::class) @JvmOverloads fun generateAccessTokenForApp( steamID: SteamID, @@ -103,7 +109,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { val response = message.getDeserializedResponse( CAuthentication_AccessToken_GenerateForApp_Response::class.java - ) + ).build() return AccessTokenGenerateResult(response) } @@ -220,7 +226,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { val response = message.getDeserializedResponse( CAuthentication_BeginAuthSessionViaCredentials_Response::class.java - ) + ).build() return CredentialsAuthSession(this, authSessionDetails.authenticator, response) } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodResponse.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodResponse.kt index 703c9323..cf37a15a 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodResponse.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodResponse.kt @@ -69,6 +69,8 @@ class ServiceMethodResponse(packetMsg: PacketClientMsgProtobuf) : CallbackMsg() * @param T Protobuf type of the response message. * @return The response to the message sent through [SteamUnifiedMessages]. */ - fun > getDeserializedResponse(clazz: Class): T = - ClientMsgProtobuf(clazz, packetMsg).body + fun > getDeserializedResponse(clazz: Class): T { + val msg = ClientMsgProtobuf(clazz, packetMsg) + return msg.body + } } From 68a2cde7eb919ca3fbe4129fccbb337e42dded6d Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Mon, 16 Sep 2024 20:04:52 -0500 Subject: [PATCH 4/6] Add NoResponse to rpc methods that do not need a response. --- .../generators/rpc/parser/ProtoParser.kt | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt b/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt index 395f5583..1d52d7d7 100644 --- a/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt +++ b/buildSrc/src/main/kotlin/in/dragonbra/generators/rpc/parser/ProtoParser.kt @@ -1,7 +1,6 @@ package `in`.dragonbra.generators.rpc.parser import com.squareup.kotlinpoet.* -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import `in`.dragonbra.generators.rpc.RpcGenTask import java.io.File import java.util.* @@ -10,7 +9,6 @@ class ProtoParser(private val outputDir: File) { private companion object { private const val RPC_PACKAGE = "in.dragonbra.javasteam.rpc" - private const val SERVICE_PACKAGE = "${RPC_PACKAGE}.service" private const val INTERFACE_PACKAGE = "$RPC_PACKAGE.interfaces" private val suppressAnnotation = AnnotationSpec @@ -21,20 +19,10 @@ class ProtoParser(private val outputDir: File) { .addMember("%S", "FunctionName") // Service messages might be case-sensitive, preserve it. .build() - private val classAsyncJobSingle = ClassName( - "in.dragonbra.javasteam.types", - "AsyncJobSingle" - ) - private val classServiceMethodResponse = ClassName( - "in.dragonbra.javasteam.steam.handlers.steamunifiedmessages.callback", - "ServiceMethodResponse" - ) - - private val kDocNoResponse = """|No return value.""".trimMargin() private fun kDocReturns(requestClassName: ClassName, returnClassName: ClassName): String = """ |@param request The request. |@see [${requestClassName.simpleName}] - |@returns [${returnClassName.canonicalName}] + |@returns [${returnClassName.simpleName}] """.trimMargin() } @@ -42,7 +30,7 @@ class ProtoParser(private val outputDir: File) { * Open a .proto file and find all service interfaces. * Then grab the name of the RPC interface name and everything between the curly braces * Then loop through all RPC interface methods, destructuring them to name, type, and response and put them in a list. - * Collect the items into a [Service] and pass it off to [buildInterface] and [buildClass] + * Collect the items into a [Service] and pass it off to [buildInterface] */ fun parseFile(file: File) { Regex("""service\s+(\w+)\s*\{([^}]*)}""") @@ -121,19 +109,21 @@ class ProtoParser(private val outputDir: File) { .addModifiers(KModifier.ABSTRACT) .addParameter("request", requestClassName) - // Add method kDoc - // Add `AsyncJobSingle` if there is a response - if (method.responseType == "NoResponse") { - funBuilder.addKdoc(kDocNoResponse) + // Add the appropriate return class + val returnPackageName = if (method.responseType == "NoResponse") { + "SteammessagesUnifiedBaseSteamclient" } else { - val returnClassName = ClassName( - packageName = "in.dragonbra.javasteam.protobufs.steamclient.$protoFileName", - method.responseType - ) - val kDoc = kDocReturns(requestClassName, returnClassName) - funBuilder.addKdoc(kDoc) - .returns(returnClassName) + protoFileName } + val returnClassName = ClassName( + packageName = "in.dragonbra.javasteam.protobufs.steamclient.$returnPackageName", + method.responseType + ) + + // Add method kDoc + val kDoc = kDocReturns(requestClassName, returnClassName) + funBuilder.addKdoc(kDoc) + .returns(returnClassName) // Add the function to the interface class. iBuilder.addFunction(funBuilder.build()) From 189a5110015f191547cbb9738ff079b3fb070291 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Mon, 16 Sep 2024 23:00:53 -0500 Subject: [PATCH 5/6] Update dependencies. Potentially fix UnifiedService to be Java compatible. This hopefully should not require kotlin-stdlib dependency. --- build.gradle.kts | 1 - gradle/libs.versions.toml | 5 +-- .../steamunifiedmessages/UnifiedService.kt | 43 +++++++++++++++++-- .../javasteam/util/compat/FunctionCompat1.kt | 10 +++++ 4 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 src/main/java/in/dragonbra/javasteam/util/compat/FunctionCompat1.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2020c914..41192493 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -116,7 +116,6 @@ dependencies { implementation(libs.commons.validator) implementation(libs.gson) implementation(libs.kotlin.coroutines) - implementation(libs.kotlin.stdib) implementation(libs.okHttp) implementation(libs.protobuf.java) implementation(libs.webSocket) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9f8d3486..f38a2e44 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,9 +18,9 @@ commons-validator = "1.9.0" # https://mvnrepository.com/artifact/commons-validat gson = "2.11.0" # https://mvnrepository.com/artifact/com.google.code.gson/gson jacoco = "0.8.12" # https://www.eclemma.org/jacoco javaWebSocket = "1.5.7" # https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -kotlin-coroutines = "1.8.1" # https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core +kotlin-coroutines = "1.9.0" # https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core okHttp = "5.0.0-alpha.14" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -protobuf = "4.28.0" # https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java +protobuf = "4.28.1" # https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java protobuf-gradle = "0.9.4" # https://mvnrepository.com/artifact/com.google.protobuf/protobuf-gradle-plugin publishPlugin = "1.3.0" # https://mvnrepository.com/artifact/io.github.gradle-nexus/publish-plugin qrCode = "1.0.1" # https://mvnrepository.com/artifact/pro.leaco.qrcode/console-qrcode @@ -38,7 +38,6 @@ commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "co commons-validator = { module = "commons-validator:commons-validator", version.ref = "commons-validator" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } -kotlin-stdib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" } protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt index 9d890e03..88d7217f 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/UnifiedService.kt @@ -3,6 +3,7 @@ package `in`.dragonbra.javasteam.steam.handlers.steamunifiedmessages import com.google.protobuf.GeneratedMessage import `in`.dragonbra.javasteam.steam.handlers.steamunifiedmessages.callback.ServiceMethodResponse import `in`.dragonbra.javasteam.types.AsyncJobSingle +import `in`.dragonbra.javasteam.util.compat.FunctionCompat1 import `in`.dragonbra.javasteam.util.log.LogManager import java.lang.reflect.Proxy @@ -26,6 +27,32 @@ class UnifiedService( private val logger = LogManager.getLogger(UnifiedService::class.java) } + // Maybe not needed... + /** + * Java Compat: Sends a message. + * Results are returned in a [ServiceMethodResponse]. + * The returned [AsyncJobSingle] can also be awaited to retrieve the callback result. + * @param TRequest The type of the protobuf object which is the request to the RPC call + * @param TResponse The type of the protobuf object which is the response to the RPC call. + * @param expr RPC call expression, e.g. x -> x.SomeMethodCall(message); + * @return The JobID of the request. This can be used to find the appropriate [ServiceMethodResponse]. + */ + fun , TResponse : GeneratedMessage> sendMessageCompat( + expr: FunctionCompat1, + ): AsyncJobSingle = sendMessageOrNotification(expr, false)!! + + // Maybe not needed... + /** + * Java Compat: Sends a notification. + * @param TRequest The type of the protobuf object which is the request to the RPC call.1 + * @param TResponse The type of the protobuf object which is the response to the RPC call. + * @param expr RPC call expression, e.g. x -> x.SomeMethodCall(message); + * @return null + */ + fun , TResponse : GeneratedMessage> sendNotificationCompat( + expr: FunctionCompat1, + ): AsyncJobSingle? = sendMessageOrNotification(expr, true) + /** * Sends a message. * Results are returned in a [ServiceMethodResponse]. @@ -37,7 +64,12 @@ class UnifiedService( */ fun , TResponse : GeneratedMessage> sendMessage( expr: (TService) -> TResponse, - ): AsyncJobSingle = sendMessageOrNotification(expr, false)!! + ): AsyncJobSingle { + val compatExpr = object : FunctionCompat1 { + override fun invoke(p1: TService): TResponse = expr(p1) + } + return sendMessageOrNotification(compatExpr, false)!! + } /** * Sends a notification. @@ -48,10 +80,15 @@ class UnifiedService( */ fun , TResponse : GeneratedMessage> sendNotification( expr: (TService) -> TResponse, - ): AsyncJobSingle? = sendMessageOrNotification(expr, true) + ): AsyncJobSingle? { + val compatExpr = object : FunctionCompat1 { + override fun invoke(p1: TService): TResponse = expr(p1) + } + return sendMessageOrNotification(compatExpr, true) + } private fun , TResponse : GeneratedMessage> sendMessageOrNotification( - expr: (TService) -> TResponse, + expr: FunctionCompat1, isNotification: Boolean, ): AsyncJobSingle? { var methodName: String? = null diff --git a/src/main/java/in/dragonbra/javasteam/util/compat/FunctionCompat1.kt b/src/main/java/in/dragonbra/javasteam/util/compat/FunctionCompat1.kt new file mode 100644 index 00000000..7262c01b --- /dev/null +++ b/src/main/java/in/dragonbra/javasteam/util/compat/FunctionCompat1.kt @@ -0,0 +1,10 @@ +package `in`.dragonbra.javasteam.util.compat + +/** + * Compat class basically kotlin stdlib Function.kt + * https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/runtime/kotlin/jvm/functions/FunctionCompat1.kt + */ +interface FunctionCompat1 : Function { + /** Invokes the function with the specified argument. */ + operator fun invoke(p1: P1): R +} From 7c7b6adbe22e0501e61c8d2cc9f0464648f96e13 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sun, 29 Sep 2024 01:08:30 -0500 Subject: [PATCH 6/6] Remove ICallbackMsg, inherit CallbackMsg directly --- .../javasteam/steam/steamclient/SteamClient.kt | 11 +++++------ .../steam/steamclient/callbackmgr/Callback.kt | 2 +- .../steamclient/callbackmgr/CallbackManager.kt | 10 +++++----- .../steam/steamclient/callbackmgr/CallbackMsg.kt | 4 ++-- .../steam/steamclient/callbackmgr/ICallbackMsg.kt | 15 --------------- 5 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/ICallbackMsg.kt diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt index 043f1344..493a83fb 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt @@ -21,7 +21,6 @@ import `in`.dragonbra.javasteam.steam.handlers.steamuser.SteamUser import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.SteamUserStats import `in`.dragonbra.javasteam.steam.handlers.steamworkshop.SteamWorkshop import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg -import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.ICallbackMsg import `in`.dragonbra.javasteam.steam.steamclient.callbacks.CMListCallback import `in`.dragonbra.javasteam.steam.steamclient.callbacks.ConnectedCallback import `in`.dragonbra.javasteam.steam.steamclient.callbacks.DisconnectedCallback @@ -57,7 +56,7 @@ class SteamClient @JvmOverloads constructor( private val processStartTime: Date - private val callbackQueue = Channel(Channel.UNLIMITED) + private val callbackQueue = Channel(Channel.UNLIMITED) internal val jobManager: AsyncJobManager // What does this even do now? @@ -142,13 +141,13 @@ class SteamClient @JvmOverloads constructor( * Gets the next callback object in the queue, and removes it. * @return The next callback in the queue, or null if no callback is waiting. */ - fun getCallback(): ICallbackMsg? = callbackQueue.tryReceive().getOrNull() + fun getCallback(): CallbackMsg? = callbackQueue.tryReceive().getOrNull() /** * Blocks the calling thread until a callback object is posted to the queue, and removes it. * @return The callback object from the queue. */ - fun waitForCallback(): ICallbackMsg = runBlocking(Dispatchers.Default) { + fun waitForCallback(): CallbackMsg = runBlocking(Dispatchers.Default) { callbackQueue.receive() } @@ -156,14 +155,14 @@ class SteamClient @JvmOverloads constructor( * Asynchronously awaits until a callback object is posted to the queue, and removes it. * @return The callback object from the queue. */ - suspend fun waitForCallbackAsync(): ICallbackMsg = callbackQueue.receive() + suspend fun waitForCallbackAsync(): CallbackMsg = callbackQueue.receive() /** * Blocks the calling thread until a callback object is posted to the queue, or null after the timeout has elapsed. * @param timeout The length of time to block in ms. * @return A callback object from the queue if a callback has been posted, or null if the timeout has elapsed. */ - fun waitForCallback(timeout: Long): ICallbackMsg? = runBlocking { + fun waitForCallback(timeout: Long): CallbackMsg? = runBlocking { withTimeoutOrNull(timeout) { callbackQueue.receive() } diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/Callback.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/Callback.kt index 77c6156a..e038fa3e 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/Callback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/Callback.kt @@ -5,7 +5,7 @@ import `in`.dragonbra.javasteam.util.compat.Consumer import java.io.Closeable @Suppress("unused") -class Callback @JvmOverloads constructor( +class Callback @JvmOverloads constructor( override val callbackType: Class, func: Consumer?, private var mgr: ICallbackMgrInternals? = null, diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackManager.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackManager.kt index dc80671f..3d032786 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackManager.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackManager.kt @@ -26,7 +26,7 @@ class CallbackManager(private val steamClient: SteamClient) : ICallbackMgrIntern * @return true if a callback has been run, false otherwise. */ fun runCallbacks(): Boolean { - val call: ICallbackMsg = steamClient.getCallback() ?: return false + val call = steamClient.getCallback() ?: return false handle(call) return true @@ -40,7 +40,7 @@ class CallbackManager(private val steamClient: SteamClient) : ICallbackMgrIntern * @return true if a callback has been run, false otherwise. */ fun runWaitCallbacks(timeout: Long): Boolean { - val call: ICallbackMsg = steamClient.waitForCallback(timeout) ?: return false + val call = steamClient.waitForCallback(timeout) ?: return false handle(call) return true @@ -90,7 +90,7 @@ class CallbackManager(private val steamClient: SteamClient) : ICallbackMgrIntern * @param callbackFunc The function to invoke with the callback. * @return An [Closeable]. Disposing of the return value will unsubscribe the callbackFunc . */ - fun subscribe( + fun subscribe( callbackType: Class, jobID: JobID, callbackFunc: Consumer, @@ -107,7 +107,7 @@ class CallbackManager(private val steamClient: SteamClient) : ICallbackMgrIntern * @param callbackFunc The function to invoke with the callback. * @return An [Closeable]. Disposing of the return value will unsubscribe the callbackFunc. */ - fun subscribe( + fun subscribe( callbackType: Class, callbackFunc: Consumer, ): Closeable = subscribe(callbackType, JobID.INVALID, callbackFunc) @@ -124,7 +124,7 @@ class CallbackManager(private val steamClient: SteamClient) : ICallbackMgrIntern registeredCallbacks.remove(callback) } - private fun handle(call: ICallbackMsg) { + private fun handle(call: CallbackMsg) { val type = call.javaClass // find handlers interested in this callback diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackMsg.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackMsg.kt index 144f06a2..d0b96aff 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackMsg.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/CallbackMsg.kt @@ -10,10 +10,10 @@ import `in`.dragonbra.javasteam.types.JobID * @author lngtr * @since 2018-02-22 */ -abstract class CallbackMsg : ICallbackMsg { +abstract class CallbackMsg { /** * Gets or sets the job ID this callback refers to. If it is not a job callback, it will be [JobID.INVALID]. */ - override var jobID: JobID = JobID.INVALID + var jobID: JobID = JobID.INVALID } diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/ICallbackMsg.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/ICallbackMsg.kt deleted file mode 100644 index 1686b525..00000000 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/callbackmgr/ICallbackMsg.kt +++ /dev/null @@ -1,15 +0,0 @@ -package `in`.dragonbra.javasteam.steam.steamclient.callbackmgr - -import `in`.dragonbra.javasteam.types.JobID - -/** - * A callback message - */ -interface ICallbackMsg { - - /** - * The [JobID] that this callback is associated with. If there is no job associated, - * then this will be [JobID.INVALID] - */ - var jobID: JobID -}