session-ios/SessionMessagingKit/Open Groups/V1/OpenGroupMessage.swift

197 lines
9.0 KiB
Swift
Raw Normal View History

2019-08-19 04:07:09 +02:00
import PromiseKit
import Curve25519Kit
import SessionUtilitiesKit
2019-08-19 04:07:09 +02:00
@objc(SNOpenGroupMessage)
public final class OpenGroupMessage : NSObject {
public let serverID: UInt64?
2020-07-20 03:02:58 +02:00
public let senderPublicKey: String
public let displayName: String
2020-02-13 06:51:07 +01:00
public let profilePicture: ProfilePicture?
public let body: String
2019-08-19 04:07:09 +02:00
/// - 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?
2019-10-18 02:32:36 +02:00
public var attachments: [Attachment] = []
2019-10-02 05:34:34 +02:00
public let signature: Signature?
2020-08-27 02:46:47 +02:00
/// - Note: Used for sorting.
2020-08-26 06:11:42 +02:00
public let serverTimestamp: UInt64
@objc(serverID)
public var objc_serverID: UInt64 { return serverID ?? 0 }
2019-10-02 05:34:34 +02:00
// MARK: Settings
private let signatureVersion: UInt64 = 1
2019-10-18 02:32:36 +02:00
private let attachmentType = "net.app.core.oembed"
2019-10-02 05:34:34 +02:00
// MARK: Types
2020-02-13 06:51:07 +01:00
public struct ProfilePicture {
public let profileKey: Data
public let url: String
}
public struct Quote {
public let quotedMessageTimestamp: UInt64
2020-07-20 03:02:58 +02:00
public let quoteePublicKey: String
2020-12-01 03:39:27 +01:00
public let quotedMessageBody: String
2019-10-02 05:34:34 +02:00
public let quotedMessageServerID: UInt64?
}
2019-10-18 02:32:36 +02:00
public struct Attachment {
2019-10-21 02:43:46 +02:00
public let kind: Kind
2019-10-21 00:32:28 +02:00
public let server: String
public let serverID: UInt64
2019-10-21 00:32:28 +02:00
public let contentType: String
public let size: UInt
public let fileName: String
public let flags: UInt
2019-10-18 02:32:36 +02:00
public let width: UInt
public let height: UInt
2019-10-18 05:29:56 +02:00
public let caption: String?
2019-10-18 02:32:36 +02:00
public let url: String
2019-10-21 02:43:46 +02:00
/// 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?
2019-10-18 02:32:36 +02:00
2019-10-21 02:43:46 +02:00
public enum Kind : String { case attachment, linkPreview = "preview" }
2019-10-23 05:56:27 +02:00
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"
}
}
2019-10-18 02:32:36 +02:00
}
2019-10-02 05:34:34 +02:00
public struct Signature {
2019-10-02 05:50:44 +02:00
public let data: Data
2019-10-02 05:34:34 +02:00
public let version: UInt64
}
2019-10-02 05:34:34 +02:00
// 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) {
2019-08-19 04:07:09 +02:00
self.serverID = serverID
2020-07-20 03:02:58 +02:00
self.senderPublicKey = senderPublicKey
2019-08-19 04:07:09 +02:00
self.displayName = displayName
2020-02-13 06:51:07 +01:00
self.profilePicture = profilePicture
2019-08-19 04:07:09 +02:00
self.body = body
self.type = type
self.timestamp = timestamp
self.quote = quote
2019-10-18 05:29:56 +02:00
self.attachments = attachments
2019-10-02 05:34:34 +02:00
self.signature = signature
2020-08-26 06:11:42 +02:00
self.serverTimestamp = serverTimestamp
super.init()
}
@objc public convenience init(senderPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64,
2020-12-01 03:39:27 +01:00
quotedMessageTimestamp: UInt64, quoteePublicKey: String?, quotedMessageBody: String, quotedMessageServerID: UInt64,
signatureData: Data?, signatureVersion: UInt64, serverTimestamp: UInt64) {
let quote: Quote?
2020-11-30 02:00:26 +01:00
if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteePublicKey {
2019-10-02 05:34:34 +02:00
let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil
2020-07-20 03:02:58 +02:00
quote = Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteeHexEncodedPublicKey, quotedMessageBody: quotedMessageBody, quotedMessageServerID: quotedMessageServerID)
} else {
quote = nil
}
2019-10-02 05:34:34 +02:00
let signature: Signature?
2019-10-02 05:50:44 +02:00
if let signatureData = signatureData, signatureVersion != 0 {
signature = Signature(data: signatureData, version: signatureVersion)
2019-10-02 05:34:34 +02:00
} else {
signature = nil
}
2020-08-26 06:11:42 +02:00
self.init(serverID: nil, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: nil, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature, serverTimestamp: serverTimestamp)
2019-10-02 05:34:34 +02:00
}
// MARK: Crypto
internal func sign(with privateKey: Data) -> OpenGroupMessage? {
2019-10-02 06:23:00 +02:00
guard let data = getValidationData(for: signatureVersion) else {
SNLog("Failed to sign open group message.")
2019-10-02 05:34:34 +02:00
return nil
}
2020-12-02 06:25:16 +01:00
let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair()!
2020-11-12 00:41:45 +01:00
guard let signatureData = try? Ed25519.sign(data, with: userKeyPair) else {
SNLog("Failed to sign open group message.")
2019-10-02 05:34:34 +02:00
return nil
}
2019-10-02 05:50:44 +02:00
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)
2019-08-19 04:07:09 +02:00
}
2019-10-02 05:34:34 +02:00
internal func hasValidSignature() -> Bool {
guard let signature = signature else { return false }
2019-10-02 06:23:00 +02:00
guard let data = getValidationData(for: signature.version) else { return false }
2020-07-20 03:02:58 +02:00
let publicKey = Data(hex: self.senderPublicKey.removing05PrefixIfNeeded())
2020-11-12 00:41:45 +01:00
return (try? Ed25519.verifySignature(signature.data, publicKey: publicKey, data: data)) ?? false
2019-10-02 05:34:34 +02:00
}
// MARK: JSON
internal func toJSON() -> JSON {
2019-10-02 05:34:34 +02:00
var value: JSON = [ "timestamp" : timestamp ]
if let quote = quote {
2020-12-01 03:39:27 +01:00
let quoteAsJSON: JSON = [ "id" : quote.quotedMessageTimestamp, "author" : quote.quoteePublicKey, "text" : quote.quotedMessageBody ]
2020-11-30 06:10:58 +01:00
value["quote"] = quoteAsJSON
}
2019-10-02 05:34:34 +02:00
if let signature = signature {
2019-10-02 05:50:44 +02:00
value["sig"] = signature.data.toHexString()
2019-10-02 05:34:34 +02:00
value["sigver"] = signature.version
}
2020-02-13 06:51:07 +01:00
if let profilePicture = profilePicture {
value["avatar"] = profilePicture;
2019-11-18 05:59:50 +01:00
}
2019-10-02 05:34:34 +02:00
let annotation: JSON = [ "type" : type, "value" : value ]
2019-10-18 05:29:56 +02:00
let attachmentAnnotations: [JSON] = attachments.map { attachment in
2019-10-21 00:32:28 +02:00
var attachmentValue: JSON = [
// Fields required by the .NET API
2019-10-23 05:56:27 +02:00
"version" : 1, "type" : attachment.dotNETType,
2019-10-21 00:32:28 +02:00
// Custom fields
2019-10-22 04:47:41 +02:00
"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
2019-10-21 00:32:28 +02:00
]
2019-10-18 05:29:56 +02:00
if let caption = attachment.caption {
attachmentValue["caption"] = caption
2019-10-18 05:29:56 +02:00
}
2019-10-21 02:43:46 +02:00
if let linkPreviewURL = attachment.linkPreviewURL {
attachmentValue["linkPreviewUrl"] = linkPreviewURL
}
if let linkPreviewTitle = attachment.linkPreviewTitle {
attachmentValue["linkPreviewTitle"] = linkPreviewTitle
}
2019-10-18 02:32:36 +02:00
return [ "type" : attachmentType, "value" : attachmentValue ]
}
var result: JSON = [ "text" : body, "annotations": [ annotation ] + attachmentAnnotations ]
2019-10-02 05:34:34 +02:00
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?) {
2019-10-21 02:43:46 +02:00
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)
2019-10-18 02:32:36 +02:00
attachments.append(attachment)
}
2019-10-02 06:23:00 +02:00
private func getValidationData(for signatureVersion: UInt64) -> Data? {
2019-10-02 05:34:34 +02:00
var string = "\(body.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))\(timestamp)"
if let quote = quote {
2020-12-01 03:39:27 +01:00
string += "\(quote.quotedMessageTimestamp)\(quote.quoteePublicKey)\(quote.quotedMessageBody.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))"
2019-10-02 05:50:44 +02:00
if let quotedMessageServerID = quote.quotedMessageServerID {
string += "\(quotedMessageServerID)"
}
2019-10-02 05:34:34 +02:00
}
2019-10-21 02:43:46 +02:00
string += attachments.sorted { $0.serverID < $1.serverID }.map { "\($0.serverID)" }.joined(separator: "")
2019-10-02 05:34:34 +02:00
string += "\(signatureVersion)"
return string.data(using: String.Encoding.utf8)
2019-08-19 04:07:09 +02:00
}
}