stremio/apps/ios/VMC/Playback/ClearKeyMaterial.swift

88 lines
3.2 KiB
Swift

import Foundation
/// ClearKey kids/keys zoals Android `buildClearKeyLicenseJson` (hex 32 + GUID-endianness).
struct ClearKeyKeyMap {
/// kid-hex (lowercase, 32 chars) → key bytes (16)
private let keysByKid: [String: Data]
init(entries: [ClearKeyEntry]) {
var map: [String: Data] = [:]
for entry in entries {
guard let kid = Self.hex16(entry.kid), let key = Self.hex16(entry.key) else { continue }
let kidHex = kid.map { String(format: "%02x", $0) }.joined()
map[kidHex] = key
let swapped = Self.swapUuidEndianness(kid)
let swappedHex = swapped.map { String(format: "%02x", $0) }.joined()
if swappedHex != kidHex {
map[swappedHex] = key
}
}
keysByKid = map
}
var isEmpty: Bool { keysByKid.isEmpty }
/// Unieke key-waarden in de map (meestal 1).
var uniqueKeyCount: Int {
Set(keysByKid.values.map { $0.base64EncodedString() }).count
}
/// Eerste key — alleen veilig als er precies één unieke key is.
var primaryKey: Data? {
guard uniqueKeyCount == 1 else { return nil }
return keysByKid.values.first
}
func hasExactKey(forKid kid: Data) -> Bool {
guard kid.count == 16 else { return false }
if keysByKid[kid.map { String(format: "%02x", $0) }.joined()] != nil { return true }
let swapped = Self.swapUuidEndianness(kid)
return keysByKid[swapped.map { String(format: "%02x", $0) }.joined()] != nil
}
func key(forKid kid: Data) -> Data? {
if kid.count == 16 {
let hex = kid.map { String(format: "%02x", $0) }.joined()
if let k = keysByKid[hex] { return k }
let swapped = Self.swapUuidEndianness(kid)
let swappedHex = swapped.map { String(format: "%02x", $0) }.joined()
if let k = keysByKid[swappedHex] { return k }
}
// Streams met één key (Android parity): gebruik die key als KID-match faalt.
return primaryKey
}
static func sanitizeHex(_ raw: String) -> String {
raw.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: "0x", with: "", options: .caseInsensitive)
.replacingOccurrences(of: "-", with: "")
.replacingOccurrences(of: ":", with: "")
.replacingOccurrences(of: " ", with: "")
.lowercased()
}
static func hex16(_ raw: String) -> Data? {
let clean = sanitizeHex(raw)
guard clean.count == 32, clean.allSatisfy({ $0.isHexDigit }) else { return nil }
var out = Data(capacity: 16)
var i = clean.startIndex
while i < clean.endIndex {
let j = clean.index(i, offsetBy: 2)
guard let b = UInt8(clean[i..<j], radix: 16) else { return nil }
out.append(b)
i = j
}
return out
}
/// Microsoft GUID / sommige ClearKey packs: DWORD/WORD little-endian in eerste 8 bytes.
static func swapUuidEndianness(_ b: Data) -> Data {
guard b.count == 16 else { return b }
return Data([
b[3], b[2], b[1], b[0],
b[5], b[4],
b[7], b[6],
b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15],
])
}
}