Call lifecycle edge cases.

pull/1/head
Matthew Chen 7 years ago
parent 6e0d92e039
commit 3967a5ab05

@ -98,6 +98,97 @@ protocol CallServiceObserver: class {
remoteVideoTrack: RTCVideoTrack?) remoteVideoTrack: RTCVideoTrack?)
} }
// Gather all per-call state in one place.
private class SignalCallData: NSObject {
public let call: SignalCall
// Used to coordinate promises across delegate methods
let fulfillCallConnectedPromise: (() -> Void)
let rejectCallConnectedPromise: ((Error) -> Void)
let callConnectedPromise: Promise<Void>
// Used to ensure any received ICE messages wait until the peer connection client is set up.
let fulfillPeerConnectionClientPromise: (() -> Void)
let rejectPeerConnectionClientPromise: ((Error) -> Void)
let peerConnectionClientPromise: Promise<Void>
// Used to ensure CallOffer was sent before sending any ICE updates.
let fulfillReadyToSendIceUpdatesPromise: (() -> Void)
let rejectReadyToSendIceUpdatesPromise: ((Error) -> Void)
let readyToSendIceUpdatesPromise: Promise<Void>
weak var localVideoTrack: RTCVideoTrack? {
didSet {
SwiftAssertIsOnMainThread(#function)
Logger.info("\(self.logTag) \(#function)")
}
}
weak var remoteVideoTrack: RTCVideoTrack? {
didSet {
SwiftAssertIsOnMainThread(#function)
Logger.info("\(self.logTag) \(#function)")
}
}
var isRemoteVideoEnabled = false {
didSet {
SwiftAssertIsOnMainThread(#function)
Logger.info("\(self.logTag) \(#function): \(isRemoteVideoEnabled)")
}
}
required init(call: SignalCall) {
self.call = call
let (callConnectedPromise, fulfillCallConnectedPromise, rejectCallConnectedPromise) = Promise<Void>.pending()
self.callConnectedPromise = callConnectedPromise
self.fulfillCallConnectedPromise = fulfillCallConnectedPromise
self.rejectCallConnectedPromise = rejectCallConnectedPromise
let (peerConnectionClientPromise, fulfillPeerConnectionClientPromise, rejectPeerConnectionClientPromise) = Promise<Void>.pending()
self.peerConnectionClientPromise = peerConnectionClientPromise
self.fulfillPeerConnectionClientPromise = fulfillPeerConnectionClientPromise
self.rejectPeerConnectionClientPromise = rejectPeerConnectionClientPromise
let (readyToSendIceUpdatesPromise, fulfillReadyToSendIceUpdatesPromise, rejectReadyToSendIceUpdatesPromise) = Promise<Void>.pending()
self.readyToSendIceUpdatesPromise = readyToSendIceUpdatesPromise
self.fulfillReadyToSendIceUpdatesPromise = fulfillReadyToSendIceUpdatesPromise
self.rejectReadyToSendIceUpdatesPromise = rejectReadyToSendIceUpdatesPromise
super.init()
}
deinit {
Logger.debug("[SignalCallData] deinit")
}
// MARK: -
public func terminate() {
SwiftAssertIsOnMainThread(#function)
Logger.debug("\(self.logTag) in \(#function)")
self.call.removeAllObservers()
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
rejectCallConnectedPromise(CallError.obsoleteCall(description: "Terminating call"))
// In case we're still waiting on the peer connection setup somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
rejectPeerConnectionClientPromise(CallError.obsoleteCall(description: "Terminating call"))
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
rejectReadyToSendIceUpdatesPromise(CallError.obsoleteCall(description: "Terminating call"))
}
}
// This class' state should only be accessed on the main queue. // This class' state should only be accessed on the main queue.
@objc class CallService: NSObject, CallObserver, PeerConnectionClientDelegate { @objc class CallService: NSObject, CallObserver, PeerConnectionClientDelegate {
@ -130,77 +221,75 @@ protocol CallServiceObserver: class {
} }
} }
var call: SignalCall? { fileprivate var callData: SignalCallData? {
didSet { didSet {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
oldValue?.removeObserver(self) oldValue?.call.removeObserver(self)
call?.addObserverAndSyncState(observer: self) callData?.call.addObserverAndSyncState(observer: self)
updateIsVideoEnabled() updateIsVideoEnabled()
// Prevent device from sleeping while we have an active call. // Prevent device from sleeping while we have an active call.
if oldValue != call { if oldValue != callData {
if let oldValue = oldValue { if let oldValue = oldValue {
DeviceSleepManager.sharedInstance.removeBlock(blockObject: oldValue) DeviceSleepManager.sharedInstance.removeBlock(blockObject: oldValue)
} }
stopAnyCallTimer() stopAnyCallTimer()
if let call = call { if let callData = callData {
DeviceSleepManager.sharedInstance.addBlock(blockObject: call) DeviceSleepManager.sharedInstance.addBlock(blockObject: callData)
self.startCallTimer() self.startCallTimer()
} }
} }
Logger.debug("\(self.logTag) .call setter: \(oldValue?.identifiersForLogs as Optional) -> \(call?.identifiersForLogs as Optional)") Logger.debug("\(self.logTag) .callData setter: \(oldValue?.call.identifiersForLogs as Optional) -> \(callData?.call.identifiersForLogs as Optional)")
for observer in observers { for observer in observers {
observer.value?.didUpdateCall(call: call) observer.value?.didUpdateCall(call: callData?.call)
} }
} }
} }
// Used to coordinate promises across delegate methods // TODO: Remove?
private var fulfillCallConnectedPromise: (() -> Void)? var call: SignalCall? {
private var rejectCallConnectedPromise: ((Error) -> Void)? get {
SwiftAssertIsOnMainThread(#function)
// Used by waitForPeerConnectionClient to make sure any received
// ICE messages wait until the peer connection client is set up.
private var fulfillPeerConnectionClientPromise: (() -> Void)?
private var rejectPeerConnectionClientPromise: ((Error) -> Void)?
private var peerConnectionClientPromise: Promise<Void>?
// Used by waituntilReadyToSendIceUpdates to make sure CallOffer was guard let callData = callData else {
// sent before sending any ICE updates. return nil
private var fulfillReadyToSendIceUpdatesPromise: (() -> Void)? }
private var rejectReadyToSendIceUpdatesPromise: ((Error) -> Void)? return callData.call
private var readyToSendIceUpdatesPromise: Promise<Void>? }
}
weak var localVideoTrack: RTCVideoTrack? { var localVideoTrack: RTCVideoTrack? {
didSet { get {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
Logger.info("\(self.logTag) \(#function)") guard let callData = callData else {
return nil
fireDidUpdateVideoTracks() }
return callData.localVideoTrack
} }
} }
weak var remoteVideoTrack: RTCVideoTrack? { weak var remoteVideoTrack: RTCVideoTrack? {
didSet { get {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
Logger.info("\(self.logTag) \(#function)") guard let callData = callData else {
return nil
fireDidUpdateVideoTracks() }
return callData.remoteVideoTrack
} }
} }
var isRemoteVideoEnabled = false { var isRemoteVideoEnabled: Bool {
didSet { get {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
Logger.info("\(self.logTag) \(#function): \(isRemoteVideoEnabled)") guard let callData = callData else {
return false
fireDidUpdateVideoTracks() }
return callData.isRemoteVideoEnabled
} }
} }
@ -270,7 +359,8 @@ protocol CallServiceObserver: class {
return Promise(error: CallError.assertionError(description: errorDescription)) return Promise(error: CallError.assertionError(description: errorDescription))
} }
self.call = call let callData = SignalCallData(call: call)
self.callData = callData
let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeOutgoingIncomplete, in: call.thread) let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeOutgoingIncomplete, in: call.thread)
callRecord.save() callRecord.save()
@ -295,7 +385,7 @@ protocol CallServiceObserver: class {
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing, useTurnOnly: useTurnOnly) let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing, useTurnOnly: useTurnOnly)
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function) for call: \(call.identifiersForLogs)") Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function) for call: \(call.identifiersForLogs)")
self.peerConnectionClient = peerConnectionClient self.peerConnectionClient = peerConnectionClient
self.fulfillPeerConnectionClientPromise?() callData.fulfillPeerConnectionClientPromise()
return peerConnectionClient.createOffer() return peerConnectionClient.createOffer()
}.then { (sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in }.then { (sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in
@ -321,10 +411,6 @@ protocol CallServiceObserver: class {
// clients that don't support receiving ICE updates before receiving the call offer. // clients that don't support receiving ICE updates before receiving the call offer.
self.readyToSendIceUpdates(call: call) self.readyToSendIceUpdates(call: call)
let (callConnectedPromise, fulfill, reject) = Promise<Void>.pending()
self.fulfillCallConnectedPromise = fulfill
self.rejectCallConnectedPromise = reject
// Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway. // Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway.
let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in
// This code will always be called, whether or not the call has timed out. // This code will always be called, whether or not the call has timed out.
@ -333,7 +419,7 @@ protocol CallServiceObserver: class {
throw CallError.timeout(description: "timed out waiting to receive call answer") throw CallError.timeout(description: "timed out waiting to receive call answer")
} }
return race(timeout, callConnectedPromise) return race(timeout, callData.callConnectedPromise)
}.then { }.then {
Logger.info(self.call == call Logger.info(self.call == call
? "\(self.logTag) outgoing call connected: \(call.identifiersForLogs)." ? "\(self.logTag) outgoing call connected: \(call.identifiersForLogs)."
@ -360,22 +446,17 @@ protocol CallServiceObserver: class {
func readyToSendIceUpdates(call: SignalCall) { func readyToSendIceUpdates(call: SignalCall) {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
guard self.call == call else { guard let callData = self.callData else {
self.handleFailedCall(failedCall: call, error: .obsoleteCall(description:"obsolete call in \(#function)")) self.handleFailedCall(failedCall: call, error: .obsoleteCall(description:"obsolete call in \(#function)"))
return return
} }
if self.fulfillReadyToSendIceUpdatesPromise == nil { guard callData.call == call else {
createReadyToSendIceUpdatesPromise() self.handleFailedCall(failedCall: call, error: .obsoleteCall(description:"obsolete call in \(#function)"))
}
guard let fulfillReadyToSendIceUpdatesPromise = self.fulfillReadyToSendIceUpdatesPromise else {
OWSProdError(OWSAnalyticsEvents.callServiceMissingFulfillReadyToSendIceUpdatesPromise(), file: #file, function: #function, line: #line)
self.handleFailedCall(failedCall: call, error: CallError.assertionError(description: "failed to create fulfillReadyToSendIceUpdatesPromise"))
return return
} }
fulfillReadyToSendIceUpdatesPromise() callData.fulfillReadyToSendIceUpdatesPromise()
} }
/** /**
@ -559,7 +640,8 @@ protocol CallServiceObserver: class {
Logger.info("\(self.logTag) starting new call: \(newCall.identifiersForLogs)") Logger.info("\(self.logTag) starting new call: \(newCall.identifiersForLogs)")
self.call = newCall let callData = SignalCallData(call: newCall)
self.callData = callData
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: "\(#function)", completionBlock: { [weak self] status in var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: "\(#function)", completionBlock: { [weak self] status in
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
@ -599,7 +681,7 @@ protocol CallServiceObserver: class {
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function) for: \(newCall.identifiersForLogs)") Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function) for: \(newCall.identifiersForLogs)")
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming, useTurnOnly: useTurnOnly) let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming, useTurnOnly: useTurnOnly)
self.peerConnectionClient = peerConnectionClient self.peerConnectionClient = peerConnectionClient
self.fulfillPeerConnectionClientPromise?() callData.fulfillPeerConnectionClientPromise()
let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription) let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription)
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
@ -627,8 +709,6 @@ protocol CallServiceObserver: class {
// a more intuitive ordering. // a more intuitive ordering.
self.readyToSendIceUpdates(call: newCall) self.readyToSendIceUpdates(call: newCall)
let (promise, fulfill, reject) = Promise<Void>.pending()
let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in
// rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled // rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file: #file, function: #function, line: #line) OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file: #file, function: #function, line: #line)
@ -636,10 +716,7 @@ protocol CallServiceObserver: class {
} }
// This will be fulfilled (potentially) by the RTCDataChannel delegate method // This will be fulfilled (potentially) by the RTCDataChannel delegate method
self.fulfillCallConnectedPromise = fulfill return race(callData.callConnectedPromise, timeout)
self.rejectCallConnectedPromise = reject
return race(promise, timeout)
}.then { }.then {
Logger.info(self.call == newCall Logger.info(self.call == newCall
? "\(self.logTag) incoming call connected: \(newCall.identifiersForLogs)." ? "\(self.logTag) incoming call connected: \(newCall.identifiersForLogs)."
@ -660,6 +737,7 @@ protocol CallServiceObserver: class {
}.always { }.always {
Logger.debug("\(self.logTag) ending background task awaiting inbound call connection") Logger.debug("\(self.logTag) ending background task awaiting inbound call connection")
assert(backgroundTask != nil)
backgroundTask = nil backgroundTask = nil
} }
incomingCallPromise.retainUntilComplete() incomingCallPromise.retainUntilComplete()
@ -669,7 +747,15 @@ protocol CallServiceObserver: class {
* Remote client (could be caller or callee) sent us a connectivity update * Remote client (could be caller or callee) sent us a connectivity update
*/ */
public func handleRemoteAddedIceCandidate(thread: TSContactThread, callId: UInt64, sdp: String, lineIndex: Int32, mid: String) { public func handleRemoteAddedIceCandidate(thread: TSContactThread, callId: UInt64, sdp: String, lineIndex: Int32, mid: String) {
waitForPeerConnectionClient().then { () -> Void in Logger.verbose("\(logTag) \(#function) callId: \(callId)")
guard let callData = self.callData else {
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
self.handleFailedCurrentCall(error: .obsoleteCall(description: "ignoring remote ice update, since there is no current call."))
return
}
callData.peerConnectionClientPromise.then { () -> Void in
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
guard let call = self.call else { guard let call = self.call else {
@ -692,10 +778,11 @@ protocol CallServiceObserver: class {
return return
} }
Logger.verbose("\(self.logTag) \(#function) addRemoteIceCandidate")
peerConnectionClient.addRemoteIceCandidate(RTCIceCandidate(sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid)) peerConnectionClient.addRemoteIceCandidate(RTCIceCandidate(sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid))
}.catch { error in }.catch { error in
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleRemoteAddedIceCandidate(), file: #file, function: #function, line: #line) OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleRemoteAddedIceCandidate(), file: #file, function: #function, line: #line)
Logger.error("\(self.logTag) in \(#function) waitForPeerConnectionClient failed with error: \(error)") Logger.error("\(self.logTag) in \(#function) peerConnectionClientPromise failed with error: \(error)")
}.retainUntilComplete() }.retainUntilComplete()
} }
@ -706,15 +793,16 @@ protocol CallServiceObserver: class {
private func handleLocalAddedIceCandidate(_ iceCandidate: RTCIceCandidate) { private func handleLocalAddedIceCandidate(_ iceCandidate: RTCIceCandidate) {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
guard let call = self.call else { guard let callData = self.callData else {
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
self.handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring local ice candidate, since there is no current call.")) self.handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring local ice candidate, since there is no current call."))
return return
} }
let call = callData.call
// Wait until we've sent the CallOffer before sending any ice updates for the call to ensure // Wait until we've sent the CallOffer before sending any ice updates for the call to ensure
// intuitive message ordering for other clients. // intuitive message ordering for other clients.
waitUntilReadyToSendIceUpdates().then { () -> Void in callData.readyToSendIceUpdatesPromise.then { () -> Void in
guard call == self.call else { guard call == self.call else {
self.handleFailedCurrentCall(error: .obsoleteCall(description: "current call changed since we became ready to send ice updates")) self.handleFailedCurrentCall(error: .obsoleteCall(description: "current call changed since we became ready to send ice updates"))
return return
@ -731,6 +819,7 @@ protocol CallServiceObserver: class {
let iceUpdateMessage = OWSCallIceUpdateMessage(callId: call.signalingId, sdp: iceCandidate.sdp, sdpMLineIndex: iceCandidate.sdpMLineIndex, sdpMid: iceCandidate.sdpMid) let iceUpdateMessage = OWSCallIceUpdateMessage(callId: call.signalingId, sdp: iceCandidate.sdp, sdpMLineIndex: iceCandidate.sdpMLineIndex, sdpMid: iceCandidate.sdpMid)
Logger.info("\(self.logTag) in \(#function) sending ICE Candidate.") Logger.info("\(self.logTag) in \(#function) sending ICE Candidate.")
Logger.info("\(self.logTag) in \(#function) sending ICE Candidate \(call.identifiersForLogs).")
let callMessage = OWSOutgoingCallMessage(thread: call.thread, iceUpdateMessage: iceUpdateMessage) let callMessage = OWSOutgoingCallMessage(thread: call.thread, iceUpdateMessage: iceUpdateMessage)
let sendPromise = self.messageSender.sendPromise(message: callMessage) let sendPromise = self.messageSender.sendPromise(message: callMessage)
sendPromise.retainUntilComplete() sendPromise.retainUntilComplete()
@ -915,6 +1004,12 @@ protocol CallServiceObserver: class {
Logger.info("\(self.logTag) in \(#function)") Logger.info("\(self.logTag) in \(#function)")
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
guard let callData = self.callData else {
OWSProdError(OWSAnalyticsEvents.callServiceCallDataMissing(), file: #file, function: #function, line: #line)
handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(self.logTag) callData unexpectedly nil in \(#function)"))
return
}
guard let peerConnectionClient = self.peerConnectionClient else { guard let peerConnectionClient = self.peerConnectionClient else {
OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line) OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line)
handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(self.logTag) peerConnectionClient unexpectedly nil in \(#function)")) handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(self.logTag) peerConnectionClient unexpectedly nil in \(#function)"))
@ -923,9 +1018,8 @@ protocol CallServiceObserver: class {
Logger.info("\(self.logTag) handleConnectedCall: \(call.identifiersForLogs).") Logger.info("\(self.logTag) handleConnectedCall: \(call.identifiersForLogs).")
assert(self.fulfillCallConnectedPromise != nil)
// cancel connection timeout // cancel connection timeout
self.fulfillCallConnectedPromise?() callData.fulfillCallConnectedPromise()
call.state = .connected call.state = .connected
@ -1195,13 +1289,14 @@ protocol CallServiceObserver: class {
private func handleDataChannelMessage(_ message: OWSWebRTCProtosData) { private func handleDataChannelMessage(_ message: OWSWebRTCProtosData) {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
guard let call = self.call else { guard let callData = self.callData else {
// This should never happen; return to a known good state. // This should never happen; return to a known good state.
owsFail("\(self.logTag) received data message, but there is no current call. Ignoring.") owsFail("\(self.logTag) received data message, but there is no current call. Ignoring.")
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
handleFailedCurrentCall(error: CallError.assertionError(description: "\(self.logTag) received data message, but there is no current call. Ignoring.")) handleFailedCurrentCall(error: CallError.assertionError(description: "\(self.logTag) received data message, but there is no current call. Ignoring."))
return return
} }
let call = callData.call
if message.hasConnected() { if message.hasConnected() {
Logger.debug("\(self.logTag) remote participant sent Connected via data channel: \(call.identifiersForLogs).") Logger.debug("\(self.logTag) remote participant sent Connected via data channel: \(call.identifiersForLogs).")
@ -1236,7 +1331,8 @@ protocol CallServiceObserver: class {
} else if message.hasVideoStreamingStatus() { } else if message.hasVideoStreamingStatus() {
Logger.debug("\(self.logTag) remote participant sent VideoStreamingStatus via data channel: \(call.identifiersForLogs).") Logger.debug("\(self.logTag) remote participant sent VideoStreamingStatus via data channel: \(call.identifiersForLogs).")
self.isRemoteVideoEnabled = message.videoStreamingStatus.enabled() callData.isRemoteVideoEnabled = message.videoStreamingStatus.enabled()
self.fireDidUpdateVideoTracks()
} else { } else {
Logger.info("\(self.logTag) received unknown or empty DataChannelMessage: \(call.identifiersForLogs).") Logger.info("\(self.logTag) received unknown or empty DataChannelMessage: \(call.identifiersForLogs).")
} }
@ -1321,105 +1417,32 @@ protocol CallServiceObserver: class {
Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient") Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
return return
} }
guard let callData = callData else {
self.localVideoTrack = videoTrack
}
internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, didUpdateRemote videoTrack: RTCVideoTrack?) {
SwiftAssertIsOnMainThread(#function)
guard peerConnectionClient == self.peerConnectionClient else {
Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient") Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
return return
} }
self.remoteVideoTrack = videoTrack callData.localVideoTrack = videoTrack
fireDidUpdateVideoTracks()
} }
// MARK: Helpers internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, didUpdateRemote videoTrack: RTCVideoTrack?) {
private func waitUntilReadyToSendIceUpdates() -> Promise<Void> {
SwiftAssertIsOnMainThread(#function)
if self.readyToSendIceUpdatesPromise == nil {
createReadyToSendIceUpdatesPromise()
}
guard let readyToSendIceUpdatesPromise = self.readyToSendIceUpdatesPromise else {
OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreateReadyToSendIceUpdatesPromise(), file: #file, function: #function, line: #line)
return Promise(error: CallError.assertionError(description: "failed to create readyToSendIceUpdatesPromise"))
}
return readyToSendIceUpdatesPromise
}
private func createReadyToSendIceUpdatesPromise() {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
guard self.readyToSendIceUpdatesPromise == nil else { guard peerConnectionClient == self.peerConnectionClient else {
owsFail("expected readyToSendIceUpdatesPromise to be nil") Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
return
}
guard self.fulfillReadyToSendIceUpdatesPromise == nil else {
owsFail("expected fulfillReadyToSendIceUpdatesPromise to be nil")
return return
} }
guard let callData = callData else {
guard self.rejectReadyToSendIceUpdatesPromise == nil else { Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
owsFail("expected rejectReadyToSendIceUpdatesPromise to be nil")
return return
} }
let (promise, fulfill, reject) = Promise<Void>.pending() callData.remoteVideoTrack = videoTrack
self.fulfillReadyToSendIceUpdatesPromise = fulfill fireDidUpdateVideoTracks()
self.rejectReadyToSendIceUpdatesPromise = reject
self.readyToSendIceUpdatesPromise = promise
}
private func waitForPeerConnectionClient() -> Promise<Void> {
SwiftAssertIsOnMainThread(#function)
guard self.peerConnectionClient == nil else {
// peerConnectionClient already set
return Promise(value: ())
}
if self.peerConnectionClientPromise == nil {
createPeerConnectionClientPromise()
}
guard let peerConnectionClientPromise = self.peerConnectionClientPromise else {
OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreatePeerConnectionClientPromise(), file: #file, function: #function, line: #line)
return Promise(error: CallError.assertionError(description: "failed to create peerConnectionClientPromise"))
}
return peerConnectionClientPromise
} }
private func createPeerConnectionClientPromise() { // MARK: -
SwiftAssertIsOnMainThread(#function)
guard self.peerConnectionClientPromise == nil else {
owsFail("expected peerConnectionClientPromise to be nil")
return
}
guard self.fulfillPeerConnectionClientPromise == nil else {
owsFail("expected fulfillPeerConnectionClientPromise to be nil")
return
}
guard self.rejectPeerConnectionClientPromise == nil else {
owsFail("expected rejectPeerConnectionClientPromise to be nil")
return
}
let (promise, fulfill, reject) = Promise<Void>.pending()
self.fulfillPeerConnectionClientPromise = fulfill
self.rejectPeerConnectionClientPromise = reject
self.peerConnectionClientPromise = promise
}
/** /**
* RTCIceServers are used when attempting to establish an optimal connection to the other party. SignalService supplies * RTCIceServers are used when attempting to establish an optimal connection to the other party. SignalService supplies
@ -1511,44 +1534,17 @@ protocol CallServiceObserver: class {
Logger.debug("\(self.logTag) in \(#function)") Logger.debug("\(self.logTag) in \(#function)")
self.localVideoTrack = nil let currentCallData = self.callData
self.remoteVideoTrack = nil self.callData = nil
self.isRemoteVideoEnabled = false
self.peerConnectionClient?.terminate() self.peerConnectionClient?.terminate()
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function)") Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function)")
self.peerConnectionClient = nil self.peerConnectionClient = nil
self.call?.removeAllObservers() currentCallData?.terminate()
self.callUIAdapter.didTerminateCall(self.call)
self.call = nil
self.fulfillCallConnectedPromise = nil
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak. self.callUIAdapter.didTerminateCall(self.call)
// There is no harm in rejecting a previously fulfilled promise. fireDidUpdateVideoTracks()
if let rejectCallConnectedPromise = self.rejectCallConnectedPromise {
rejectCallConnectedPromise(CallError.obsoleteCall(description: "Terminating call"))
}
self.rejectCallConnectedPromise = nil
// In case we're still waiting on the peer connection setup somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
if let rejectPeerConnectionClientPromise = self.rejectPeerConnectionClientPromise {
rejectPeerConnectionClientPromise(CallError.obsoleteCall(description: "Terminating call"))
}
self.rejectPeerConnectionClientPromise = nil
self.fulfillPeerConnectionClientPromise = nil
self.peerConnectionClientPromise = nil
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
if let rejectReadyToSendIceUpdatesPromise = self.rejectReadyToSendIceUpdatesPromise {
rejectReadyToSendIceUpdatesPromise(CallError.obsoleteCall(description: "Terminating call"))
}
self.fulfillReadyToSendIceUpdatesPromise = nil
self.rejectReadyToSendIceUpdatesPromise = nil
self.readyToSendIceUpdatesPromise = nil
} }
// MARK: - CallObserver // MARK: - CallObserver

@ -166,6 +166,10 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
} }
} }
deinit {
Logger.debug("[PeerConnectionClient] deinit")
}
// MARK: - Media Streams // MARK: - Media Streams
private func createSignalingDataChannel() { private func createSignalingDataChannel() {

@ -57,6 +57,9 @@ class WebRTCCallMessageHandler: NSObject, OWSCallMessageHandler {
public func receivedIceUpdate(_ iceUpdate: OWSSignalServiceProtosCallMessageIceUpdate, from callerId: String) { public func receivedIceUpdate(_ iceUpdate: OWSSignalServiceProtosCallMessageIceUpdate, from callerId: String) {
SwiftAssertIsOnMainThread(#function) SwiftAssertIsOnMainThread(#function)
Logger.verbose("\(logTag) \(#function)")
guard iceUpdate.hasId() else { guard iceUpdate.hasId() else {
owsFail("no callId in \(#function)") owsFail("no callId in \(#function)")
return return

@ -375,6 +375,8 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
{ {
AssertOnDispatchQueue(self.serialQueue); AssertOnDispatchQueue(self.serialQueue);
DDLogVerbose(@"%@ %s: %zd", self.logTag, __PRETTY_FUNCTION__, jobs.count);
NSMutableArray<OWSMessageContentJob *> *processedJobs = [NSMutableArray new]; NSMutableArray<OWSMessageContentJob *> *processedJobs = [NSMutableArray new];
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (OWSMessageContentJob *job in jobs) { for (OWSMessageContentJob *job in jobs) {
@ -401,6 +403,7 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
} }
} }
}]; }];
DDLogVerbose(@"%@ %s complete: %zd", self.logTag, __PRETTY_FUNCTION__, processedJobs.count);
return processedJobs; return processedJobs;
} }
@ -488,6 +491,8 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
OWSAssert(envelopeData); OWSAssert(envelopeData);
OWSAssert(transaction); OWSAssert(transaction);
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
// We need to persist the decrypted envelope data ASAP to prevent data loss. // We need to persist the decrypted envelope data ASAP to prevent data loss.
[self.processingQueue enqueueEnvelopeData:envelopeData plaintextData:plaintextData transaction:transaction]; [self.processingQueue enqueueEnvelopeData:envelopeData plaintextData:plaintextData transaction:transaction];

@ -296,19 +296,23 @@ NS_ASSUME_NONNULL_BEGIN
if (envelope.hasContent) { if (envelope.hasContent) {
OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData]; OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData];
DDLogInfo(@"%@ handling content: <Content: %@>", self.logTag, [self descriptionForContent:content]); DDLogInfo(@"%@ content/sync", self.logTag);
if (content.hasSyncMessage) { if (content.hasSyncMessage) {
DDLogInfo(@"%@ handling content: <Content: %@>", self.logTag, [self descriptionForContent:content]);
[self handleIncomingEnvelope:envelope withSyncMessage:content.syncMessage transaction:transaction]; [self handleIncomingEnvelope:envelope withSyncMessage:content.syncMessage transaction:transaction];
[[OWSDeviceManager sharedManager] setHasReceivedSyncMessage]; [[OWSDeviceManager sharedManager] setHasReceivedSyncMessage];
} else if (content.hasDataMessage) { } else if (content.hasDataMessage) {
DDLogInfo(@"%@ content/data", self.logTag);
[self handleIncomingEnvelope:envelope withDataMessage:content.dataMessage transaction:transaction]; [self handleIncomingEnvelope:envelope withDataMessage:content.dataMessage transaction:transaction];
} else if (content.hasCallMessage) { } else if (content.hasCallMessage) {
DDLogInfo(@"%@ content/call", self.logTag);
[self handleIncomingEnvelope:envelope withCallMessage:content.callMessage]; [self handleIncomingEnvelope:envelope withCallMessage:content.callMessage];
} else if (content.hasNullMessage) { } else if (content.hasNullMessage) {
DDLogInfo(@"%@ content/null", self.logTag);
DDLogInfo(@"%@ Received null message.", self.logTag); DDLogInfo(@"%@ Received null message.", self.logTag);
} else if (content.hasReceiptMessage) { } else if (content.hasReceiptMessage) {
DDLogInfo(@"%@ content/receipt", self.logTag);
[self handleIncomingEnvelope:envelope withReceiptMessage:content.receiptMessage transaction:transaction]; [self handleIncomingEnvelope:envelope withReceiptMessage:content.receiptMessage transaction:transaction];
} else { } else {
DDLogWarn(@"%@ Ignoring envelope. Content with no known payload", self.logTag); DDLogWarn(@"%@ Ignoring envelope. Content with no known payload", self.logTag);
@ -469,6 +473,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(envelope); OWSAssert(envelope);
OWSAssert(callMessage); OWSAssert(callMessage);
DDLogVerbose(@"%@ handleIncomingEnvelope:withCallMessage:", self.logTag);
if ([callMessage hasProfileKey]) { if ([callMessage hasProfileKey]) {
NSData *profileKey = [callMessage profileKey]; NSData *profileKey = [callMessage profileKey];
NSString *recipientId = envelope.source; NSString *recipientId = envelope.source;
@ -480,19 +486,26 @@ NS_ASSUME_NONNULL_BEGIN
// definition will end if the app exits. // definition will end if the app exits.
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (callMessage.hasOffer) { if (callMessage.hasOffer) {
DDLogVerbose(@"%@ handleIncomingEnvelope:withCallMessage -> offer:", self.logTag);
[self.callMessageHandler receivedOffer:callMessage.offer fromCallerId:envelope.source]; [self.callMessageHandler receivedOffer:callMessage.offer fromCallerId:envelope.source];
} else if (callMessage.hasAnswer) { } else if (callMessage.hasAnswer) {
DDLogVerbose(@"%@ handleIncomingEnvelope:withCallMessage -> answer:", self.logTag);
[self.callMessageHandler receivedAnswer:callMessage.answer fromCallerId:envelope.source]; [self.callMessageHandler receivedAnswer:callMessage.answer fromCallerId:envelope.source];
} else if (callMessage.iceUpdate.count > 0) { } else if (callMessage.iceUpdate.count > 0) {
DDLogVerbose(
@"%@ handleIncomingEnvelope:withCallMessage -> ice: %zd", self.logTag, callMessage.iceUpdate.count);
for (OWSSignalServiceProtosCallMessageIceUpdate *iceUpdate in callMessage.iceUpdate) { for (OWSSignalServiceProtosCallMessageIceUpdate *iceUpdate in callMessage.iceUpdate) {
[self.callMessageHandler receivedIceUpdate:iceUpdate fromCallerId:envelope.source]; [self.callMessageHandler receivedIceUpdate:iceUpdate fromCallerId:envelope.source];
} }
} else if (callMessage.hasHangup) { } else if (callMessage.hasHangup) {
DDLogVerbose(@"%@ handleIncomingEnvelope:withCallMessage -> hangup:", self.logTag);
DDLogVerbose(@"%@ Received CallMessage with Hangup.", self.logTag); DDLogVerbose(@"%@ Received CallMessage with Hangup.", self.logTag);
[self.callMessageHandler receivedHangup:callMessage.hangup fromCallerId:envelope.source]; [self.callMessageHandler receivedHangup:callMessage.hangup fromCallerId:envelope.source];
} else if (callMessage.hasBusy) { } else if (callMessage.hasBusy) {
DDLogVerbose(@"%@ handleIncomingEnvelope:withCallMessage -> busy:", self.logTag);
[self.callMessageHandler receivedBusy:callMessage.busy fromCallerId:envelope.source]; [self.callMessageHandler receivedBusy:callMessage.busy fromCallerId:envelope.source];
} else { } else {
DDLogVerbose(@"%@ handleIncomingEnvelope:withCallMessage -> unknown:", self.logTag);
OWSProdInfoWEnvelope([OWSAnalyticsEvents messageManagerErrorCallMessageNoActionablePayload], envelope); OWSProdInfoWEnvelope([OWSAnalyticsEvents messageManagerErrorCallMessageNoActionablePayload], envelope);
} }
}); });

@ -443,6 +443,8 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
- (void)handleReceivedEnvelope:(OWSSignalServiceProtosEnvelope *)envelope - (void)handleReceivedEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
{ {
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
// Drop any too-large messages on the floor. Well behaving clients should never send them. // Drop any too-large messages on the floor. Well behaving clients should never send them.
NSUInteger kMaxEnvelopeByteCount = 250 * 1024; NSUInteger kMaxEnvelopeByteCount = 250 * 1024;
if (envelope.serializedSize > kMaxEnvelopeByteCount) { if (envelope.serializedSize > kMaxEnvelopeByteCount) {

@ -76,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)callServicePeerConnectionMissing; + (NSString *)callServicePeerConnectionMissing;
+ (NSString *)callServiceCallDataMissing;
+ (NSString *)contactsErrorContactsIntersectionFailed; + (NSString *)contactsErrorContactsIntersectionFailed;
+ (NSString *)errorAttachmentRequestFailed; + (NSString *)errorAttachmentRequestFailed;

@ -157,6 +157,11 @@ NS_ASSUME_NONNULL_BEGIN
return @"call_service_peer_connection_missing"; return @"call_service_peer_connection_missing";
} }
+ (NSString *)callServiceCallDataMissing
{
return @"call_service_call_data_missing";
}
+ (NSString *)contactsErrorContactsIntersectionFailed + (NSString *)contactsErrorContactsIntersectionFailed
{ {
return @"contacts_error_contacts_intersection_failed"; return @"contacts_error_contacts_intersection_failed";

Loading…
Cancel
Save