mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			367 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			367 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import Foundation
 | |
| import GRDB
 | |
| import SessionUtilitiesKit
 | |
| 
 | |
| public final class ConfigurationMessage: ControlMessage {
 | |
|     private enum CodingKeys: String, CodingKey {
 | |
|         case closedGroups
 | |
|         case openGroups
 | |
|         case displayName
 | |
|         case profilePictureUrl
 | |
|         case profileKey
 | |
|         case contacts
 | |
|     }
 | |
|     
 | |
|     public var closedGroups: Set<CMClosedGroup> = []
 | |
|     public var openGroups: Set<String> = []
 | |
|     public var displayName: String?
 | |
|     public var profilePictureUrl: String?
 | |
|     public var profileKey: Data?
 | |
|     public var contacts: Set<CMContact> = []
 | |
| 
 | |
|     public override var isSelfSendValid: Bool { true }
 | |
|     
 | |
|     // MARK: - Initialization
 | |
| 
 | |
|     public init(
 | |
|         displayName: String?,
 | |
|         profilePictureUrl: String?,
 | |
|         profileKey: Data?,
 | |
|         closedGroups: Set<CMClosedGroup>,
 | |
|         openGroups: Set<String>,
 | |
|         contacts: Set<CMContact>
 | |
|     ) {
 | |
|         super.init()
 | |
|         
 | |
|         self.displayName = displayName
 | |
|         self.profilePictureUrl = profilePictureUrl
 | |
|         self.profileKey = profileKey
 | |
|         self.closedGroups = closedGroups
 | |
|         self.openGroups = openGroups
 | |
|         self.contacts = contacts
 | |
|     }
 | |
|     
 | |
|     // MARK: - Codable
 | |
|     
 | |
|     required init(from decoder: Decoder) throws {
 | |
|         try super.init(from: decoder)
 | |
|         
 | |
|         let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
 | |
|         
 | |
|         closedGroups = ((try? container.decode(Set<CMClosedGroup>.self, forKey: .closedGroups)) ?? [])
 | |
|         openGroups = ((try? container.decode(Set<String>.self, forKey: .openGroups)) ?? [])
 | |
|         displayName = try? container.decode(String.self, forKey: .displayName)
 | |
|         profilePictureUrl = try? container.decode(String.self, forKey: .profilePictureUrl)
 | |
|         profileKey = try? container.decode(Data.self, forKey: .profileKey)
 | |
|         contacts = ((try? container.decode(Set<CMContact>.self, forKey: .contacts)) ?? [])
 | |
|     }
 | |
|     
 | |
|     public override func encode(to encoder: Encoder) throws {
 | |
|         try super.encode(to: encoder)
 | |
|         
 | |
|         var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
 | |
|         
 | |
|         try container.encodeIfPresent(closedGroups, forKey: .closedGroups)
 | |
|         try container.encodeIfPresent(openGroups, forKey: .openGroups)
 | |
|         try container.encodeIfPresent(displayName, forKey: .displayName)
 | |
|         try container.encodeIfPresent(profilePictureUrl, forKey: .profilePictureUrl)
 | |
|         try container.encodeIfPresent(profileKey, forKey: .profileKey)
 | |
|         try container.encodeIfPresent(contacts, forKey: .contacts)
 | |
|     }
 | |
| 
 | |
|     // MARK: - Proto Conversion
 | |
|     
 | |
|     public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ConfigurationMessage? {
 | |
|         guard let configurationProto = proto.configurationMessage else { return nil }
 | |
|         let displayName = configurationProto.displayName
 | |
|         let profilePictureUrl = configurationProto.profilePicture
 | |
|         let profileKey = configurationProto.profileKey
 | |
|         let closedGroups = Set(configurationProto.closedGroups.compactMap { CMClosedGroup.fromProto($0) })
 | |
|         let openGroups = Set(configurationProto.openGroups)
 | |
|         let contacts = Set(configurationProto.contacts.compactMap { CMContact.fromProto($0) })
 | |
|         
 | |
|         return ConfigurationMessage(
 | |
|             displayName: displayName,
 | |
|             profilePictureUrl: profilePictureUrl,
 | |
|             profileKey: profileKey,
 | |
|             closedGroups: closedGroups,
 | |
|             openGroups: openGroups,
 | |
|             contacts: contacts
 | |
|         )
 | |
|     }
 | |
| 
 | |
|     public override func toProto(_ db: Database) -> SNProtoContent? {
 | |
|         let configurationProto = SNProtoConfigurationMessage.builder()
 | |
|         if let displayName = displayName { configurationProto.setDisplayName(displayName) }
 | |
|         if let profilePictureUrl = profilePictureUrl { configurationProto.setProfilePicture(profilePictureUrl) }
 | |
|         if let profileKey = profileKey { configurationProto.setProfileKey(profileKey) }
 | |
|         configurationProto.setClosedGroups(closedGroups.compactMap { $0.toProto() })
 | |
|         configurationProto.setOpenGroups([String](openGroups))
 | |
|         configurationProto.setContacts(contacts.compactMap { $0.toProto() })
 | |
|         let contentProto = SNProtoContent.builder()
 | |
|         do {
 | |
|             contentProto.setConfigurationMessage(try configurationProto.build())
 | |
|             return try contentProto.build()
 | |
|         } catch {
 | |
|             SNLog("Couldn't construct configuration proto from: \(self).")
 | |
|             return nil
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // MARK: - Description
 | |
|     
 | |
|     public var description: String {
 | |
|         """
 | |
|         ConfigurationMessage(
 | |
|             closedGroups: \([CMClosedGroup](closedGroups).prettifiedDescription),
 | |
|             openGroups: \([String](openGroups).prettifiedDescription),
 | |
|             displayName: \(displayName ?? "null"),
 | |
|             profilePictureUrl: \(profilePictureUrl ?? "null"),
 | |
|             profileKey: \(profileKey?.toHexString() ?? "null"),
 | |
|             contacts: \([CMContact](contacts).prettifiedDescription)
 | |
|         )
 | |
|         """
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - Closed Group
 | |
| 
 | |
| extension ConfigurationMessage {
 | |
|     public struct CMClosedGroup: Codable, Hashable, CustomStringConvertible {
 | |
|         private enum CodingKeys: String, CodingKey {
 | |
|             case publicKey
 | |
|             case name
 | |
|             case encryptionKeyPublicKey
 | |
|             case encryptionKeySecretKey
 | |
|             case members
 | |
|             case admins
 | |
|             case expirationTimer
 | |
|         }
 | |
|         
 | |
|         public let publicKey: String
 | |
|         public let name: String
 | |
|         public let encryptionKeyPublicKey: Data
 | |
|         public let encryptionKeySecretKey: Data
 | |
|         public let members: Set<String>
 | |
|         public let admins: Set<String>
 | |
|         public let expirationTimer: UInt32
 | |
| 
 | |
|         public var isValid: Bool { !members.isEmpty && !admins.isEmpty }
 | |
|         
 | |
|         // MARK: - Initialization
 | |
|         
 | |
|         public init(
 | |
|             publicKey: String,
 | |
|             name: String,
 | |
|             encryptionKeyPublicKey: Data,
 | |
|             encryptionKeySecretKey: Data,
 | |
|             members: Set<String>,
 | |
|             admins: Set<String>,
 | |
|             expirationTimer: UInt32
 | |
|         ) {
 | |
|             self.publicKey = publicKey
 | |
|             self.name = name
 | |
|             self.encryptionKeyPublicKey = encryptionKeyPublicKey
 | |
|             self.encryptionKeySecretKey = encryptionKeySecretKey
 | |
|             self.members = members
 | |
|             self.admins = admins
 | |
|             self.expirationTimer = expirationTimer
 | |
|         }
 | |
| 
 | |
|         // MARK: - Codable
 | |
|         
 | |
|         public init(from decoder: Decoder) throws {
 | |
|             let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
 | |
|             
 | |
|             publicKey = try container.decode(String.self, forKey: .publicKey)
 | |
|             name = try container.decode(String.self, forKey: .name)
 | |
|             encryptionKeyPublicKey = try container.decode(Data.self, forKey: .encryptionKeyPublicKey)
 | |
|             encryptionKeySecretKey = try container.decode(Data.self, forKey: .encryptionKeySecretKey)
 | |
|             members = try container.decode(Set<String>.self, forKey: .members)
 | |
|             admins = try container.decode(Set<String>.self, forKey: .admins)
 | |
|             expirationTimer = try container.decode(UInt32.self, forKey: .expirationTimer)
 | |
|         }
 | |
|         
 | |
|         public func encode(to encoder: Encoder) throws {
 | |
|             var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
 | |
| 
 | |
|             try container.encode(publicKey, forKey: .publicKey)
 | |
|             try container.encode(name, forKey: .name)
 | |
|             try container.encode(encryptionKeyPublicKey, forKey: .encryptionKeyPublicKey)
 | |
|             try container.encode(encryptionKeySecretKey, forKey: .encryptionKeySecretKey)
 | |
|             try container.encode(members, forKey: .members)
 | |
|             try container.encode(admins, forKey: .admins)
 | |
|             try container.encode(expirationTimer, forKey: .expirationTimer)
 | |
|         }
 | |
| 
 | |
|         public static func fromProto(_ proto: SNProtoConfigurationMessageClosedGroup) -> CMClosedGroup? {
 | |
|             guard
 | |
|                 let publicKey = proto.publicKey?.toHexString(),
 | |
|                 let name = proto.name,
 | |
|                 let encryptionKeyPairAsProto = proto.encryptionKeyPair
 | |
|             else { return nil }
 | |
|             
 | |
|             let members = Set(proto.members.map { $0.toHexString() })
 | |
|             let admins = Set(proto.admins.map { $0.toHexString() })
 | |
|             let expirationTimer = proto.expirationTimer
 | |
|             let result = CMClosedGroup(
 | |
|                 publicKey: publicKey,
 | |
|                 name: name,
 | |
|                 encryptionKeyPublicKey: encryptionKeyPairAsProto.publicKey,
 | |
|                 encryptionKeySecretKey: encryptionKeyPairAsProto.privateKey,
 | |
|                 members: members,
 | |
|                 admins: admins,
 | |
|                 expirationTimer: expirationTimer
 | |
|             )
 | |
|             
 | |
|             guard result.isValid else { return nil }
 | |
|             return result
 | |
|         }
 | |
| 
 | |
|         public func toProto() -> SNProtoConfigurationMessageClosedGroup? {
 | |
|             guard isValid else { return nil }
 | |
|             let result = SNProtoConfigurationMessageClosedGroup.builder()
 | |
|             result.setPublicKey(Data(hex: publicKey))
 | |
|             result.setName(name)
 | |
|             do {
 | |
|                 let encryptionKeyPairAsProto = try SNProtoKeyPair.builder(
 | |
|                     publicKey: encryptionKeyPublicKey,
 | |
|                     privateKey: encryptionKeySecretKey
 | |
|                 ).build()
 | |
|                 result.setEncryptionKeyPair(encryptionKeyPairAsProto)
 | |
|             } catch {
 | |
|                 SNLog("Couldn't construct closed group proto from: \(self).")
 | |
|                 return nil
 | |
|             }
 | |
|             result.setMembers(members.map { Data(hex: $0) })
 | |
|             result.setAdmins(admins.map { Data(hex: $0) })
 | |
|             result.setExpirationTimer(expirationTimer)
 | |
|             do {
 | |
|                 return try result.build()
 | |
|             } catch {
 | |
|                 SNLog("Couldn't construct closed group proto from: \(self).")
 | |
|                 return nil
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public var description: String { name }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - Contact
 | |
| 
 | |
| extension ConfigurationMessage {
 | |
|     public struct CMContact: Codable, Hashable, CustomStringConvertible {
 | |
|         private enum CodingKeys: String, CodingKey {
 | |
|             case publicKey
 | |
|             case displayName
 | |
|             case profilePictureUrl
 | |
|             case profileKey
 | |
|             
 | |
|             case hasIsApproved
 | |
|             case isApproved
 | |
|             case hasIsBlocked
 | |
|             case isBlocked
 | |
|             case hasDidApproveMe
 | |
|             case didApproveMe
 | |
|         }
 | |
|         
 | |
|         public var publicKey: String?
 | |
|         public var displayName: String?
 | |
|         public var profilePictureUrl: String?
 | |
|         public var profileKey: Data?
 | |
|         
 | |
|         public var hasIsApproved: Bool
 | |
|         public var isApproved: Bool
 | |
|         public var hasIsBlocked: Bool
 | |
|         public var isBlocked: Bool
 | |
|         public var hasDidApproveMe: Bool
 | |
|         public var didApproveMe: Bool
 | |
| 
 | |
|         public var isValid: Bool { publicKey != nil && displayName != nil }
 | |
| 
 | |
|         public init(
 | |
|             publicKey: String?,
 | |
|             displayName: String?,
 | |
|             profilePictureUrl: String?,
 | |
|             profileKey: Data?,
 | |
|             hasIsApproved: Bool,
 | |
|             isApproved: Bool,
 | |
|             hasIsBlocked: Bool,
 | |
|             isBlocked: Bool,
 | |
|             hasDidApproveMe: Bool,
 | |
|             didApproveMe: Bool
 | |
|         ) {
 | |
|             self.publicKey = publicKey
 | |
|             self.displayName = displayName
 | |
|             self.profilePictureUrl = profilePictureUrl
 | |
|             self.profileKey = profileKey
 | |
|             self.hasIsApproved = hasIsApproved
 | |
|             self.isApproved = isApproved
 | |
|             self.hasIsBlocked = hasIsBlocked
 | |
|             self.isBlocked = isBlocked
 | |
|             self.hasDidApproveMe = hasDidApproveMe
 | |
|             self.didApproveMe = didApproveMe
 | |
|         }
 | |
|         
 | |
|         // MARK: - Codable
 | |
|         
 | |
|         public init(from decoder: Decoder) throws {
 | |
|             let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
 | |
|             
 | |
|             publicKey = try? container.decode(String.self, forKey: .publicKey)
 | |
|             displayName = try? container.decode(String.self, forKey: .displayName)
 | |
|             profilePictureUrl = try? container.decode(String.self, forKey: .profilePictureUrl)
 | |
|             profileKey = try? container.decode(Data.self, forKey: .profileKey)
 | |
|             
 | |
|             hasIsApproved = try container.decode(Bool.self, forKey: .hasIsApproved)
 | |
|             isApproved = try container.decode(Bool.self, forKey: .isApproved)
 | |
|             hasIsBlocked = try container.decode(Bool.self, forKey: .hasIsBlocked)
 | |
|             isBlocked = try container.decode(Bool.self, forKey: .isBlocked)
 | |
|             hasDidApproveMe = try container.decode(Bool.self, forKey: .hasDidApproveMe)
 | |
|             didApproveMe = try container.decode(Bool.self, forKey: .didApproveMe)
 | |
|         }
 | |
| 
 | |
|         public static func fromProto(_ proto: SNProtoConfigurationMessageContact) -> CMContact? {
 | |
|             let result: CMContact = CMContact(
 | |
|                 publicKey: proto.publicKey.toHexString(),
 | |
|                 displayName: proto.name,
 | |
|                 profilePictureUrl: proto.profilePicture,
 | |
|                 profileKey: proto.profileKey,
 | |
|                 hasIsApproved: proto.hasIsApproved,
 | |
|                 isApproved: proto.isApproved,
 | |
|                 hasIsBlocked: proto.hasIsBlocked,
 | |
|                 isBlocked: proto.isBlocked,
 | |
|                 hasDidApproveMe: proto.hasDidApproveMe,
 | |
|                 didApproveMe: proto.didApproveMe
 | |
|             )
 | |
|             
 | |
|             guard result.isValid else { return nil }
 | |
|             return result
 | |
|         }
 | |
| 
 | |
|         public func toProto() -> SNProtoConfigurationMessageContact? {
 | |
|             guard isValid else { return nil }
 | |
|             guard let publicKey = publicKey, let displayName = displayName else { return nil }
 | |
|             let result = SNProtoConfigurationMessageContact.builder(publicKey: Data(hex: publicKey), name: displayName)
 | |
|             if let profilePictureUrl = profilePictureUrl { result.setProfilePicture(profilePictureUrl) }
 | |
|             if let profileKey = profileKey { result.setProfileKey(profileKey) }
 | |
|             
 | |
|             if hasIsApproved { result.setIsApproved(isApproved) }
 | |
|             if hasIsBlocked { result.setIsBlocked(isBlocked) }
 | |
|             if hasDidApproveMe { result.setDidApproveMe(didApproveMe) }
 | |
|             
 | |
|             do {
 | |
|                 return try result.build()
 | |
|             } catch {
 | |
|                 SNLog("Couldn't construct contact proto from: \(self).")
 | |
|                 return nil
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public var description: String { displayName ?? "" }
 | |
|     }
 | |
| }
 |