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.
		
		
		
		
		
			
		
			
				
	
	
		
			196 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			196 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Swift
		
	
| import PromiseKit
 | |
| import Curve25519Kit
 | |
| import SessionUtilitiesKit
 | |
| 
 | |
| @objc(SNOpenGroupMessage)
 | |
| public final class OpenGroupMessage : NSObject {
 | |
|     public let serverID: UInt64?
 | |
|     public let senderPublicKey: String
 | |
|     public let displayName: String
 | |
|     public let profilePicture: ProfilePicture?
 | |
|     public let body: String
 | |
|     /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970.
 | |
|     public let timestamp: UInt64
 | |
|     public let type: String
 | |
|     public let quote: Quote?
 | |
|     public var attachments: [Attachment] = []
 | |
|     public let signature: Signature?
 | |
|     /// - Note: Used for sorting.
 | |
|     public let serverTimestamp: UInt64
 | |
|     
 | |
|     @objc(serverID)
 | |
|     public var objc_serverID: UInt64 { return serverID ?? 0 }
 | |
|     
 | |
|     // MARK: Settings
 | |
|     private let signatureVersion: UInt64 = 1
 | |
|     private let attachmentType = "net.app.core.oembed"
 | |
|     
 | |
|     // MARK: Types
 | |
|     public struct ProfilePicture {
 | |
|         public let profileKey: Data
 | |
|         public let url: String
 | |
|     }
 | |
|     
 | |
|     public struct Quote {
 | |
|         public let quotedMessageTimestamp: UInt64
 | |
|         public let quoteePublicKey: String
 | |
|         public let quotedMessageBody: String
 | |
|         public let quotedMessageServerID: UInt64?
 | |
|     }
 | |
|     
 | |
|     public struct Attachment {
 | |
|         public let kind: Kind
 | |
|         public let server: String
 | |
|         public let serverID: UInt64
 | |
|         public let contentType: String
 | |
|         public let size: UInt
 | |
|         public let fileName: String
 | |
|         public let flags: UInt
 | |
|         public let width: UInt
 | |
|         public let height: UInt
 | |
|         public let caption: String?
 | |
|         public let url: String
 | |
|         /// Guaranteed to be non-`nil` if `kind` is `linkPreview`
 | |
|         public let linkPreviewURL: String?
 | |
|         /// Guaranteed to be non-`nil` if `kind` is `linkPreview`
 | |
|         public let linkPreviewTitle: String?
 | |
|         
 | |
|         public enum Kind : String { case attachment, linkPreview = "preview" }
 | |
|         
 | |
|         public var dotNETType: String {
 | |
|             if contentType.hasPrefix("image") {
 | |
|                 return "photo"
 | |
|             } else if contentType.hasPrefix("video") {
 | |
|                 return "video"
 | |
|             } else if contentType.hasPrefix("audio") {
 | |
|                 return "audio"
 | |
|             } else {
 | |
|                 return "other"
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public struct Signature {
 | |
|         public let data: Data
 | |
|         public let version: UInt64
 | |
|     }
 | |
|     
 | |
|     // MARK: Initialization
 | |
|     public init(serverID: UInt64?, senderPublicKey: String, displayName: String, profilePicture: ProfilePicture?, body: String,
 | |
|             type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?, serverTimestamp: UInt64) {
 | |
|         self.serverID = serverID
 | |
|         self.senderPublicKey = senderPublicKey
 | |
|         self.displayName = displayName
 | |
|         self.profilePicture = profilePicture
 | |
|         self.body = body
 | |
|         self.type = type
 | |
|         self.timestamp = timestamp
 | |
|         self.quote = quote
 | |
|         self.attachments = attachments
 | |
|         self.signature = signature
 | |
|         self.serverTimestamp = serverTimestamp
 | |
|         super.init()
 | |
|     }
 | |
|     
 | |
|     @objc public convenience init(senderPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64,
 | |
|             quotedMessageTimestamp: UInt64, quoteePublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64,
 | |
|             signatureData: Data?, signatureVersion: UInt64, serverTimestamp: UInt64) {
 | |
|         let quote: Quote?
 | |
|         if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteePublicKey, let quotedMessageBody = quotedMessageBody {
 | |
|             let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil
 | |
|             quote = Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteeHexEncodedPublicKey, quotedMessageBody: quotedMessageBody, quotedMessageServerID: quotedMessageServerID)
 | |
|         } else {
 | |
|             quote = nil
 | |
|         }
 | |
|         let signature: Signature?
 | |
|         if let signatureData = signatureData, signatureVersion != 0 {
 | |
|             signature = Signature(data: signatureData, version: signatureVersion)
 | |
|         } else {
 | |
|             signature = nil
 | |
|         }
 | |
|         self.init(serverID: nil, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: nil, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature, serverTimestamp: serverTimestamp)
 | |
|     }
 | |
|     
 | |
|     // MARK: Crypto
 | |
|     internal func sign(with privateKey: Data) -> OpenGroupMessage? {
 | |
|         guard let data = getValidationData(for: signatureVersion) else {
 | |
|             SNLog("Failed to sign open group message.")
 | |
|             return nil
 | |
|         }
 | |
|         let userKeyPair = Configuration.shared.storage.getUserKeyPair()!
 | |
|         guard let signatureData = try? Ed25519.sign(data, with: userKeyPair) else {
 | |
|             SNLog("Failed to sign open group message.")
 | |
|             return nil
 | |
|         }
 | |
|         let signature = Signature(data: signatureData, version: signatureVersion)
 | |
|         return OpenGroupMessage(serverID: serverID, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: profilePicture, body: body, type: type, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature, serverTimestamp: serverTimestamp)
 | |
|     }
 | |
|     
 | |
|     internal func hasValidSignature() -> Bool {
 | |
|         guard let signature = signature else { return false }
 | |
|         guard let data = getValidationData(for: signature.version) else { return false }
 | |
|         let publicKey = Data(hex: self.senderPublicKey.removing05PrefixIfNeeded())
 | |
|         return (try? Ed25519.verifySignature(signature.data, publicKey: publicKey, data: data)) ?? false
 | |
|     }
 | |
|     
 | |
|     // MARK: JSON
 | |
|     internal func toJSON() -> JSON {
 | |
|         var value: JSON = [ "timestamp" : timestamp ]
 | |
|         if let quote = quote {
 | |
|             value["quote"] = [ "id" : quote.quotedMessageTimestamp, "author" : quote.quoteePublicKey, "text" : quote.quotedMessageBody ]
 | |
|         }
 | |
|         if let signature = signature {
 | |
|             value["sig"] = signature.data.toHexString()
 | |
|             value["sigver"] = signature.version
 | |
|         }
 | |
|         if let profilePicture = profilePicture {
 | |
|             value["avatar"] = profilePicture;
 | |
|         }
 | |
|         let annotation: JSON = [ "type" : type, "value" : value ]
 | |
|         let attachmentAnnotations: [JSON] = attachments.map { attachment in
 | |
|             var attachmentValue: JSON = [
 | |
|                 // Fields required by the .NET API
 | |
|                 "version" : 1, "type" : attachment.dotNETType,
 | |
|                 // Custom fields
 | |
|                 "lokiType" : attachment.kind.rawValue, "server" : attachment.server, "id" : attachment.serverID, "contentType" : attachment.contentType, "size" : attachment.size, "fileName" : attachment.fileName, "width" : attachment.width, "height" : attachment.height, "url" : attachment.url
 | |
|             ]
 | |
|             if let caption = attachment.caption {
 | |
|                 attachmentValue["caption"] = caption
 | |
|             }
 | |
|             if let linkPreviewURL = attachment.linkPreviewURL {
 | |
|                 attachmentValue["linkPreviewUrl"] = linkPreviewURL
 | |
|             }
 | |
|             if let linkPreviewTitle = attachment.linkPreviewTitle {
 | |
|                 attachmentValue["linkPreviewTitle"] = linkPreviewTitle
 | |
|             }
 | |
|             return [ "type" : attachmentType, "value" : attachmentValue ]
 | |
|         }
 | |
|         var result: JSON = [ "text" : body, "annotations": [ annotation ] + attachmentAnnotations ]
 | |
|         if let quotedMessageServerID = quote?.quotedMessageServerID {
 | |
|             result["reply_to"] = quotedMessageServerID
 | |
|         }
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     // MARK: Convenience
 | |
|     @objc public func addAttachment(kind: String, server: String, serverID: UInt64, contentType: String, size: UInt,
 | |
|             fileName: String, flags: UInt, width: UInt, height: UInt, caption: String?, url: String, linkPreviewURL: String?, linkPreviewTitle: String?) {
 | |
|         guard let kind = Attachment.Kind(rawValue: kind) else { preconditionFailure() }
 | |
|         let attachment = Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle)
 | |
|         attachments.append(attachment)
 | |
|     }
 | |
|     
 | |
|     private func getValidationData(for signatureVersion: UInt64) -> Data? {
 | |
|         var string = "\(body.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))\(timestamp)"
 | |
|         if let quote = quote {
 | |
|             string += "\(quote.quotedMessageTimestamp)\(quote.quoteePublicKey)\(quote.quotedMessageBody.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))"
 | |
|             if let quotedMessageServerID = quote.quotedMessageServerID {
 | |
|                 string += "\(quotedMessageServerID)"
 | |
|             }
 | |
|         }
 | |
|         string += attachments.sorted { $0.serverID < $1.serverID }.map { "\($0.serverID)" }.joined(separator: "")
 | |
|         string += "\(signatureVersion)"
 | |
|         return string.data(using: String.Encoding.utf8)
 | |
|     }
 | |
| }
 |