Skip to content

chore: update core crypto to 5.x - WPB-15886 #2806

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 15 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions WireAPI/Sources/WireAPI/APIs/MLSAPI/MLSAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ public protocol MLSAPI {

func getBackendMLSPublicKeys() async throws -> BackendMLSPublicKeys

/// Post a commit bundle.
///
/// - Parameter bundle: commit bundle to post
/// - Returns: updates events generated by the commit
///
/// Available from ``APIVersion`` v5.
///

func postCommitBundle(_ bundle: CommitBundle) async throws -> [UpdateEvent]

}
28 changes: 27 additions & 1 deletion WireAPI/Sources/WireAPI/APIs/MLSAPI/MLSAPIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,17 @@ import Foundation

/// Errors originating from `MLSAPI`.

public enum MLSAPIError: Error {
public enum MLSAPIError: Error, Codable, Equatable {

public init(from string: String) throws {
self = try JSONDecoder().decode(MLSAPIError.self, from: Data(string.utf8))
}

public func encodeAsString() throws -> String {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
return String(decoding: try encoder.encode(self), as: UTF8.self)
}

/// Unsupported endpoint for API version

Expand All @@ -30,4 +40,20 @@ public enum MLSAPIError: Error {

case mlsNotEnabled

/// Message was sent in an too old epoch

case mlsStaleMessage

/// A proposal of type Add or Remove does not apply to the full list of clients for a user

case mlsClientMismatch

/// The commit is not referencing all pending proposals

case mlsCommitMissingReferences

/// Generic error for all non recoverable MLS error

case mlsError(_ label: String, _ message: String)

}
4 changes: 4 additions & 0 deletions WireAPI/Sources/WireAPI/APIs/MLSAPI/MLSAPIV0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ class MLSAPIV0: MLSAPI, VersionedAPI {
throw MLSAPIError.unsupportedEndpointForAPIVersion
}

func postCommitBundle(_ bundle: CommitBundle) async throws -> [UpdateEvent] {
throw MLSAPIError.unsupportedEndpointForAPIVersion
}

}
45 changes: 45 additions & 0 deletions WireAPI/Sources/WireAPI/APIs/MLSAPI/MLSAPIV5.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,40 @@ class MLSAPIV5: MLSAPIV4 {
.parse(code: response.statusCode, data: data)
}

override func postCommitBundle(_ bundle: CommitBundle) async throws -> [UpdateEvent] {
let request = try URLRequestBuilder(path: "\(pathPrefix)/mls/commit-bundles")
.withMethod(.post)
.withAcceptType(.json)
.withBody(bundle.transportData(), contentType: .mls)
.build()

let (data, response) = try await apiService.executeRequest(
request,
requiringAccessToken: true
)

do {
return try ResponseParser()
.success(code: .created, type: CommitBundleResponseV5.self)
.failure(code: .conflict, label: "mls-stale-message", error: MLSAPIError.mlsStaleMessage)
.failure(code: .conflict, label: "mls-client-mismatch", error: MLSAPIError.mlsClientMismatch)
.failure(
code: .badRequest,
label: "mls-commit-missing-references",
error: MLSAPIError.mlsCommitMissingReferences
)
.failure(code: .conflict, decodableError: FailureResponse.self)
.parse(code: response.statusCode, data: data)
} catch {
if let failureResponse = error as? FailureResponse {
throw MLSAPIError.mlsError(failureResponse.label, failureResponse.message)
} else {
throw error
}
}

}

}

private struct BackendMLSPublicKeysResponseV5: Decodable, ToAPIModelConvertible {
Expand All @@ -52,3 +86,14 @@ private struct BackendMLSPublicKeysResponseV5: Decodable, ToAPIModelConvertible
}

}

private struct CommitBundleResponseV5: Decodable, ToAPIModelConvertible {

let time: UTCTime?
let events: [UpdateEventDecodingProxy]

func toAPIModel() -> [UpdateEvent] {
events.map(\.updateEvent)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,30 @@

import Foundation

enum ExternalCommitError: Error, Equatable {
public struct CommitBundle: Sendable, Equatable {

case failedToSendCommit(recovery: RecoveryStrategy, cause: SendCommitBundleAction.Failure)
case failedToMergePendingGroup
case failedToClearPendingGroup
public var welcome: Data?

enum RecoveryStrategy {
public var commit: Data

/// Retry the action from the beginning
case retry

/// Abort the action and log the error
case giveUp
public var groupInfo: Data

public init(welcome: Data?, commit: Data, groupInfo: Data) {
self.welcome = welcome
self.commit = commit
self.groupInfo = groupInfo
}
}

extension ExternalCommitError.RecoveryStrategy {
func transportData() -> Data {
var data = Data()
data.append(commit)

/// Whether the pending group should be cleared
if let welcome {
data.append(welcome)
}

var shouldClearPendingGroup: Bool {
switch self {
case .retry:
false
data.append(groupInfo)

case .giveUp:
true
}
return data
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ enum HTTPContentType: String {

case json = "application/json"

case mls = "message/mls"

}
93 changes: 93 additions & 0 deletions WireAPI/Tests/WireAPITests/APIs/MLSAPI/MLSAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,83 @@ final class MLSAPITests: XCTestCase {
try await api.getBackendMLSPublicKeys()
}
}

// MARK: - Send commit bundle

func testPostCommitBundleRequest() async throws {
// Given
let apiVersions = APIVersion.v5.andNextVersions

// Then
try await apiSnapshotHelper.verifyRequest(for: apiVersions) { sut in
// When
_ = try await sut.postCommitBundle(Scaffolding.commitBundle)
}
}

func testPostCommitBundle_SuccessResponse_201_V5_And_Next_Versions() async throws {
// Given
try await withThrowingTaskGroup(of: [UpdateEvent].self) { taskGroup in
let testedVersions = APIVersion.v5.andNextVersions

for version in testedVersions {
let apiService = MockAPIServiceProtocol.withResponses([
(.created, "PostCommitBundleSuccessResponse1")
])
let sut = version.buildAPI(apiService: apiService)

taskGroup.addTask {
// When
try await sut.postCommitBundle(Scaffolding.commitBundle)
}

for try await value in taskGroup {
// Then
XCTAssertEqual(value, [], "should get 201 for APIVersion \(version)")
}
}
}
}

func testPostCommitBundle_SuccessResponseWithEvents_201_V5_And_Next_Versions() async throws {
// Given
try await withThrowingTaskGroup(of: [UpdateEvent].self) { taskGroup in
let testedVersions = APIVersion.v5.andNextVersions

for version in testedVersions {
let apiService = MockAPIServiceProtocol.withResponses([
(.created, "PostCommitBundleSuccessResponse2")
])
let sut = version.buildAPI(apiService: apiService)

taskGroup.addTask {
// When
try await sut.postCommitBundle(Scaffolding.commitBundle)
}

for try await value in taskGroup {
// Then
XCTAssertEqual(value, Scaffolding.updateEvents, "should get 201 for APIVersion \(version)")
}
}
}
}

func testPostCommitBundle_givenV5AndErrorResponse() async throws {
// Given
let apiService = MockAPIServiceProtocol.withError(
statusCode: .conflict,
label: "mls-stale-message"
)

let api = MLSAPIV5(apiService: apiService)

// Then
await XCTAssertThrowsErrorAsync(MLSAPIError.mlsStaleMessage) {
// When
try await api.postCommitBundle(Scaffolding.commitBundle)
}
}
}

private extension APIVersion {
Expand All @@ -113,3 +190,19 @@ private extension APIVersion {
}

}

// MARK: Helpers

private enum Scaffolding {

static let commitBundle = CommitBundle(
welcome: nil,
commit: Data("commit".utf8),
groupInfo: Data("groupinfo".utf8)
)

static let updateEvents = [
UpdateEvent.unknown(eventType: "some event")
]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"events":[],
"time":"2025-03-31T15:09:38.196Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"events":[{
"type": "some event"
}],
"time":"2025-04-01T10:28:55.622Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
curl \
--request POST \
--header "Accept: application/json" \
--header "Content-Type: message/mls" \
--data "commitgroupinfo" \
"/v5/mls/commit-bundles"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
curl \
--request POST \
--header "Accept: application/json" \
--header "Content-Type: message/mls" \
--data "commitgroupinfo" \
"/v6/mls/commit-bundles"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
curl \
--request POST \
--header "Accept: application/json" \
--header "Content-Type: message/mls" \
--data "commitgroupinfo" \
"/v7/mls/commit-bundles"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
curl \
--request POST \
--header "Accept: application/json" \
--header "Content-Type: message/mls" \
--data "commitgroupinfo" \
"/v8/mls/commit-bundles"
15 changes: 13 additions & 2 deletions WireCoreCrypto/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,25 @@ let package = Package(
.library(
name: "WireCoreCrypto",
targets: ["WireCoreCrypto"]
),
.library(
name: "WireCoreCryptoUniffi",
targets: ["WireCoreCryptoUniffi"]
)
],
dependencies: [],
targets: [
.binaryTarget(
name: "WireCoreCrypto",
url: "https://github.com/wireapp/core-crypto/releases/download/v3.1.1/WireCoreCrypto.xcframework.zip",
checksum: "fc1ec9eb58d6324ab32c34d131c7a22838e076e16461b00da3506e3be3488011"
url: "https://github.com/wireapp/core-crypto/releases/download/v5.3.0/WireCoreCrypto.xcframework.zip",
checksum: "d291cf8ef997b1414448890446893465bb2147f23b396e92e6a8098de948b7f9"
),
// this is an internal dependency to WireCoreCrypto but currently needs to explictly
// added as a dependency due to limitations of Swift packages.
.binaryTarget(
name: "WireCoreCryptoUniffi",
url: "https://github.com/wireapp/core-crypto/releases/download/v5.3.0/WireCoreCryptoUniffi.xcframework.zip",
checksum: "4931c7473c83e157f5c89a6e6dda9a087d746e97f9b0a4443b106cb56e5b8789"
)
]
)
Loading
Loading