diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 1d119567a..92fbb98c5 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ D70A26F02B7D02E6006CC8FC /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26EF2B7D02E6006CC8FC /* ConversationViewModel.swift */; }; D70A26F22B7F5D95006CC8FC /* ConversationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26F12B7F5D95006CC8FC /* ConversationFragment.swift */; }; D70C93DE2AC2D0F60063CA3B /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */; }; + D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */; }; D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071D2AC5922E0037746F /* ColorExtension.swift */; }; D71707202AC5989C0037746F /* TextExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071F2AC5989C0037746F /* TextExtension.swift */; }; D7173EBE2B7A5C0A00BCC481 /* LinphoneUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */; }; @@ -191,6 +192,7 @@ D70A26EF2B7D02E6006CC8FC /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = ""; }; D70A26F12B7F5D95006CC8FC /* ConversationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFragment.swift; sourceTree = ""; }; D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMediaEncryptionModel.swift; sourceTree = ""; }; D717071D2AC5922E0037746F /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; D717071F2AC5989C0037746F /* TextExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextExtension.swift; sourceTree = ""; }; D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinphoneUtils.swift; sourceTree = ""; }; @@ -523,6 +525,7 @@ isa = PBXGroup; children = ( D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */, + D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */, ); path = Model; sourceTree = ""; @@ -1000,6 +1003,7 @@ 6613A0B42BAEBE3F008923A4 /* MeetingViewModel.swift in Sources */, D7173EBE2B7A5C0A00BCC481 /* LinphoneUtils.swift in Sources */, 66C492012B24DB6900CEA16D /* Log.swift in Sources */, + D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */, 6613A0B62BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift in Sources */, D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */, D7CEE0382B7A214F00FD79B7 /* ConversationsListViewModel.swift in Sources */, diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 2652bf8e4..4a74bed5c 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -192,6 +192,9 @@ }, "All modifications will be canceled." : { + }, + "Annuler" : { + }, "Appel" : { @@ -257,6 +260,9 @@ }, "Chiffrement de bout en bout de tous vos échanges, grâce au mode default vos communications sont à l’abri des regards." : { + }, + "Chiffrement du média" : { + }, "Clear logs" : { @@ -374,6 +380,9 @@ }, "Etes-vous sûr de vouloir supprimer %@ ?" : { + }, + "Faire la validation à nouveau" : { + }, "Favourites" : { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 90d1e08b0..a25d1dc49 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -41,6 +41,7 @@ struct CallView: View { @State var audioRouteSheet: Bool = false @State var changeLayoutSheet: Bool = false + @State var mediaEncryptedSheet: Bool = false @State var optionsAudioRoute: Int = 1 @State var optionsChangeLayout: Int = 2 @State var imageAudioRoute: String = "" @@ -64,6 +65,12 @@ struct CallView: View { ZStack { if #available(iOS 16.4, *), idiom != .pad { innerView(geometry: geo) + .sheet(isPresented: $mediaEncryptedSheet, onDismiss: { + mediaEncryptedSheet = false + }) { + mediaEncryptedSheetBottomSheet() + .presentationDetents([.medium]) + } .sheet(isPresented: $audioRouteSheet, onDismiss: { audioRouteSheet = false }) { @@ -87,6 +94,12 @@ struct CallView: View { } } else if #available(iOS 16.0, *), idiom != .pad { innerView(geometry: geo) + .sheet(isPresented: $mediaEncryptedSheet, onDismiss: { + mediaEncryptedSheet = false + }) { + mediaEncryptedSheetBottomSheet() + .presentationDetents([.medium]) + } .sheet(isPresented: $audioRouteSheet, onDismiss: { audioRouteSheet = false }) { @@ -109,6 +122,11 @@ struct CallView: View { } } else { innerView(geometry: geo) + .halfSheet(showSheet: $mediaEncryptedSheet) { + mediaEncryptedSheetBottomSheet() + } onDismiss: { + mediaEncryptedSheet = false + } .halfSheet(showSheet: $audioRouteSheet) { audioRouteBottomSheet() } onDismiss: { @@ -168,6 +186,83 @@ struct CallView: View { } } + @ViewBuilder + func mediaEncryptedSheetBottomSheet() -> some View { + VStack { + if idiom != .pad && (orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + Spacer() + HStack { + Spacer() + Button("Close") { + mediaEncryptedSheet = false + } + } + .padding(.trailing) + } else { + Capsule() + .fill(Color.grayMain2c300) + .frame(width: 75, height: 5) + .padding(15) + } + + Text("Chiffrement du média") + .default_text_style_white_600(styleSize: 15) + .padding(.top, 10) + + Spacer() + + Text(callViewModel.callMediaEncryptionModel.mediaEncryption) + .default_text_style_white(styleSize: 15) + + Spacer() + + Text(callViewModel.callMediaEncryptionModel.zrtpCipher) + .default_text_style_white(styleSize: 15) + + Spacer() + + Text(callViewModel.callMediaEncryptionModel.zrtpKeyAgreement) + .default_text_style_white(styleSize: 15) + + Spacer() + + Text(callViewModel.callMediaEncryptionModel.zrtpHash) + .default_text_style_white(styleSize: 15) + + Spacer() + + Text(callViewModel.callMediaEncryptionModel.zrtpAuthTag) + .default_text_style_white(styleSize: 15) + + Spacer() + + Text(callViewModel.callMediaEncryptionModel.zrtpAuthSas) + .default_text_style_white(styleSize: 15) + .padding(.bottom, 10) + + Spacer() + + Button(action: { + callViewModel.showZrtpSasDialogIfPossible() + mediaEncryptedSheet = false + }, label: { + Text("Faire la validation à nouveau") + .default_text_style_white_600(styleSize: 20) + .frame(height: 35) + .frame(maxWidth: .infinity) + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(Color.orangeMain500) + .cornerRadius(60) + .padding(.bottom) + .padding(.horizontal, 10) + } + .background(Color.gray600) + } + @ViewBuilder func audioRouteBottomSheet() -> some View { VStack(spacing: 0) { @@ -479,7 +574,7 @@ struct CallView: View { Spacer() } .onTapGesture { - callViewModel.showZrtpSasDialogIfPossible() + mediaEncryptedSheet = true } .frame(height: 40) .zIndex(1) diff --git a/Linphone/UI/Call/MeetingWaitingRoomFragment.swift b/Linphone/UI/Call/MeetingWaitingRoomFragment.swift index 003b56cd2..a6aec3d93 100644 --- a/Linphone/UI/Call/MeetingWaitingRoomFragment.swift +++ b/Linphone/UI/Call/MeetingWaitingRoomFragment.swift @@ -386,6 +386,21 @@ struct MeetingWaitingRoomFragment: View { .frame(width: 35, height: 35) Spacer() + + Button(action: { + meetingWaitingRoomViewModel.cancelMeeting() + }, label: { + Text("Annuler") + .default_text_style_white_600(styleSize: 20) + .frame(height: 35) + .frame(maxWidth: .infinity) + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(Color.orangeMain500) + .cornerRadius(60) + .padding(.bottom) + .padding(.horizontal, 10) } } diff --git a/Linphone/UI/Call/Model/CallMediaEncryptionModel.swift b/Linphone/UI/Call/Model/CallMediaEncryptionModel.swift new file mode 100644 index 000000000..a4eaf15c0 --- /dev/null +++ b/Linphone/UI/Call/Model/CallMediaEncryptionModel.swift @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Foundation +import linphonesw + +class CallMediaEncryptionModel: ObservableObject { + var coreContext = CoreContext.shared + + @Published var mediaEncryption = "" + + @Published var isMediaEncryptionZrtp = false + @Published var zrtpCipher = "" + @Published var zrtpKeyAgreement = "" + @Published var zrtpHash = "" + @Published var zrtpAuthTag = "" + @Published var zrtpAuthSas = "" + + func update(call: Call) { + coreContext.doOnCoreQueue { core in + var stats = call.getStats(type: StreamType.Audio) + if stats != nil { + // ZRTP stats are only available when authentication token isn't null ! + if call.currentParams!.mediaEncryption == .ZRTP && call.authenticationToken != nil { + let isMediaEncryptionZrtpTmp = true + + var mediaEncryptionTmp = "" + if stats!.isZrtpKeyAgreementAlgoPostQuantum { + mediaEncryptionTmp = "Media encryption: " + "Post Quantum ZRTP" + } else { + switch call.currentParams!.mediaEncryption { + case .None: + mediaEncryptionTmp = "Media encryption: " + "None" + case .SRTP: + mediaEncryptionTmp = "Media encryption: " + "SRTP" + case .ZRTP: + mediaEncryptionTmp = "Media encryption: " + "ZRTP" + case .DTLS: + mediaEncryptionTmp = "Media encryption: " + "DTLS" + default: + mediaEncryptionTmp = "Media encryption: " + "None" + } + } + + let zrtpCipherTmp = "Cipher algorithm: " + stats!.zrtpCipherAlgo + + let zrtpKeyAgreementTmp = "Key agreement algorithm: " + stats!.zrtpKeyAgreementAlgo + + let zrtpHashTmp = "Hash algorithm: " + stats!.zrtpHashAlgo + + let zrtpAuthTagTmp = "Authentication algorithm: " + stats!.zrtpAuthTagAlgo + + let zrtpAuthSasTmp = "SAS algorithm: " + stats!.zrtpSasAlgo + + DispatchQueue.main.async { + self.isMediaEncryptionZrtp = isMediaEncryptionZrtpTmp + + self.mediaEncryption = mediaEncryptionTmp + + self.zrtpCipher = zrtpCipherTmp + + self.zrtpKeyAgreement = zrtpKeyAgreementTmp + + self.zrtpHash = zrtpHashTmp + + self.zrtpAuthTag = zrtpAuthTagTmp + + self.zrtpAuthSas = zrtpAuthSasTmp + } + } else { + let mediaEncryptionTmp = "Media encryption: " + call.currentParams!.mediaEncryption.rawValue.description //call.currentParams.mediaEncryption + + DispatchQueue.main.async { + self.mediaEncryption = mediaEncryptionTmp + } + } + + } + } + } +} diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 16629dafb..a8c33de8d 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -53,6 +53,7 @@ class CallViewModel: ObservableObject { @Published var activeSpeakerParticipant: ParticipantModel? @Published var activeSpeakerName: String = "" @Published var myParticipantModel: ParticipantModel? + @Published var callMediaEncryptionModel = CallMediaEncryptionModel() private var mConferenceSuscriptions = Set() @@ -150,6 +151,10 @@ class CallViewModel: ObservableObject { let isDeviceTrusted = self.currentCall!.authenticationTokenVerified && authToken != nil let isRemoteDeviceTrustedTmp = self.telecomManager.callInProgress ? isDeviceTrusted : false + if self.currentCall != nil { + self.callMediaEncryptionModel.update(call: self.currentCall!) + } + DispatchQueue.main.async { self.direction = directionTmp self.remoteAddressString = remoteAddressStringTmp @@ -192,6 +197,9 @@ class CallViewModel: ObservableObject { self.callSuscriptions.insert(self.currentCall!.publisher?.onEncryptionChanged?.postOnMainQueue {(cbVal: (call: Call, on: Bool, authenticationToken: String?)) in _ = self.updateEncryption() + if self.currentCall != nil { + self.callMediaEncryptionModel.update(call: self.currentCall!) + } }) } } diff --git a/Linphone/UI/Call/ViewModel/MeetingWaitingRoomViewModel.swift b/Linphone/UI/Call/ViewModel/MeetingWaitingRoomViewModel.swift index 5c67af20f..6cba61b85 100644 --- a/Linphone/UI/Call/ViewModel/MeetingWaitingRoomViewModel.swift +++ b/Linphone/UI/Call/ViewModel/MeetingWaitingRoomViewModel.swift @@ -263,4 +263,12 @@ class MeetingWaitingRoomViewModel: ObservableObject { } } } + + func cancelMeeting() { + coreContext.doOnCoreQueue { core in + if core.currentCall != nil { + self.telecomManager.terminateCall(call: core.currentCall!) + } + } + } }