mirror of
https://github.com/signalapp/libsignal.git
synced 2026-04-26 01:35:22 +02:00
545 lines
18 KiB
Swift
545 lines
18 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC.
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import SignalFfi
|
|
|
|
// These testing endpoints aren't generated in device builds, to save on code size.
|
|
#if !os(iOS) || targetEnvironment(simulator)
|
|
|
|
extension AuthenticatedChatConnection {
|
|
internal static func fakeConnect(
|
|
tokioAsyncContext: TokioAsyncContext,
|
|
listener: any ChatConnectionListener,
|
|
alerts: [String] = []
|
|
) -> (AuthenticatedChatConnection, FakeChatRemote) {
|
|
let (fakeChatConnection, listenerBridge) = failOnError {
|
|
try FakeChatConnection.create(
|
|
tokioAsyncContext: tokioAsyncContext,
|
|
listener: listener,
|
|
alerts: alerts
|
|
)
|
|
}
|
|
|
|
return failOnError {
|
|
let chatHandle = try fakeChatConnection.withNativeHandle { connectionHandle in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_connection_take_authenticated_chat(
|
|
$0,
|
|
connectionHandle.const()
|
|
)
|
|
}
|
|
}
|
|
let chat = AuthenticatedChatConnection(
|
|
fakeHandle: NonNull(chatHandle)!,
|
|
tokioAsyncContext: tokioAsyncContext
|
|
)
|
|
|
|
listenerBridge.setConnection(chatConnection: chat)
|
|
let fakeRemoteHandle = try fakeChatConnection.withNativeHandle { connectionHandle in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_connection_take_remote(
|
|
$0,
|
|
connectionHandle.const()
|
|
)
|
|
}
|
|
}
|
|
|
|
let fakeRemote = FakeChatRemote(
|
|
handle: NonNull(fakeRemoteHandle)!,
|
|
tokioAsyncContext: tokioAsyncContext
|
|
)
|
|
return (chat, fakeRemote)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension UnauthenticatedChatConnection {
|
|
internal static func fakeConnect(
|
|
tokioAsyncContext: TokioAsyncContext,
|
|
listener: any ConnectionEventsListener<UnauthenticatedChatConnection>
|
|
) -> (UnauthenticatedChatConnection, FakeChatRemote) {
|
|
let (fakeChatConnection, listenerBridge) = failOnError {
|
|
try FakeChatConnection.create(
|
|
tokioAsyncContext: tokioAsyncContext,
|
|
listener: listener,
|
|
alerts: []
|
|
)
|
|
}
|
|
|
|
return failOnError {
|
|
let chatHandle = try fakeChatConnection.withNativeHandle { connectionHandle in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_connection_take_unauthenticated_chat(
|
|
$0,
|
|
connectionHandle.const()
|
|
)
|
|
}
|
|
}
|
|
let chat = UnauthenticatedChatConnection(
|
|
fakeHandle: NonNull(chatHandle)!,
|
|
tokioAsyncContext: tokioAsyncContext,
|
|
environment: .staging
|
|
)
|
|
|
|
listenerBridge.setConnection(chatConnection: chat)
|
|
let fakeRemoteHandle = try fakeChatConnection.withNativeHandle { connectionHandle in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_connection_take_remote(
|
|
$0,
|
|
connectionHandle.const()
|
|
)
|
|
}
|
|
}
|
|
|
|
let fakeRemote = FakeChatRemote(
|
|
handle: NonNull(fakeRemoteHandle)!,
|
|
tokioAsyncContext: tokioAsyncContext
|
|
)
|
|
return (chat, fakeRemote)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ProvisioningConnection {
|
|
internal static func fakeConnect(
|
|
tokioAsyncContext: TokioAsyncContext,
|
|
listener: any ProvisioningConnectionListener
|
|
) -> (ProvisioningConnection, FakeChatRemote) {
|
|
let (fakeChatConnection, listenerBridge) = failOnError {
|
|
try FakeChatConnection.create(
|
|
tokioAsyncContext: tokioAsyncContext,
|
|
listener: listener,
|
|
)
|
|
}
|
|
|
|
return failOnError {
|
|
let connectionHandle = try fakeChatConnection.withNativeHandle { connectionHandle in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_connection_take_provisioning_chat(
|
|
$0,
|
|
connectionHandle.const()
|
|
)
|
|
}
|
|
}
|
|
let connection = ProvisioningConnection(
|
|
fakeHandle: NonNull(connectionHandle)!,
|
|
tokioAsyncContext: tokioAsyncContext,
|
|
)
|
|
|
|
listenerBridge.setConnection(connection)
|
|
let fakeRemoteHandle = try fakeChatConnection.withNativeHandle { connectionHandle in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_connection_take_remote(
|
|
$0,
|
|
connectionHandle.const()
|
|
)
|
|
}
|
|
}
|
|
|
|
let fakeRemote = FakeChatRemote(
|
|
handle: NonNull(fakeRemoteHandle)!,
|
|
tokioAsyncContext: tokioAsyncContext
|
|
)
|
|
return (connection, fakeRemote)
|
|
}
|
|
}
|
|
}
|
|
|
|
private class SetChatLaterListenerBridge: ChatListenerBridge {
|
|
private var savedAlerts: [String]?
|
|
|
|
override init(chatConnectionListenerForTesting chatListener: any ChatConnectionListener) {
|
|
super.init(chatConnectionListenerForTesting: chatListener)
|
|
}
|
|
|
|
func setConnection(chatConnection: AuthenticatedChatConnection) {
|
|
self.chatConnection = chatConnection
|
|
|
|
if let savedAlerts {
|
|
super.didReceiveAlerts(savedAlerts)
|
|
self.savedAlerts = nil
|
|
}
|
|
}
|
|
|
|
// Override point for ChatConnection+Fake.
|
|
override func didReceiveAlerts(_ alerts: [String]) {
|
|
// This callback can happen before setConnection, so we might need to replay it later.
|
|
guard self.chatConnection != nil else {
|
|
self.savedAlerts = alerts
|
|
return
|
|
}
|
|
|
|
super.didReceiveAlerts(alerts)
|
|
}
|
|
}
|
|
|
|
private class SetChatLaterUnauthListenerBridge: UnauthConnectionEventsListenerBridge {
|
|
override init(
|
|
chatConnectionEventsListenerForTesting chatListener: any ConnectionEventsListener<UnauthenticatedChatConnection>
|
|
) {
|
|
super.init(chatConnectionEventsListenerForTesting: chatListener)
|
|
}
|
|
|
|
func setConnection(chatConnection: UnauthenticatedChatConnection) {
|
|
self.chatConnection = chatConnection
|
|
}
|
|
}
|
|
|
|
// swiftlint:disable:next type_name
|
|
private class SetConnectionLaterProvisioningListenerBridge: ProvisioningListenerBridge {
|
|
override init(
|
|
connectionListenerForTesting listener: any ProvisioningConnectionListener
|
|
) {
|
|
super.init(connectionListenerForTesting: listener)
|
|
}
|
|
|
|
func setConnection(_ connection: ProvisioningConnection) {
|
|
self.connection = connection
|
|
}
|
|
}
|
|
|
|
internal class FakeChatRemote: NativeHandleOwner<SignalMutPointerFakeChatRemoteEnd> {
|
|
private let tokioAsyncContext: TokioAsyncContext
|
|
|
|
required init(owned: NonNull<SignalMutPointerFakeChatRemoteEnd>) {
|
|
fatalError("must not be invoked directly")
|
|
}
|
|
|
|
init(
|
|
handle: NonNull<SignalMutPointerFakeChatRemoteEnd>,
|
|
tokioAsyncContext: TokioAsyncContext
|
|
) {
|
|
self.tokioAsyncContext = tokioAsyncContext
|
|
super.init(owned: handle)
|
|
}
|
|
|
|
func injectServerRequest(base64: String) {
|
|
self.injectServerRequest(Data(base64Encoded: base64)!)
|
|
}
|
|
|
|
func injectServerRequest(_ requestBytes: Data) {
|
|
withNativeHandle { handle in
|
|
requestBytes.withUnsafeBorrowedBuffer { requestBytes in
|
|
failOnError(
|
|
signal_testing_fake_chat_remote_end_send_raw_server_request(
|
|
handle.const(),
|
|
requestBytes
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getNextIncomingRequest() async throws -> (ChatRequest.InternalRequest, UInt64) {
|
|
while true {
|
|
let request = try await self.tokioAsyncContext.invokeAsyncFunction { promise, asyncContext in
|
|
withNativeHandle { handle in
|
|
signal_testing_fake_chat_remote_end_receive_incoming_request(
|
|
promise,
|
|
asyncContext.const(),
|
|
handle.const()
|
|
)
|
|
}
|
|
}
|
|
guard request.present else {
|
|
continue
|
|
}
|
|
|
|
let httpRequest = ChatRequest.InternalRequest(owned: NonNull(request.first)!)
|
|
let requestId = request.second
|
|
|
|
return (httpRequest, requestId)
|
|
}
|
|
}
|
|
|
|
func sendResponse(requestId: UInt64, _ response: ChatResponse) throws {
|
|
let fakeResponse = FakeChatResponse(requestId: requestId, response)
|
|
try self.withNativeHandle { nativeHandle in
|
|
try fakeResponse.withNativeHandle { response in
|
|
try checkError(
|
|
signal_testing_fake_chat_remote_end_send_server_response(nativeHandle.const(), response.const())
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func injectServerResponse(base64: String) {
|
|
self.injectServerResponse(Data(base64Encoded: base64)!)
|
|
}
|
|
|
|
func injectServerResponse(_ responseBytes: Data) {
|
|
withNativeHandle { handle in
|
|
responseBytes.withUnsafeBorrowedBuffer { responseBytes in
|
|
failOnError(
|
|
signal_testing_fake_chat_remote_end_send_raw_server_response(
|
|
handle.const(),
|
|
responseBytes
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func injectConnectionInterrupted() {
|
|
withNativeHandle { handle in
|
|
failOnError(
|
|
signal_testing_fake_chat_remote_end_inject_connection_interrupted(
|
|
handle.const()
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
override class func destroyNativeHandle(
|
|
_ handle: NonNull<SignalMutPointerFakeChatRemoteEnd>
|
|
) -> SignalFfiErrorRef? {
|
|
signal_fake_chat_remote_end_destroy(handle.pointer)
|
|
}
|
|
}
|
|
|
|
internal class FakeChatServer: NativeHandleOwner<SignalMutPointerFakeChatServer>, @unchecked Sendable {
|
|
internal let asyncContext: TokioAsyncContext
|
|
internal init(asyncContext: TokioAsyncContext) {
|
|
self.asyncContext = asyncContext
|
|
|
|
let pointer = failOnError {
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_server_create($0)
|
|
}
|
|
}
|
|
super.init(owned: NonNull(pointer)!)
|
|
}
|
|
|
|
internal required init(owned: NonNull<SignalMutPointerFakeChatServer>) {
|
|
fatalError("cannot be invoked directly")
|
|
}
|
|
|
|
override class func destroyNativeHandle(
|
|
_ nativeHandle: NonNull<SignalMutPointerFakeChatServer>
|
|
) -> SignalFfiErrorRef? {
|
|
signal_fake_chat_server_destroy(nativeHandle.pointer)
|
|
}
|
|
|
|
internal func getNextRemote() async throws -> FakeChatRemote {
|
|
let remote = try await self.asyncContext.invokeAsyncFunction { promise, asyncContext in
|
|
self.withNativeHandle { nativeHandle in
|
|
signal_testing_fake_chat_server_get_next_remote(promise, asyncContext.const(), nativeHandle.const())
|
|
}
|
|
}
|
|
return FakeChatRemote(handle: NonNull(remote)!, tokioAsyncContext: self.asyncContext)
|
|
}
|
|
}
|
|
|
|
internal class FakeChatResponse: NativeHandleOwner<SignalMutPointerFakeChatResponse> {
|
|
internal init(requestId: UInt64, _ response: ChatResponse) {
|
|
let nativeHandle = failOnError {
|
|
try response.message.withCString { message in
|
|
try response.headers.map { (key: String, value: String) in
|
|
"\(key): \(value)"
|
|
}.withUnsafeBorrowedBytestringArray { headers in
|
|
try response.body.withUnsafeBorrowedBuffer { body in
|
|
try invokeFnReturningValueByPointer(.init()) {
|
|
signal_testing_fake_chat_response_create(
|
|
$0,
|
|
requestId,
|
|
response.status,
|
|
message,
|
|
headers,
|
|
SignalOptionalBorrowedSliceOfc_uchar(present: true, value: body)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
super.init(owned: NonNull(nativeHandle)!)
|
|
}
|
|
|
|
internal required init(owned: NonNull<SignalMutPointerFakeChatResponse>) {
|
|
fatalError("cannot be invoked directly")
|
|
}
|
|
|
|
override class func destroyNativeHandle(
|
|
_ nativeHandle: NonNull<SignalMutPointerFakeChatResponse>
|
|
) -> SignalFfiErrorRef? {
|
|
signal_fake_chat_response_destroy(nativeHandle.pointer)
|
|
}
|
|
}
|
|
|
|
private class FakeChatConnection: NativeHandleOwner<SignalMutPointerFakeChatConnection> {
|
|
static func create(
|
|
tokioAsyncContext: TokioAsyncContext,
|
|
listener: any ChatConnectionListener,
|
|
alerts: [String]
|
|
) throws -> (FakeChatConnection, SetChatLaterListenerBridge) {
|
|
let listenerBridge = SetChatLaterListenerBridge(
|
|
chatConnectionListenerForTesting: listener
|
|
)
|
|
var listenerStruct = listenerBridge.makeListenerStruct()
|
|
let chat = try FakeChatConnection.internalCreate(tokioAsyncContext, &listenerStruct, alerts)
|
|
return (chat, listenerBridge)
|
|
}
|
|
|
|
static func create(
|
|
tokioAsyncContext: TokioAsyncContext,
|
|
listener: any ConnectionEventsListener<UnauthenticatedChatConnection>,
|
|
alerts: [String]
|
|
) throws -> (FakeChatConnection, SetChatLaterUnauthListenerBridge) {
|
|
let listenerBridge = SetChatLaterUnauthListenerBridge(
|
|
chatConnectionEventsListenerForTesting: listener
|
|
)
|
|
var listenerStruct = listenerBridge.makeListenerStruct()
|
|
let chat = try FakeChatConnection.internalCreate(tokioAsyncContext, &listenerStruct, alerts)
|
|
return (chat, listenerBridge)
|
|
}
|
|
|
|
static func create(
|
|
tokioAsyncContext: TokioAsyncContext,
|
|
listener: any ProvisioningConnectionListener
|
|
) throws -> (FakeChatConnection, SetConnectionLaterProvisioningListenerBridge) {
|
|
let listenerBridge = SetConnectionLaterProvisioningListenerBridge(
|
|
connectionListenerForTesting: listener
|
|
)
|
|
var listenerStruct = listenerBridge.makeListenerStruct()
|
|
let connection: FakeChatConnection = try withUnsafePointer(to: &listenerStruct) { listener in
|
|
try tokioAsyncContext.withNativeHandle { asyncContext in
|
|
try invokeFnReturningNativeHandle {
|
|
signal_testing_fake_chat_connection_create_provisioning(
|
|
$0,
|
|
asyncContext.const(),
|
|
SignalConstPointerFfiProvisioningListenerStruct(raw: listener),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return (connection, listenerBridge)
|
|
}
|
|
|
|
private static func internalCreate(
|
|
_ tokioAsyncContext: TokioAsyncContext,
|
|
_ listenerStruct: inout SignalFfiChatListenerStruct,
|
|
_ alerts: [String]
|
|
) throws -> FakeChatConnection {
|
|
let connection: FakeChatConnection = try withUnsafePointer(to: &listenerStruct) { listener in
|
|
try tokioAsyncContext.withNativeHandle { asyncContext in
|
|
try invokeFnReturningNativeHandle {
|
|
signal_testing_fake_chat_connection_create(
|
|
$0,
|
|
asyncContext.const(),
|
|
SignalConstPointerFfiChatListenerStruct(raw: listener),
|
|
alerts.joined(separator: "\n")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return connection
|
|
}
|
|
|
|
override class func destroyNativeHandle(
|
|
_ handle: NonNull<SignalMutPointerFakeChatConnection>
|
|
) -> SignalFfiErrorRef? {
|
|
signal_fake_chat_connection_destroy(handle.pointer)
|
|
}
|
|
}
|
|
|
|
extension SignalMutPointerFakeChatConnection: SignalMutPointer {
|
|
public typealias ConstPointer = SignalConstPointerFakeChatConnection
|
|
|
|
public init(untyped: OpaquePointer?) {
|
|
self.init(raw: untyped)
|
|
}
|
|
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
|
|
public func const() -> Self.ConstPointer {
|
|
Self.ConstPointer(raw: self.raw)
|
|
}
|
|
}
|
|
|
|
extension SignalConstPointerFakeChatConnection: SignalConstPointer {
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
}
|
|
|
|
extension SignalMutPointerFakeChatRemoteEnd: SignalMutPointer {
|
|
public typealias ConstPointer = SignalConstPointerFakeChatRemoteEnd
|
|
|
|
public init(untyped: OpaquePointer?) {
|
|
self.init(raw: untyped)
|
|
}
|
|
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
|
|
public func const() -> Self.ConstPointer {
|
|
Self.ConstPointer(raw: self.raw)
|
|
}
|
|
}
|
|
|
|
extension SignalConstPointerFakeChatRemoteEnd: SignalConstPointer {
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
}
|
|
|
|
extension SignalMutPointerFakeChatServer: SignalMutPointer {
|
|
public typealias ConstPointer = SignalConstPointerFakeChatServer
|
|
|
|
public init(untyped: OpaquePointer?) {
|
|
self.init(raw: untyped)
|
|
}
|
|
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
|
|
public func const() -> Self.ConstPointer {
|
|
Self.ConstPointer(raw: self.raw)
|
|
}
|
|
}
|
|
|
|
extension SignalConstPointerFakeChatServer: SignalConstPointer {
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
}
|
|
|
|
extension SignalMutPointerFakeChatResponse: SignalMutPointer {
|
|
public typealias ConstPointer = SignalConstPointerFakeChatResponse
|
|
|
|
public init(untyped: OpaquePointer?) {
|
|
self.init(raw: untyped)
|
|
}
|
|
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
|
|
public func const() -> Self.ConstPointer {
|
|
Self.ConstPointer(raw: self.raw)
|
|
}
|
|
}
|
|
|
|
extension SignalConstPointerFakeChatResponse: SignalConstPointer {
|
|
public func toOpaque() -> OpaquePointer? {
|
|
self.raw
|
|
}
|
|
}
|
|
|
|
extension SignalCPromiseOptionalPairOfMutPointerHttpRequestu64: PromiseStruct {
|
|
typealias Result = SignalOptionalPairOfMutPointerHttpRequestu64
|
|
}
|
|
|
|
extension SignalCPromiseMutPointerFakeChatRemoteEnd: PromiseStruct {
|
|
typealias Result = SignalMutPointerFakeChatRemoteEnd
|
|
}
|
|
|
|
#endif
|