Files
Morgan Pretty 6bcd3f43a6 Refactoring to move pro state management to VMs instead of views
• Refactored the MessageViewModel to be a dumb data types instead of a monolithic query
• Refactored the ConversationViewModel due to handle the updated MessageViewModel
• Refactored OWSAudioPlayer into Swift
• Refactored the OpenGroupManager and OpenGroupManagerCache to be a single actor (CommunityManager), it also holds community state in memory
• Removed a bunch of GRDB-specific relationship code (just makes things messy since we have raw SQL elsewhere)
• Fixed a bug where the ObservationBuilder could incorrectly run queries in cancelled contexts resulting in potential buggy outputs
• Fixed a bug where outgoing voice messages might not be able to be played
2025-11-13 14:26:38 +11:00

133 lines
5.9 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtilitiesKit
extension Network.SOGS {
public struct Message: Codable, Equatable, Hashable {
enum CodingKeys: String, CodingKey {
case id
case sender = "session_id"
case posted
case edited
case deleted
case seqNo = "seqno"
case whisper
case whisperMods = "whisper_mods"
case whisperTo = "whisper_to"
case base64EncodedData = "data"
case base64EncodedSignature = "signature"
case reactions = "reactions"
}
public let id: Int64
public let sender: String?
public let posted: TimeInterval?
public let edited: TimeInterval?
public let deleted: Bool?
public let seqNo: Int64
public let whisper: Bool
public let whisperMods: Bool
public let whisperTo: String?
public let base64EncodedData: String?
public let base64EncodedSignature: String?
public struct Reaction: Codable, Equatable, Hashable {
enum CodingKeys: String, CodingKey {
case count
case reactors
case you
case index
}
public let count: Int64
public let reactors: [String]?
public let you: Bool
public let index: Int64
}
public let reactions: [String: Reaction]?
}
}
// MARK: - Decoder
extension Network.SOGS.Message {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
let maybeSender: String? = try container.decodeIfPresent(String.self, forKey: .sender)
let maybeBase64EncodedData: String? = try container.decodeIfPresent(String.self, forKey: .base64EncodedData)
let maybeBase64EncodedSignature: String? = try container.decodeIfPresent(String.self, forKey: .base64EncodedSignature)
let maybeReactions: [String: Reaction]? = try container.decodeIfPresent([String: Reaction].self, forKey: .reactions)
// If we have data and a signature (ie. the message isn't a deletion) then validate the signature
if let base64EncodedData: String = maybeBase64EncodedData, let base64EncodedSignature: String = maybeBase64EncodedSignature {
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
throw NetworkError.parsingFailed
}
// Verify the signature based on the SessionId.Prefix type
let maybeSenderSessionId: SessionId? = try? SessionId(from: sender)
let dependencies: Dependencies = try decoder.dependencies ?? { throw DependenciesError.missingDependencies }()
switch (maybeSenderSessionId, maybeSenderSessionId?.prefix) {
case (.some(let sessionId), .blinded15), (.some(let sessionId), .blinded25):
guard
dependencies[singleton: .crypto].verify(
.signature(message: data.bytes, publicKey: sessionId.publicKey, signature: signature.bytes)
)
else {
Log.info("Ignoring message with invalid signature.")
throw NetworkError.parsingFailed
}
case (.some(let sessionId), .standard), (.some(let sessionId), .unblinded):
guard
dependencies[singleton: .crypto].verify(
.signatureXed25519(signature, curve25519PublicKey: sessionId.publicKey, data: data)
)
else {
Log.info("Ignoring message with invalid signature.")
throw NetworkError.parsingFailed
}
case (.some, .none), (.none, _), (_, .group), (_, .versionBlinded07):
Log.info("Ignoring message with invalid sender.")
throw NetworkError.parsingFailed
}
}
self = Network.SOGS.Message(
id: try container.decode(Int64.self, forKey: .id),
sender: try container.decodeIfPresent(String.self, forKey: .sender),
posted: try container.decodeIfPresent(TimeInterval.self, forKey: .posted),
edited: try container.decodeIfPresent(TimeInterval.self, forKey: .edited),
deleted: try container.decodeIfPresent(Bool.self, forKey: .deleted),
seqNo: try container.decode(Int64.self, forKey: .seqNo),
whisper: ((try container.decodeIfPresent(Bool.self, forKey: .whisper)) ?? false),
whisperMods: ((try container.decodeIfPresent(Bool.self, forKey: .whisperMods)) ?? false),
whisperTo: try container.decodeIfPresent(String.self, forKey: .whisperTo),
base64EncodedData: maybeBase64EncodedData,
base64EncodedSignature: maybeBase64EncodedSignature,
reactions: !container.contains(.reactions) ? nil : (maybeReactions ?? [:])
)
}
}
extension Network.SOGS.Message.Reaction {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
self = Network.SOGS.Message.Reaction(
count: try container.decode(Int64.self, forKey: .count),
reactors: try container.decodeIfPresent([String].self, forKey: .reactors),
you: ((try container.decodeIfPresent(Bool.self, forKey: .you)) ?? false),
index: (try container.decode(Int64.self, forKey: .index))
)
}
}