From 13fd18a9f3a602f30d23474010f19674675bbc9e Mon Sep 17 00:00:00 2001 From: KremeCN <3265494849@qq.com> Date: Sun, 23 Aug 2026 07:13:15 +0800 Subject: [PATCH] fix: reject invalid infrared batch responses --- .../abox/manager/absdk/bridge/AbsdkBridge.kt | 15 +++++++++--- .../absdk/bridge/AbsdkDeviceBridgeTest.kt | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/absdk-bridge/src/main/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkBridge.kt b/absdk-bridge/src/main/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkBridge.kt index eb6f337..9c26d29 100644 --- a/absdk-bridge/src/main/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkBridge.kt +++ b/absdk-bridge/src/main/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkBridge.kt @@ -249,12 +249,19 @@ class AbsdkBridge internal constructor( codes: List, response: AbsdkResponse?, ): DomainResult> { - val results = response?.data?.get(IR_RESULTS_KEY) as? List<*> + val validResponse = response?.takeIf { it.code in IR_DOWNLOAD_RESULT_CODES } ?: return DomainResult.Failure(AbsdkErrorMapper.fromResponse(response)) + val results = validResponse.data[IR_RESULTS_KEY] as? List<*> + ?: return DomainResult.Failure( + DomainError.OperationFailed( + externalCode = validResponse.code, + detail = "Missing infrared download results", + ), + ) if (results.size != codes.size || results.any { it !is AbsdkIrResult }) { return DomainResult.Failure( DomainError.OperationFailed( - externalCode = response.code, + externalCode = validResponse.code, detail = "Malformed infrared download result", ), ) @@ -267,7 +274,7 @@ class AbsdkBridge internal constructor( ) { return DomainResult.Failure( DomainError.OperationFailed( - externalCode = response.code, + externalCode = validResponse.code, detail = "Infrared download results do not match their requests", ), ) @@ -318,6 +325,8 @@ class AbsdkBridge internal constructor( } private const val SUCCESS_CODE = "00000" +private const val PARTIAL_IR_DOWNLOAD_CODE = "20204" +private val IR_DOWNLOAD_RESULT_CODES = setOf(SUCCESS_CODE, PARTIAL_IR_DOWNLOAD_CODE) private sealed interface ClientCall { data class Completed(val response: AbsdkResponse?) : ClientCall diff --git a/absdk-bridge/src/test/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkDeviceBridgeTest.kt b/absdk-bridge/src/test/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkDeviceBridgeTest.kt index 5f52112..84002ea 100644 --- a/absdk-bridge/src/test/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkDeviceBridgeTest.kt +++ b/absdk-bridge/src/test/kotlin/com/flagship/abox/manager/absdk/bridge/AbsdkDeviceBridgeTest.kt @@ -11,6 +11,7 @@ import com.flagship.abox.manager.domain.SocketPowerState import com.flagship.abox.manager.domain.Username import java.io.IOException import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -114,6 +115,29 @@ class AbsdkDeviceBridgeTest { ) } + @Test + fun infraredTokenErrorCannotBeMaskedByValidResults() = runTest { + val code = InfraredCode(DeviceName("ir-1"), "power", "code-1") + val client = ScriptedAbsdkClient().apply { + downloadResponse = AbsdkResponse( + code = "10002", + message = "expired", + data = mapOf( + "ret" to listOf(AbsdkIrResult("ir-1", "power", "1")), + ), + ) + } + val bridge = authenticatedBridge(client) + + val result = bridge.downloadCodes(profile, listOf(code)) + + assertTrue((result as DomainResult.Failure).error is DomainError.TokenInvalid) + assertEquals(null, bridge.observeSession(profile).first()) + val second = bridge.sendKey(profile, DeviceName("ir-1"), "power") + assertTrue((second as DomainResult.Failure).error is DomainError.TokenInvalid) + assertEquals(listOf("login:demo", "ir-download"), client.calls) + } + @Test fun infraredMalformedResultsFailTheBatch() = runTest { val code = InfraredCode(DeviceName("ir-1"), "power", "code-1")