fix: AirPlayProtocol 编译错误修复

- StreamState 构造函数语法修正 (data class 参数不允许 val 声明)
- Byte 位运算需转 Int: and/or/xor 使用 .toInt() 再转回 Byte
- when 表达式赋值重写: 修复 val decrypted 不可重赋值问题
- X25519.scalarMult 公钥生成改用6参数版本 (base point)
- dd-plist: NSNumber 类型匹配, NSArray.getArray(), NSDictionary.put()
- PropertyListParser.parse(byte[]) 替代 InputStream
- clientEdPublicKey!! 非 null 断言
This commit is contained in:
2026-05-31 12:25:30 +08:00
parent 5ac2798b08
commit 6753f12453

View File

@@ -8,6 +8,7 @@ import com.dd.plist.NSDictionary
import com.dd.plist.NSNumber
import com.dd.plist.NSString
import com.dd.plist.PropertyListParser
import com.dd.plist.PropertyListParser
import kotlinx.coroutines.*
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator
import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters
@@ -305,7 +306,7 @@ class AirPlayProtocol(
System.arraycopy(ed25519KeyPair.second, 0, verifyData, 96, 32)
val verified = ed25519Verify(
pairingState.clientEdPublicKey, verifyData, clientSignature
pairingState.clientEdPublicKey!!, verifyData, clientSignature
)
if (verified) {
@@ -375,14 +376,14 @@ class AirPlayProtocol(
// Parse binary plist from SETUP body
try {
val plist = PropertyListParser.parse(data.inputStream()) as? NSDictionary ?: run {
val plist = PropertyListParser.parse(data) as? NSDictionary ?: run {
Log.e(TAG, "SETUP body 不是有效的 plist")
sendResponse(output, 400, "Bad Request")
return
}
val streams = plist["streams"] as? NSArray
val streamDict = streams?.array?.firstOrNull() as? NSDictionary
val streamDict = streams?.getArray()?.firstOrNull() as? NSDictionary
if (streamDict != null) {
val streamType = (streamDict["type"] as? NSNumber)?.longValue()?.toInt()
@@ -399,7 +400,7 @@ class AirPlayProtocol(
deriveStreamKeys(streamConnectionId)
val responsePlist = NSDictionary()
responsePlist.put("dataPort", NSNumber(streamState?.videoDataPort ?: 7001L))
responsePlist.put("dataPort", NSNumber((streamState?.videoDataPort ?: 7001).toLong()))
val responseBytes = BinaryPropertyListWriter.writeToArray(responsePlist)
sendResponse(output, 200, "OK", responseBytes, "application/octet-stream")
@@ -412,8 +413,8 @@ class AirPlayProtocol(
Log.w(TAG, "AirPlay 音频格式: $audioFormat")
val responsePlist = NSDictionary()
responsePlist.put("dataPort", NSNumber(streamState?.audioDataPort ?: 7002L))
responsePlist.put("controlPort", NSNumber(streamState?.audioControlPort ?: 6001L))
responsePlist.put("dataPort", NSNumber((streamState?.audioDataPort ?: 7002).toLong()))
responsePlist.put("controlPort", NSNumber((streamState?.audioControlPort ?: 6001).toLong()))
val responseBytes = BinaryPropertyListWriter.writeToArray(responsePlist)
sendResponse(output, 200, "OK", responseBytes, "application/octet-stream")
@@ -432,9 +433,11 @@ class AirPlayProtocol(
val eventPort = (plist["eventPort"] as? NSNumber)?.longValue()?.toInt()
?: 6002
val localSessionId = (plist["sessionID"] as? NSNumber)?.longValue()
?: System.nanoTime()
streamState = StreamState(
val localSessionId = (plist["sessionID"] as? NSNumber)?.longValue()
?: System.nanoTime(),
localSessionId = localSessionId,
videoDataPort = 7001,
audioDataPort = 7002,
audioControlPort = 6001,
@@ -512,21 +515,13 @@ class AirPlayProtocol(
val encryptedPayload = data.copyOfRange(4, minOf(4 + payloadSize, data.size))
try {
val decrypted: ByteArray
when (payloadType) {
0 -> {
// Encrypted video data — decrypt with AES-CTR
decrypted = decryptVideoAesCtr(encryptedPayload)
?: continue
}
val decrypted: ByteArray = when (payloadType) {
0 -> decryptVideoAesCtr(encryptedPayload) ?: continue
1 -> {
// SPS/PPS data (not encrypted, contains codec config)
decrypted = processSpsPps(encryptedPayload)
if (decrypted.isEmpty()) continue
}
else -> {
decrypted = encryptedPayload
val sps = processSpsPps(encryptedPayload)
if (sps.isEmpty()) continue else sps
}
else -> encryptedPayload
}
val isKeyFrame = payloadType == 1 || isKeyFrameNAL(decrypted)
@@ -604,11 +599,14 @@ class AirPlayProtocol(
val privateKey = ByteArray(32)
SecureRandom().nextBytes(privateKey)
// Clamp the private key as per Curve25519 spec
privateKey[0] = (privateKey[0] and 0xF8).toByte()
privateKey[31] = ((privateKey[31] and 0x7F) or 0x40).toByte()
privateKey[0] = (privateKey[0].toInt() and 0xF8).toByte()
privateKey[31] = ((privateKey[31].toInt() and 0x7F) or 0x40).toByte()
val publicKey = ByteArray(32)
org.bouncycastle.math.ec.rfc7748.X25519.scalarMult(privateKey, 0, publicKey, 0)
// Base point for Curve25519 (9, 0, 0, ..., 0)
val basePoint = ByteArray(32)
basePoint[0] = 9
org.bouncycastle.math.ec.rfc7748.X25519.scalarMult(privateKey, 0, basePoint, 0, publicKey, 0)
return Pair(privateKey, publicKey)
}
@@ -657,8 +655,8 @@ class AirPlayProtocol(
val videoKey = ByteArray(16)
val videoIv = ByteArray(16)
for (i in 0 until 16) {
videoKey[i] = (keyHash[i] xor eaesKey[i]).toByte()
videoIv[i] = (ivHash[i] xor eaesKey[i]).toByte()
videoKey[i] = ((keyHash[i].toInt() xor eaesKey[i].toInt()) and 0xFF).toByte()
videoIv[i] = ((ivHash[i].toInt() xor eaesKey[i].toInt()) and 0xFF).toByte()
}
videoAesKey = SecretKeySpec(videoKey, "AES")
@@ -737,7 +735,7 @@ class AirPlayProtocol(
System.arraycopy(hash, 0, response, 6, minOf(32, 136))
// Pad remaining with streamConnectionId-derived bytes
for (i in 38 until 142) {
response[i] = (hash[i % hash.size] xor data[0]).toByte()
response[i] = ((hash[i % hash.size].toInt() xor data[0].toInt()) and 0xFF).toByte()
}
return response