@@ -6,39 +6,62 @@ import PromiseKit
66import WebRTC
77
88/**
9- * ## Call Setup (Signaling) Flow
9+ * `CallService` manages the state of a WebRTC backed Signal Call (as opposed to the legacy "RedPhone Call").
1010 *
11- * ## Key
12- * - SS: Message sent via Signal Service
13- * - DC: Message sent via WebRTC Data Channel
11+ * It serves as connection from the `CallUIAdapater` to the `PeerConnectionClient`.
12+ *
13+ *
14+ * ## Signaling
15+ *
16+ * Signaling refers to the setup and tear down of the connection. Before the connection is established, this must happen
17+ * out of band (using Signal Service), but once the connection is established it's possible to publish updates
18+ * (like hangup) via the established channel.
19+ *
20+ * Following is a high level process of the exchange of messages that must take place for this to happen.
21+ *
22+ * ### Key
23+ *
24+ * --[SOMETHING]--> represents a message of type "Something" sent from the caller to the callee
25+ * <--[SOMETHING]-- represents a message of type "Something" sent from the callee to the caller
26+ * SS: Message sent via Signal Service
27+ * DC: Message sent via WebRTC Data Channel
28+ *
29+ * ### Message Exchange / State Flow Overview
1430 *
1531 * | Caller | Callee |
1632 * +----------------------------+-------------------------+
17- * handleOutgoingCall --[SS.CallOffer]-->
18- * and start storing ICE updates
33+ * Start outgoing call: `handleOutgoingCall`
34+ --[SS.CallOffer]-->
35+ * and start generating and storing ICE updates.
36+ * (As ICE candites are generated: `handleLocalAddedIceCandidate`)
1937 *
20- * Received call offer
38+ * Received call offer: `handleReceivedOffer`
2139 * Send call answer
2240 * <--[SS.CallAnswer]--
23- * Start sending ICE updates immediately
24- * <--[SS.ICEUpdates]--
41+ * Start generating ICE updates and send them as
42+ * they are generated: `handleLocalAddedIceCandidate`
43+ * <--[SS.ICEUpdate]-- (sent multiple times)
2544 *
26- * Received CallAnswer,
45+ * Received CallAnswer: `handleReceivedAnswer`
2746 * so send any stored ice updates
2847 * --[SS.ICEUpdates]-->
2948 *
3049 * Once compatible ICE updates have been exchanged...
31- * (ICE Connected)
50+ * both parties: `handleIceConnected`
3251 *
3352 * Show remote ringing UI
3453 * Connect to offered Data Channel
3554 * Show incoming call UI.
3655 *
37- * Answers Call
56+ * If callee answers Call
3857 * send connected message
3958 * <--[DC.ConnectedMesage]--
4059 * Received connected message
4160 * Show Call is connected.
61+ *
62+ * Hang up (this could equally be sent by the Callee)
63+ * --[DC.Hangup]-->
64+ * --[SS.Hangup]-->
4265 */
4366
4467enum CallError : Error {
@@ -74,11 +97,22 @@ fileprivate let timeoutSeconds = 60
7497 // MARK: Ivars
7598
7699 var peerConnectionClient : PeerConnectionClient ?
77- // TODO move thread into SignalCall? Or refactor messageSender to take SignalRecipient
100+ // TODO code cleanup: move thread into SignalCall? Or refactor messageSender to take SignalRecipient identifier.
78101 var thread : TSContactThread ?
79102 var call : SignalCall ?
103+
104+ /**
105+ * In the process of establishing a connection between the clients (ICE process) we must exchange ICE updates.
106+ * Because this happens via Signal Service it's possible the callee user has not accepted any change in the caller's
107+ * identity. In which case *each* ICE update would cause an "identity change" warning on the callee's device. Since
108+ * this could be several messages, the caller stores all ICE updates until receiving positive confirmation that the
109+ * callee has received a message from us. This positive confirmation comes in the form of the callees `CallAnswer`
110+ * message.
111+ */
80112 var sendIceUpdatesImmediately = true
81113 var pendingIceUpdateMessages = [ OWSCallIceUpdateMessage] ( )
114+
115+ // ensure the incoming call promise isn't dealloc'd prematurely
82116 var incomingCallPromise : Promise < Void > ?
83117
84118 // Used to coordinate promises across delegate methods
@@ -204,17 +238,9 @@ fileprivate let timeoutSeconds = 60
204238 }
205239 }
206240
207- private func handleLocalBusyCall( _ call: SignalCall , thread: TSContactThread ) {
208- Logger . debug ( " \( TAG) \( #function) for call: \( call) thread: \( thread) " )
209- assertOnSignalingQueue ( )
210-
211- let busyMessage = OWSCallBusyMessage ( callId: call. signalingId)
212- let callMessage = OWSOutgoingCallMessage ( thread: thread, busyMessage: busyMessage)
213- _ = sendMessage ( callMessage)
214-
215- handleMissedCall ( call, thread: thread)
216- }
217-
241+ /**
242+ * User didn't answer incoming call
243+ */
218244 public func handleMissedCall( _ call: SignalCall , thread: TSContactThread ) {
219245 // Insert missed call record
220246 let callRecord = TSCall ( timestamp: NSDate . ows_millisecondTimeStamp ( ) ,
@@ -226,6 +252,23 @@ fileprivate let timeoutSeconds = 60
226252 self . callUIAdapter. reportMissedCall ( call)
227253 }
228254
255+ /**
256+ * Received a call while already in another call.
257+ */
258+ private func handleLocalBusyCall( _ call: SignalCall , thread: TSContactThread ) {
259+ Logger . debug ( " \( TAG) \( #function) for call: \( call) thread: \( thread) " )
260+ assertOnSignalingQueue ( )
261+
262+ let busyMessage = OWSCallBusyMessage ( callId: call. signalingId)
263+ let callMessage = OWSOutgoingCallMessage ( thread: thread, busyMessage: busyMessage)
264+ _ = messageSender. sendCallMessage ( callMessage)
265+
266+ handleMissedCall ( call, thread: thread)
267+ }
268+
269+ /**
270+ * The callee was already in another call.
271+ */
229272 public func handleRemoteBusy( thread: TSContactThread ) {
230273 Logger . debug ( " \( TAG) \( #function) for thread: \( thread) " )
231274 assertOnSignalingQueue ( )
@@ -239,11 +282,6 @@ fileprivate let timeoutSeconds = 60
239282 terminateCall ( )
240283 }
241284
242- private func isBusy( ) -> Bool {
243- // TODO CallManager adapter?
244- return false
245- }
246-
247285 /**
248286 * Received an incoming call offer. We still have to complete setting up the Signaling channel before we notify
249287 * the user of an incoming call.
@@ -319,6 +357,9 @@ fileprivate let timeoutSeconds = 60
319357 }
320358 }
321359
360+ /**
361+ * Initiate a call to recipient by recipientId
362+ */
322363 public func handleCallBack( recipientId: String ) {
323364 // TODO #function is called from objc, how to access swift defiend dispatch queue (OS_dispatch_queue)
324365 //assertOnSignalingQueue()
@@ -336,6 +377,9 @@ fileprivate let timeoutSeconds = 60
336377 }
337378 }
338379
380+ /**
381+ * Remote client (could be caller or callee) sent us a connectivity update
382+ */
339383 public func handleRemoteAddedIceCandidate( thread: TSContactThread , callId: UInt64 , sdp: String , lineIndex: Int32 , mid: String ) {
340384 assertOnSignalingQueue ( )
341385 Logger . debug ( " \( TAG) called \( #function) " )
@@ -368,6 +412,10 @@ fileprivate let timeoutSeconds = 60
368412 peerConnectionClient. addIceCandidate ( RTCIceCandidate ( sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid) )
369413 }
370414
415+ /**
416+ * Local client (could be caller or callee) generated some connectivity information that we should send to the
417+ * remote client.
418+ */
371419 private func handleLocalAddedIceCandidate( _ iceCandidate: RTCIceCandidate ) {
372420 assertOnSignalingQueue ( )
373421
@@ -401,6 +449,12 @@ fileprivate let timeoutSeconds = 60
401449 }
402450 }
403451
452+ /**
453+ * The clients can now communicate via WebRTC.
454+ *
455+ * Called by both caller and callee. Compatible ICE messages have been exchanged between the local and remote
456+ * client.
457+ */
404458 private func handleIceConnected( ) {
405459 assertOnSignalingQueue ( )
406460
@@ -427,6 +481,7 @@ fileprivate let timeoutSeconds = 60
427481 case . answering:
428482 call. state = . localRinging
429483 self . callUIAdapter. reportIncomingCall ( call, thread: thread, audioManager: peerConnectionClient)
484+ // cancel connection timeout
430485 self . fulfillCallConnectedPromise ? ( )
431486 case . remoteRinging:
432487 Logger . info ( " \( TAG) call alreading ringing. Ignoring \( #function) " )
@@ -435,6 +490,9 @@ fileprivate let timeoutSeconds = 60
435490 }
436491 }
437492
493+ /**
494+ * The remote client (caller or callee) ended the call.
495+ */
438496 public func handleRemoteHangup( thread: TSContactThread ) {
439497 Logger . debug ( " \( TAG) in \( #function) " )
440498 assertOnSignalingQueue ( )
@@ -467,7 +525,9 @@ fileprivate let timeoutSeconds = 60
467525 }
468526
469527 /**
470- * Answer call by call `localId`, used by notification actions which can't serialize a call object.
528+ * User chose to answer call referrred to by call `localId`. Used by the Callee only.
529+ *
530+ * Used by notification actions which can't serialize a call object.
471531 */
472532 public func handleAnswerCall( localId: UUID ) {
473533 // TODO #function is called from objc, how to access swift defiend dispatch queue (OS_dispatch_queue)
@@ -490,6 +550,9 @@ fileprivate let timeoutSeconds = 60
490550 }
491551 }
492552
553+ /**
554+ * User chose to answer call referrred to by call `localId`. Used by the Callee only.
555+ */
493556 public func handleAnswerCall( _ call: SignalCall ) {
494557 assertOnSignalingQueue ( )
495558
@@ -533,8 +596,8 @@ fileprivate let timeoutSeconds = 60
533596 }
534597
535598 /**
536- * Called by initiator when recipient answers the call.
537- * Called by recipient upon answering the call.
599+ * For outgoing call, when the callee has chosen to accept the call.
600+ * For incoming call, when the local user has chosen to accept the call.
538601 */
539602 func handleConnectedCall( _ call: SignalCall ) {
540603 Logger . debug ( " \( TAG) in \( #function) " )
@@ -552,6 +615,13 @@ fileprivate let timeoutSeconds = 60
552615 peerConnectionClient. setVideoEnabled ( enabled: call. hasVideo)
553616 }
554617
618+ /**
619+ * Local user chose to decline the call vs. answering it.
620+ *
621+ * The call is referred to by call `localId`, which is included in Notification actions.
622+ *
623+ * Incoming call only.
624+ */
555625 public func handleDeclineCall( localId: UUID ) {
556626 // #function is called from objc, how to access swift defiend dispatch queue (OS_dispatch_queue)
557627 //assertOnSignalingQueue()
@@ -573,6 +643,11 @@ fileprivate let timeoutSeconds = 60
573643 }
574644 }
575645
646+ /**
647+ * Local user chose to decline the call vs. answering it.
648+ *
649+ * Incoming call only.
650+ */
576651 public func handleDeclineCall( _ call: SignalCall ) {
577652 assertOnSignalingQueue ( )
578653
@@ -582,6 +657,11 @@ fileprivate let timeoutSeconds = 60
582657 handleLocalHungupCall ( call)
583658 }
584659
660+ /**
661+ * Local user chose to end the call.
662+ *
663+ * Can be used for Incoming and Outgoing calls.
664+ */
585665 func handleLocalHungupCall( _ call: SignalCall ) {
586666 assertOnSignalingQueue ( )
587667
@@ -631,6 +711,11 @@ fileprivate let timeoutSeconds = 60
631711 terminateCall ( )
632712 }
633713
714+ /**
715+ * Local user toggled to mute audio.
716+ *
717+ * Can be used for Incoming and Outgoing calls.
718+ */
634719 func handleToggledMute( isMuted: Bool ) {
635720 assertOnSignalingQueue ( )
636721
@@ -641,6 +726,16 @@ fileprivate let timeoutSeconds = 60
641726 peerConnectionClient. setAudioEnabled ( enabled: !isMuted)
642727 }
643728
729+ /**
730+ * Local client received a message on the WebRTC data channel.
731+ *
732+ * The WebRTC data channel is a faster signaling channel than out of band Signal Service messages. Once it's
733+ * established we use it to communicate further signaling information. The one sort-of exception is that with
734+ * hangup messages we redundantly send a Signal Service hangup message, which is more reliable, and since the hangup
735+ * action is idemptotent, there's no harm done.
736+ *
737+ * Used by both Incoming and Outgoing calls.
738+ */
644739 private func handleDataChannelMessage( _ message: OWSWebRTCProtosData ) {
645740 assertOnSignalingQueue ( )
646741
@@ -691,6 +786,10 @@ fileprivate let timeoutSeconds = 60
691786
692787 // MARK: Helpers
693788
789+ /**
790+ * Ensure that all `SignalCall` and `CallService` state is synchronized by only mutating signaling state in
791+ * handleXXX methods, and putting those methods on the signaling queue.
792+ */
694793 private func assertOnSignalingQueue( ) {
695794 if #available( iOS 10 . 0 , * ) {
696795 dispatchPrecondition ( condition: . onQueue( type ( of: self ) . signalingQueue) )
@@ -699,16 +798,20 @@ fileprivate let timeoutSeconds = 60
699798 }
700799 }
701800
801+ /**
802+ *
803+ */
702804 private func getIceServers( ) -> Promise < [ RTCIceServer ] > {
703-
704805 return firstly {
705806 return accountManager. getTurnServerInfo ( )
706807 } . then ( on: CallService . signalingQueue) { turnServerInfo -> [ RTCIceServer ] in
707808 Logger . debug ( " \( self . TAG) got turn server urls: \( turnServerInfo. urls) " )
708809
709810 return turnServerInfo. urls. map { url in
710811 if url. hasPrefix ( " turn " ) {
711- // only pass credentials for "turn:" servers.
812+ // Only "turn:" servers require authentication. Don't include the credentials to other ICE servers
813+ // as 1.) they aren't used, and 2.) the non-turn servers might not be under our control.
814+ // e.g. we use a public fallback STUN server.
712815 return RTCIceServer ( urlStrings: [ url] , username: turnServerInfo. username, credential: turnServerInfo. password)
713816 } else {
714817 return RTCIceServer ( urlStrings: [ url] )
0 commit comments