ios-sdk icon indicating copy to clipboard operation
ios-sdk copied to clipboard

SDK crashes eventually in `-[SPTAppRemoteMessageStreamWriter writeAvailableDataToStream:]`

Open Eskils opened this issue 1 year ago • 0 comments

Error thrown: NSConcreteMutableData replaceBytesInRange:withBytes:length:]: range {0, 73} exceeds data length 0

Workaround solution until a fix is available:

-> Attempts to replace replaceBytesInRange:withBytes:length: with setData:

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        swizzleWriteAvailableDataToStream()
        
        return true
    }
    
    // ...
    
    private func swizzleWriteAvailableDataToStream() {
        // The modified implementation is approximatley equal to the Spotify SDKs -[SPTAppRemoteMessageStreamWriter writeAvailableDataToStream:]
        // except instead of -[NSMutableData replaceBytesInRange:withBytes:length:], setData is called on the messageBuffer.
        let exchangedImplementation: @convention(c) (NSObject, Selector, OutputStream) -> Void = { messageStreamWriter, selector, stream in
            guard let messageBuffer = messageStreamWriter.value(forKey: "_messageBuffer") as? NSMutableData else {
                return
            }
            
            while stream.hasSpaceAvailable {
                if messageBuffer.isEmpty {
                    break
                }
                
                let buffer = messageBuffer.bytes.assumingMemoryBound(to: UInt8.self)
                let result = stream.write(buffer, maxLength: messageBuffer.length)
                
                if result > 0 {
                    messageBuffer.setData(Data())
                }
            }
        }
        
        // Make a reference to the class and selector for which to exchange the implementation.
        guard let messageStreamWriterClass = NSClassFromString("SPTAppRemoteMessageStreamWriter") else {
            return
        }
        let writeAvailableDataToStreamSelector = NSSelectorFromString("writeAvailableDataToStream:")
        let writeAvailableDataToStreamSelectorExchanged = NSSelectorFromString("writeAvailableDataToStreamExchanged:")
        
        // This string describes the types taken by the method. `@` is an instance (id), `:` is a selector. For a full list of types, see: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
        let types = "@:@"
        
        let exchangedFunctionPointer = unsafeBitCast(exchangedImplementation, to: UnsafeRawPointer.self)
        let exchangedFunctionImplementation = IMP(exchangedFunctionPointer)
        
        // Add the modified implementation under a different selector to the class.
        class_addMethod(messageStreamWriterClass, writeAvailableDataToStreamSelectorExchanged, exchangedFunctionImplementation, types)
        
        guard
            let originalMethodPointer = class_getInstanceMethod(messageStreamWriterClass, writeAvailableDataToStreamSelector),
            let targetMethodPointer = class_getInstanceMethod(messageStreamWriterClass, writeAvailableDataToStreamSelectorExchanged)
        else {
            return
        }
        
        // Exchange (Swizzle) the two methods. The selector in the SDK will point to the modified implementation,
        // while the other selector (for the modified method) will point to the SDK implementation.
        method_exchangeImplementations(originalMethodPointer, targetMethodPointer)
    }
}

Eskils avatar Aug 11 '23 22:08 Eskils