// // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // /// Represents a call event that occurred on this device that we want to /// communicate to our linked devices. @objc(OutgoingCallEvent) class OutgoingCallEvent: NSObject, NSSecureCoding { enum CallType: UInt { case audio case video case group case adHocCall } enum EventDirection: UInt { case incoming case outgoing } enum EventType: UInt { case accepted case notAccepted case deleted case observed } let timestamp: UInt64 let conversationId: Data let callId: UInt64 let callType: CallType let eventDirection: EventDirection let eventType: EventType init( timestamp: UInt64, conversationId: Data, callId: UInt64, callType: CallType, eventDirection: EventDirection, eventType: EventType, ) { self.timestamp = timestamp self.conversationId = conversationId self.callId = callId self.callType = callType self.eventDirection = eventDirection self.eventType = eventType } // MARK: NSSecureCoding private enum Keys { static let timestamp = "timestamp" static let conversationId = "peerUuid" static let callId = "callId" static let callType = "type" static let eventDirection = "direction" static let eventType = "event" } static var supportsSecureCoding: Bool { true } func encode(with coder: NSCoder) { coder.encode(NSNumber(value: timestamp), forKey: Keys.timestamp) coder.encode(conversationId as NSData, forKey: Keys.conversationId) coder.encode(NSNumber(value: callId), forKey: Keys.callId) coder.encode(NSNumber(value: callType.rawValue), forKey: Keys.callType) coder.encode(NSNumber(value: eventDirection.rawValue), forKey: Keys.eventDirection) coder.encode(NSNumber(value: eventType.rawValue), forKey: Keys.eventType) } required init?(coder: NSCoder) { guard let timestamp = coder.decodeObject(of: NSNumber.self, forKey: Keys.timestamp)?.uint64Value, let conversationId = coder.decodeObject(of: NSData.self, forKey: Keys.conversationId) as Data?, let callId = coder.decodeObject(of: NSNumber.self, forKey: Keys.callId)?.uint64Value, let callTypeRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.callType)?.uintValue, let callType = CallType(rawValue: callTypeRaw), let eventDirectionRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.eventDirection)?.uintValue, let eventDirection = EventDirection(rawValue: eventDirectionRaw), let eventTypeRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.eventType)?.uintValue, let eventType = EventType(rawValue: eventTypeRaw) else { owsFailDebug("Missing or unrecognized fields!") return nil } self.timestamp = timestamp self.conversationId = conversationId self.callId = callId self.callType = callType self.eventDirection = eventDirection self.eventType = eventType } } /// Represents a sync message containing a "call event". /// /// Indicates to linked devices that a call has changed state. For example, that /// we accepted a ringing call on this device. /// /// - SeeAlso ``IncomingCallEventSyncMessageManager`` @objc(OutgoingCallEventSyncMessage) public class OutgoingCallEventSyncMessage: OutgoingSyncMessage { override public class var supportsSecureCoding: Bool { true } public required init?(coder: NSCoder) { guard let callEvent = coder.decodeObject(of: OutgoingCallEvent.self, forKey: "event") else { return nil } self.callEvent = callEvent super.init(coder: coder) } override public func encode(with coder: NSCoder) { super.encode(with: coder) coder.encode(callEvent, forKey: "event") } override public var hash: Int { var hasher = Hasher() hasher.combine(super.hash) hasher.combine(callEvent) return hasher.finalize() } override public func isEqual(_ object: Any?) -> Bool { guard let object = object as? Self else { return false } guard super.isEqual(object) else { return false } guard self.callEvent == object.callEvent else { return false } return true } /// The call event. let callEvent: OutgoingCallEvent init( localThread: TSContactThread, event: OutgoingCallEvent, tx: DBReadTransaction, ) { self.callEvent = event super.init(localThread: localThread, tx: tx) } override public var isUrgent: Bool { false } override public func syncMessageBuilder(tx: DBReadTransaction) -> SSKProtoSyncMessageBuilder? { let callEventBuilder = SSKProtoSyncMessageCallEvent.builder() callEventBuilder.setCallID(callEvent.callId) callEventBuilder.setType(callEvent.callType.protoValue) callEventBuilder.setDirection(callEvent.eventDirection.protoValue) callEventBuilder.setEvent(callEvent.eventType.protoValue) callEventBuilder.setTimestamp(callEvent.timestamp) callEventBuilder.setConversationID(callEvent.conversationId) let builder = SSKProtoSyncMessage.builder() builder.setCallEvent(callEventBuilder.buildInfallibly()) return builder } } private extension OutgoingCallEvent.CallType { var protoValue: SSKProtoSyncMessageCallEventType { switch self { case .audio: return .audioCall case .video: return .videoCall case .group: return .groupCall case .adHocCall: return .adHocCall } } } private extension OutgoingCallEvent.EventDirection { var protoValue: SSKProtoSyncMessageCallEventDirection { switch self { case .incoming: return .incoming case .outgoing: return .outgoing } } } private extension OutgoingCallEvent.EventType { var protoValue: SSKProtoSyncMessageCallEventEvent { switch self { case .accepted: return .accepted case .notAccepted: return .notAccepted case .deleted: return .deleted case .observed: return .observed } } }