iOS 0.5.44: fix tfhd default-sample-size flag, add per-lane decrypt diagnostics.
This commit is contained in:
parent
eed167824e
commit
9658ec6465
5 changed files with 55 additions and 7 deletions
|
|
@ -345,7 +345,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 56;
|
CURRENT_PROJECT_VERSION = 57;
|
||||||
DEVELOPMENT_TEAM = X25NYX3K49;
|
DEVELOPMENT_TEAM = X25NYX3K49;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
@ -360,7 +360,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks"
|
"@executable_path/Frameworks"
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.43;
|
MARKETING_VERSION = 0.5.44;
|
||||||
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -378,7 +378,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 56;
|
CURRENT_PROJECT_VERSION = 57;
|
||||||
DEVELOPMENT_TEAM = X25NYX3K49;
|
DEVELOPMENT_TEAM = X25NYX3K49;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
@ -393,7 +393,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks"
|
"@executable_path/Frameworks"
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.43;
|
MARKETING_VERSION = 0.5.44;
|
||||||
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,27 @@ enum CencMp4Decryptor {
|
||||||
var fallback: TrackCrypto?
|
var fallback: TrackCrypto?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Diagnose-hook (alleen aanroepen onder de crypto-lock van de proxy).
|
||||||
|
static var diagTag = ""
|
||||||
|
static var diagSink: ((String) -> Void)?
|
||||||
|
private static var diagCounts: [String: Int] = [:]
|
||||||
|
|
||||||
|
private static func diag(_ message: @autoclosure () -> String) {
|
||||||
|
guard let sink = diagSink else { return }
|
||||||
|
let n = diagCounts[diagTag, default: 0]
|
||||||
|
guard n < 8 else { return }
|
||||||
|
diagCounts[diagTag] = n + 1
|
||||||
|
sink("decrypt[\(diagTag)] \(message())")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func resetDiag() {
|
||||||
|
diagCounts.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func hex(_ d: Data) -> String {
|
||||||
|
d.map { String(format: "%02x", $0) }.joined()
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
static func process(data: inout Data, keys: ClearKeyKeyMap, state: inout SessionState) -> Bool {
|
static func process(data: inout Data, keys: ClearKeyKeyMap, state: inout SessionState) -> Bool {
|
||||||
guard !data.isEmpty, !keys.isEmpty else { return false }
|
guard !data.isEmpty, !keys.isEmpty else { return false }
|
||||||
|
|
@ -253,6 +274,7 @@ enum CencMp4Decryptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let crypto {
|
if let crypto {
|
||||||
|
diag("init track=\(trackId ?? 0) fmt=\(originalFormat ?? "-") scheme=\(crypto.scheme) pat=\(crypto.cryptByteBlock):\(crypto.skipByteBlock) iv=\(crypto.perSampleIvSize) civ=\(crypto.constantIv?.count ?? 0) kid=\(hex(crypto.kid)) exact=\(keys.hasExactKey(forKid: crypto.kid)) keys=\(keys.uniqueKeyCount)")
|
||||||
if let trackId {
|
if let trackId {
|
||||||
state.byTrackId[trackId] = crypto
|
state.byTrackId[trackId] = crypto
|
||||||
}
|
}
|
||||||
|
|
@ -263,6 +285,8 @@ enum CencMp4Decryptor {
|
||||||
state.fallback = crypto
|
state.fallback = crypto
|
||||||
}
|
}
|
||||||
_ = keys.key(forKid: crypto.kid)
|
_ = keys.key(forKid: crypto.kid)
|
||||||
|
} else {
|
||||||
|
diag("init track=\(trackId ?? 0) fmt=\(originalFormat ?? "-") clear (geen tenc)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -313,6 +337,8 @@ enum CencMp4Decryptor {
|
||||||
var sampleSizes: [Int] = []
|
var sampleSizes: [Int] = []
|
||||||
var sencSamples: [SencSample] = []
|
var sencSamples: [SencSample] = []
|
||||||
var perSampleIvSize = 8
|
var perSampleIvSize = 8
|
||||||
|
var tfhdFlagsSeen: UInt32 = 0
|
||||||
|
var trunFlagsSeen: UInt32 = 0
|
||||||
if let known = state.byTrackId.values.first, state.byTrackId.count == 1 {
|
if let known = state.byTrackId.values.first, state.byTrackId.count == 1 {
|
||||||
perSampleIvSize = known.perSampleIvSize
|
perSampleIvSize = known.perSampleIvSize
|
||||||
}
|
}
|
||||||
|
|
@ -332,14 +358,19 @@ enum CencMp4Decryptor {
|
||||||
baseDataOffset = Int64(readU64(data, o)); o += 8
|
baseDataOffset = Int64(readU64(data, o)); o += 8
|
||||||
dataOffsetPresent = true
|
dataOffsetPresent = true
|
||||||
}
|
}
|
||||||
if flags & 0x000002 != 0 {
|
if flags & 0x000002 != 0 { // sample-description-index
|
||||||
guard o + 4 <= tfhd.payloadEnd else { break }
|
guard o + 4 <= tfhd.payloadEnd else { break }
|
||||||
o += 4
|
o += 4
|
||||||
}
|
}
|
||||||
if flags & 0x000008 != 0 {
|
if flags & 0x000008 != 0 { // default-sample-duration
|
||||||
|
guard o + 4 <= tfhd.payloadEnd else { break }
|
||||||
|
o += 4
|
||||||
|
}
|
||||||
|
if flags & 0x000010 != 0 { // default-sample-size
|
||||||
guard o + 4 <= tfhd.payloadEnd else { break }
|
guard o + 4 <= tfhd.payloadEnd else { break }
|
||||||
defaultSampleSize = readU32(data, o); o += 4
|
defaultSampleSize = readU32(data, o); o += 4
|
||||||
}
|
}
|
||||||
|
tfhdFlagsSeen = flags
|
||||||
_ = versionFlags
|
_ = versionFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,6 +380,7 @@ enum CencMp4Decryptor {
|
||||||
let versionFlags = readU32(data, trun.payloadStart)
|
let versionFlags = readU32(data, trun.payloadStart)
|
||||||
let version = Int(versionFlags >> 24)
|
let version = Int(versionFlags >> 24)
|
||||||
let flags = versionFlags & 0x00FF_FFFF
|
let flags = versionFlags & 0x00FF_FFFF
|
||||||
|
trunFlagsSeen = flags
|
||||||
var o = trun.payloadStart + 4
|
var o = trun.payloadStart + 4
|
||||||
let sampleCount = Int(readU32(data, o)); o += 4
|
let sampleCount = Int(readU32(data, o)); o += 4
|
||||||
guard sampleCount >= 0, sampleCount < 100_000 else { continue }
|
guard sampleCount >= 0, sampleCount < 100_000 else { continue }
|
||||||
|
|
@ -425,6 +457,7 @@ enum CencMp4Decryptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !sampleSizes.isEmpty, sencSamples.count == sampleSizes.count || sencSamples.isEmpty == false else {
|
guard !sampleSizes.isEmpty, sencSamples.count == sampleSizes.count || sencSamples.isEmpty == false else {
|
||||||
|
diag("frag track=\(trackId) skip: samples=\(sampleSizes.count) senc=\(sencSamples.count) tfhd=0x\(String(tfhdFlagsSeen, radix: 16)) trun=0x\(String(trunFlagsSeen, radix: 16))")
|
||||||
// Advance cursor even if we cannot decrypt.
|
// Advance cursor even if we cannot decrypt.
|
||||||
for sz in sampleSizes { mdatCursor += sz }
|
for sz in sampleSizes { mdatCursor += sz }
|
||||||
continue
|
continue
|
||||||
|
|
@ -436,6 +469,7 @@ enum CencMp4Decryptor {
|
||||||
return keys.key(forKid: crypto.kid)
|
return keys.key(forKid: crypto.kid)
|
||||||
}()
|
}()
|
||||||
guard let key else {
|
guard let key else {
|
||||||
|
diag("frag track=\(trackId) skip: geen key (kid=\(crypto.map { hex($0.kid) } ?? "-") keys=\(keys.uniqueKeyCount))")
|
||||||
for sz in sampleSizes { mdatCursor += sz }
|
for sz in sampleSizes { mdatCursor += sz }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -468,6 +502,8 @@ enum CencMp4Decryptor {
|
||||||
}
|
}
|
||||||
mdatCursor += size
|
mdatCursor += size
|
||||||
}
|
}
|
||||||
|
let decryptedCount = didDecrypt ? min(sencSamples.count, sampleSizes.count) : 0
|
||||||
|
diag("frag track=\(trackId) crypto=\(crypto != nil) scheme=\(scheme) pat=\(cryptBlocks):\(skipBlocks) samples=\(sampleSizes.count) senc=\(sencSamples.count) iv=\(sencSamples.first?.iv.count ?? 0) subs=\(sencSamples.first?.subsamples?.count ?? -1) size0=\(sampleSizes.first ?? 0) dec=\(decryptedCount) tfhd=0x\(String(tfhdFlagsSeen, radix: 16)) trun=0x\(String(trunFlagsSeen, radix: 16)) mdat=\(mdat.payloadLength)")
|
||||||
// AVPlayer/VLC mogen geen tweede decrypt-poging doen op clear samples.
|
// AVPlayer/VLC mogen geen tweede decrypt-poging doen op clear samples.
|
||||||
if didDecrypt {
|
if didDecrypt {
|
||||||
for senc in findBoxesInRange(data, type: "senc", from: traf.payloadStart, to: traf.payloadEnd) {
|
for senc in findBoxesInRange(data, type: "senc", from: traf.payloadStart, to: traf.payloadEnd) {
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ final class ClearKeyDashProxy {
|
||||||
self.cryptoVideo = CencMp4Decryptor.SessionState()
|
self.cryptoVideo = CencMp4Decryptor.SessionState()
|
||||||
self.cryptoAudio = CencMp4Decryptor.SessionState()
|
self.cryptoAudio = CencMp4Decryptor.SessionState()
|
||||||
self.cryptoShared = CencMp4Decryptor.SessionState()
|
self.cryptoShared = CencMp4Decryptor.SessionState()
|
||||||
|
CencMp4Decryptor.resetDiag()
|
||||||
self.cryptoLock.unlock()
|
self.cryptoLock.unlock()
|
||||||
self.generation += 1
|
self.generation += 1
|
||||||
}
|
}
|
||||||
|
|
@ -623,6 +624,10 @@ final class ClearKeyDashProxy {
|
||||||
}
|
}
|
||||||
cryptoLock.lock()
|
cryptoLock.lock()
|
||||||
defer { cryptoLock.unlock() }
|
defer { cryptoLock.unlock() }
|
||||||
|
if CencMp4Decryptor.diagSink == nil {
|
||||||
|
CencMp4Decryptor.diagSink = { proxyLog.info("\($0, privacy: .public)") }
|
||||||
|
}
|
||||||
|
CencMp4Decryptor.diagTag = lane.rawValue
|
||||||
switch lane {
|
switch lane {
|
||||||
case .video:
|
case .video:
|
||||||
_ = CencMp4Decryptor.process(data: &data, keys: keys, state: &cryptoVideo)
|
_ = CencMp4Decryptor.process(data: &data, keys: keys, state: &cryptoVideo)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ struct ClearKeyKeyMap {
|
||||||
return keysByKid.values.first
|
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? {
|
func key(forKid kid: Data) -> Data? {
|
||||||
if kid.count == 16 {
|
if kid.count == 16 {
|
||||||
let hex = kid.map { String(format: "%02x", $0) }.joined()
|
let hex = kid.map { String(format: "%02x", $0) }.joined()
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ struct SettingsScreen: View {
|
||||||
|
|
||||||
Section("Info") {
|
Section("Info") {
|
||||||
LabeledContent("App", value: "VMC iOS")
|
LabeledContent("App", value: "VMC iOS")
|
||||||
LabeledContent("Versie", value: "0.5.43")
|
LabeledContent("Versie", value: "0.5.44")
|
||||||
LabeledContent("Live TV", value: app.iptvEnabled ? "Aan" : "Uit")
|
LabeledContent("Live TV", value: app.iptvEnabled ? "Aan" : "Uit")
|
||||||
LabeledContent("Events", value: app.eventsEnabled ? "Aan" : "Uit")
|
LabeledContent("Events", value: app.eventsEnabled ? "Aan" : "Uit")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue