Skip to content

refactor: process conversation proteus/MLS message add event - WPB-10174 #2164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b43063b
decrypt MLS message and process decrypted message content - WPB-10172
jullianm Oct 30, 2024
a6e3895
make some legacy methods public - WPB-10172
jullianm Oct 30, 2024
5dab545
add UTs - WPB-10172
jullianm Nov 4, 2024
636138c
Merge branch 'develop' into refactor/process-conversation-mls-message…
jullianm Nov 13, 2024
0bc3353
fix API test - WPB-10172 (#2120)
jullianm Nov 13, 2024
a0595f4
add MLS decryptor, process Proteus add message event - WPB-10174
jullianm Nov 15, 2024
be06042
add UTs, clean up code - WPB-10174
jullianm Nov 18, 2024
7a10c24
add doc remove unused code - WPB-10174 (#2164)
jullianm Nov 18, 2024
3be0ef3
lint and format - WPB-10174 (#2164)
jullianm Nov 18, 2024
7d6bdc4
lint and format - WPB-10174 (#2164)
jullianm Nov 18, 2024
b31805a
remove unnecessary log messages - WPB-10174 (#2164)
jullianm Nov 28, 2024
c08ed17
add protobuf message processor, process message content, factored out…
jullianm Nov 29, 2024
adae3a9
rename methods, update doc - WPB-10174 (#2164)
jullianm Dec 2, 2024
6ffdce3
lint and format - WPB-10174 (#2164)
jullianm Dec 2, 2024
6b00435
remove unnecessary repo dependency
jullianm Dec 2, 2024
a93bff7
add sanity check and log when no decrypted message to process
jullianm Dec 2, 2024
6dee7ff
create MLS conversation info local store method, remove no longer rel…
jullianm Dec 2, 2024
90da7ad
move methods to conversation local store, throw error instead of log,…
jullianm Dec 5, 2024
244f4d5
split up add Proteus/MLS message logic in MessageLocalStore, treat ea…
jullianm Dec 6, 2024
0937b42
move log message up to the processor - WPB-10174 (#2164)
jullianm Dec 6, 2024
73e1518
Merge branch 'develop' into refactor/process-conversation-proteus-mes…
jullianm Dec 11, 2024
a6cd475
fix remaining conflicts
jullianm Dec 11, 2024
19be080
fix generated mocks issue
jullianm Dec 12, 2024
392e6f5
fix UTs
jullianm Dec 12, 2024
287c9cf
lint and format
jullianm Dec 12, 2024
6818ade
Merge branch 'develop' into refactor/process-conversation-proteus-mes…
jullianm Dec 12, 2024
4af9b49
fix lint issues
jullianm Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,17 @@ struct ConversationMLSMessageAddEventDecoder {
forKey: .payload
)

let timestamp = try container.decodeIfPresent(
UTCTimeMillis.self,
forKey: .timestamp
)

return ConversationMLSMessageAddEvent(
conversationID: conversationID,
senderID: senderID,
subconversation: subconversation,
message: payload.text
message: payload.text,
timestamp: timestamp?.date
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ struct ConversationProteusMessageAddEventDecoder {
conversationID: conversationID,
senderID: senderID,
timestamp: timestamp.date,
message: .ciphertext(payload.text),
externalData: payload.data.map { .ciphertext($0) },
message: .init(encryptedMessage: payload.text),
externalData: payload.data.map { .init(encryptedMessage: $0) },
messageSenderClientID: payload.sender,
messageRecipientClientID: payload.recipient
)
Expand Down
6 changes: 3 additions & 3 deletions WireAPI/Sources/WireAPI/Models/Messaging/MessageContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import Foundation
/// The contents of a message, typically as a base-64 encoded
/// Protobuf string.

public enum MessageContent: Equatable, Codable {
public struct MessageContent: Equatable, Codable, Sendable {

/// Encrypted message content.

case ciphertext(String)
public let encryptedMessage: String

/// Unencrypted message content.

case plaintext(String)
public var decryptedMessage: String?

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,22 @@ import Foundation

/// An event where an mls message was received in a conversation.

public struct ConversationMLSMessageAddEvent: Equatable, Codable {
public struct ConversationMLSMessageAddEvent: Equatable, Codable, Sendable {

public struct DecryptedMessage: Equatable, Codable, Sendable {

public let message: String

public let senderClientID: String?

public init(
message: String,
senderClientID: String?
) {
self.message = message
self.senderClientID = senderClientID
}
}

/// The id of the conversation.

Expand All @@ -42,4 +57,13 @@ public struct ConversationMLSMessageAddEvent: Equatable, Codable {

public let message: String

/// The date the message was received.

public let timestamp: Date?

/// The decrypted current message + decrypted buffered messages
/// along with the related sender client ID for each message.

public var decryptedMessages: [DecryptedMessage] = []

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Foundation

/// An event where a proteus message was received in a conversation.

public struct ConversationProteusMessageAddEvent: Equatable, Codable {
public struct ConversationProteusMessageAddEvent: Equatable, Codable, Sendable {

/// The id of the conversation.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ private enum Scaffolding {
conversationID: conversationID,
senderID: senderID,
timestamp: timestamp,
message: .ciphertext("foo"),
externalData: .ciphertext("bar"),
message: .init(encryptedMessage: "foo"),
externalData: .init(encryptedMessage: "bar"),
messageSenderClientID: "abc123",
messageRecipientClientID: "def456"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ final class ConversationEventDecodingTests: XCTestCase {
conversationID: conversationID,
senderID: senderID,
subconversation: "subconversation",
message: "message"
message: "message",
timestamp: fractionalDate(from: "2024-06-04T15:03:07.598Z")
)

static let mlsWelcomeEvent = ConversationMLSWelcomeEvent(
Expand All @@ -471,8 +472,8 @@ final class ConversationEventDecodingTests: XCTestCase {
conversationID: conversationID,
senderID: senderID,
timestamp: timestamp,
message: .ciphertext("foo"),
externalData: .ciphertext("bar"),
message: .init(encryptedMessage: "foo"),
externalData: .init(encryptedMessage: "bar"),
messageSenderClientID: "abc123",
messageRecipientClientID: "def456"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"subconv": "subconversation",
"data": {
"text": "message"
}
},
"time": "2024-06-04T15:03:07.598Z"
}
48 changes: 42 additions & 6 deletions WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//
// Wire
// Copyright (C) 2024 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation
import WireAPI
import WireDataModel

// sourcery: AutoMockable
/// Decrypt MLS messages.
protocol MLSMessageDecryptorProtocol {

/// Decrypt a MLS message.
///
/// - Parameter eventData: A payload containing the encrypted message.
/// - Returns: The payload containing the decrypted message.

func decryptedEventData(
from eventData: ConversationMLSMessageAddEvent
) async throws -> ConversationMLSMessageAddEvent

}

struct MLSMessageDecryptor: MLSMessageDecryptorProtocol {

let mlsDecryptionService: any MLSDecryptionServiceInterface
let mlsService: any MLSServiceInterface
let conversationLocalStore: any ConversationLocalStoreProtocol

func decryptedEventData(
from eventData: ConversationMLSMessageAddEvent
) async throws -> ConversationMLSMessageAddEvent {
let conversationID = eventData.conversationID

guard let mlsConversation = await conversationLocalStore.fetchConversation(
id: conversationID.uuid,
domain: conversationID.domain
) else {
WireLogger.mls.error(
"failed to add mls message: conversation not found in db"
)

throw MLSMessageDecryptorError.conversationNotFound
}

guard let mlsGroupID = await conversationLocalStore.mlsGroupID(
for: mlsConversation
) else {
WireLogger.mls.error(
"failed to add mls message: missing MLS group ID"
)

throw MLSMessageDecryptorError.missingMLSGroupID
}

guard await conversationLocalStore.isConversationMLSReady(
mlsConversation
) else {
WireLogger.mls.warn(
"failed to add mls message: conversation is not ready"
)

throw MLSMessageDecryptorError.mlsConversationNotReady
}

let decryptionResults = await decryptMLSMessage(
message: eventData.message,
mlsGroupID: mlsGroupID,
subconversation: eventData.subconversation
)

let decryptedMessages = await processMLSMessageDecryptionResults(
decryptionResults,
mlsConversation: mlsConversation,
senderID: eventData.senderID.uuid,
senderDomain: eventData.senderID.domain,
date: eventData.timestamp
)

var decryptedEvent = eventData
decryptedEvent.decryptedMessages = decryptedMessages

return decryptedEvent
}

private func decryptMLSMessage(
message: String,
mlsGroupID: MLSGroupID,
subconversation: String?
) async -> [MLSDecryptResult] {
do {
let subconvType = subconversation != nil ? SubgroupType(rawValue: subconversation!) : nil

let results = try await mlsDecryptionService.decrypt(
message: message,
for: mlsGroupID,
subconversationType: subconvType
)

if results.isEmpty {
WireLogger.mls.info(
"successfully decrypted mls message but no result was returned"
)

return []
}

return results

} catch {
WireLogger.mls.warn(
"failed to decrypt mls message: \(String(describing: error))"
)

return []
}
}

private func processMLSMessageDecryptionResults(
_ results: [MLSDecryptResult],
mlsConversation: ZMConversation,
senderID: UUID,
senderDomain: String,
date: Date?
) async -> [ConversationMLSMessageAddEvent.DecryptedMessage] {
var decryptedMessages: [ConversationMLSMessageAddEvent.DecryptedMessage] = []

for result in results {
switch result {
case .message(let decryptedData, let senderClientID):
let mlsDecryptedMessage = decryptedData.base64EncodedString()
decryptedMessages.append(
.init(
message: mlsDecryptedMessage,
senderClientID: senderClientID
)
)

case .proposal(let commitDelay):
let scheduledDate = (date ?? Date.now) + TimeInterval(commitDelay)

await conversationLocalStore.storeConversation(
commitPendingProposalDate: scheduledDate,
conversation: mlsConversation
)

mlsService.commitPendingProposalsIfNeeded()
}
}

return decryptedMessages
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Wire
// Copyright (C) 2024 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

enum MLSMessageDecryptorError: Error {

case conversationNotFound

case missingMLSGroupID

case mlsConversationNotReady

}
Loading