88 lines
3.2 KiB
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],
|
|
])
|
|
}
|
|
}
|