From 5a17357582f1f1c1c4fd602e060f8bd4eb85605c Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Tue, 23 Jul 2024 08:49:35 +0200 Subject: [PATCH] WIP: Setup Ouisync backend in the extension --- .../dart/lib/internal/message_matcher.dart | 3 +- bindings/swift/OuisyncLib/Package.swift | 6 +- .../Sources/OuisyncLib/OuisyncFFI.swift | 140 ++++++++++++++++++ .../Sources/OuisyncLib/OuisyncMessage.swift | 12 +- .../Sources/OuisyncLib/OuisyncSession.swift | 55 ++++++- .../OuisyncLibFFI/include/OuisyncFFI.h | 23 +++ .../Sources/OuisyncLibFFI/ouisyncffi.c | 1 + 7 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncFFI.swift create mode 100644 bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/include/OuisyncFFI.h create mode 100644 bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/ouisyncffi.c diff --git a/bindings/dart/lib/internal/message_matcher.dart b/bindings/dart/lib/internal/message_matcher.dart index 54f273a55..ddcef61ed 100644 --- a/bindings/dart/lib/internal/message_matcher.dart +++ b/bindings/dart/lib/internal/message_matcher.dart @@ -65,7 +65,7 @@ class MessageMatcher { final message = deserialize(bytes.sublist(8)); // DEBUG - //print('recv: id: $id, message: $message'); + print('recv: id: $id, message: $message'); if (message is! Map) { return; @@ -103,6 +103,7 @@ class MessageMatcher { } void _handleResponseSuccess(Completer completer, Object? payload) { + print(">>>>>>>>>>>>>>>>> received $payload"); if (payload == "none") { completer.complete(null); return; diff --git a/bindings/swift/OuisyncLib/Package.swift b/bindings/swift/OuisyncLib/Package.swift index 4e9d0d70b..5f26dd94a 100644 --- a/bindings/swift/OuisyncLib/Package.swift +++ b/bindings/swift/OuisyncLib/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -20,7 +20,9 @@ let package = Package( // Targets can depend on other targets in this package and products from dependencies. .target( name: "OuisyncLib", - dependencies: [.product(name:"MessagePack", package: "MessagePack.swift")]), + dependencies: [.product(name:"MessagePack", package: "MessagePack.swift"), "OuisyncLibFFI"] + ), + .target(name: "OuisyncLibFFI", dependencies: []), .testTarget( name: "OuisyncLibTests", dependencies: ["OuisyncLib"]), diff --git a/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncFFI.swift b/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncFFI.swift new file mode 100644 index 000000000..69ad065f9 --- /dev/null +++ b/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncFFI.swift @@ -0,0 +1,140 @@ +// +// File.swift +// +// +// Created by Peter Jankuliak on 19/07/2024. +// + +import Foundation +import OuisyncLibFFI + +typealias FFISessionKind = UInt8 +typealias FFIContext = UnsafeRawPointer +typealias FFICallback = @convention(c) (FFIContext?, UnsafePointer, CUnsignedLongLong) -> Void; +typealias FFISessionCreate = @convention(c) (FFISessionKind, UnsafePointer, UnsafePointer, UnsafeRawPointer?, FFICallback) -> SessionCreateResult; +typealias FFISessionGrab = @convention(c) (UnsafeRawPointer?, FFICallback) -> SessionCreateResult; +typealias FFISessionClose = @convention(c) (SessionHandle, FFIContext?, FFICallback) -> Void; +typealias FFISessionChannelSend = @convention(c) (SessionHandle, UnsafeRawPointer, UInt64) -> Void; + +class SessionCreateError : Error, CustomStringConvertible { + let message: String + init(_ message: String) { self.message = message } + var description: String { message } +} + +public class OuisyncFFI { + let handle: UnsafeMutableRawPointer + let ffiSessionGrab: FFISessionGrab + let ffiSessionCreate: FFISessionCreate + let ffiSessionChannelSend: FFISessionChannelSend + let ffiSessionClose: FFISessionClose + let sessionKindShared: FFISessionKind = 0; + + public init() { + handle = dlopen("libouisync_ffi.dylib", RTLD_NOW)! + ffiSessionGrab = unsafeBitCast(dlsym(handle, "session_grab"), to: FFISessionGrab.self) + ffiSessionChannelSend = unsafeBitCast(dlsym(handle, "session_channel_send"), to: FFISessionChannelSend.self) + ffiSessionClose = unsafeBitCast(dlsym(handle, "session_close"), to: FFISessionClose.self) + ffiSessionCreate = unsafeBitCast(dlsym(handle, "session_create"), to: FFISessionCreate.self) + } + + // Blocks until Dart creates a session, then returns it. + func waitForSession(_ context: UnsafeRawPointer, _ callback: FFICallback) async throws -> SessionHandle { + // TODO: Might be worth change the ffi function to call a callback when the session becomes created instead of bussy sleeping. + var elapsed: UInt64 = 0; + while true { + let result = ffiSessionGrab(context, callback) + if result.errorCode == 0 { + NSLog("๐Ÿ˜€ Got Ouisync session"); + return result.session + } + NSLog("๐Ÿคจ Ouisync session not yet ready. Code: \(result.errorCode) Message:\(String(cString: result.errorMessage!))"); + + let millisecond: UInt64 = 1_000_000 + let second: UInt64 = 1000 * millisecond + + var timeout = 200 * millisecond + + if elapsed > 10 * second { + timeout = second + } + + try await Task.sleep(nanoseconds: timeout) + elapsed += timeout; + } + } + + func channelSend(_ session: SessionHandle, _ data: [UInt8]) { + let count = data.count; + data.withUnsafeBufferPointer({ maybePointer in + if let pointer = maybePointer.baseAddress { + ffiSessionChannelSend(session, pointer, UInt64(count)) + } + }) + } + + func closeSession(_ session: SessionHandle) async { + typealias C = CheckedContinuation + + class Context { + let session: SessionHandle + let continuation: C + init(_ session: SessionHandle, _ continuation: C) { + self.session = session + self.continuation = continuation + } + } + + await withCheckedContinuation(function: "FFI.closeSession", { continuation in + let context = Self.toRetainedPtr(obj: Context(session, continuation)) + let callback: FFICallback = { context, dataPointer, size in + let context: Context = OuisyncFFI.fromRetainedPtr(ptr: context!) + context.continuation.resume() + } + ffiSessionClose(session, context, callback) + }) + } + + // Retained pointers have their reference counter incremented by 1. + // https://stackoverflow.com/a/33310021/273348 + static func toUnretainedPtr(obj : T) -> UnsafeRawPointer { + return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaque()) + } + + static func fromUnretainedPtr(ptr : UnsafeRawPointer) -> T { + return Unmanaged.fromOpaque(ptr).takeUnretainedValue() + } + + static func toRetainedPtr(obj : T) -> UnsafeRawPointer { + return UnsafeRawPointer(Unmanaged.passRetained(obj).toOpaque()) + } + + static func fromRetainedPtr(ptr : UnsafeRawPointer) -> T { + return Unmanaged.fromOpaque(ptr).takeRetainedValue() + } +} + +// --------------------------------------------------------------------------------------- + +typealias Rx = AsyncStream<[UInt8]> +typealias Tx = AsyncStream<[UInt8]>.Continuation + +class Wrap { + let inner: T + init(_ inner: T) { self.inner = inner } +} + +class Channel { + let rx: Rx + let tx: Tx + + init(_ rx: Rx, _ tx: Tx) { self.rx = rx; self.tx = tx } +} + +func makeStream() -> (Rx, Tx) { + var continuation: Rx.Continuation! + let stream = Rx() { continuation = $0 } + return (stream, continuation!) +} + +// --------------------------------------------------------------------------------------- diff --git a/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncMessage.swift b/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncMessage.swift index d6590dc85..da156750a 100644 --- a/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncMessage.swift +++ b/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncMessage.swift @@ -190,7 +190,7 @@ public class IncomingMessage { // TODO: Rename to ResponseMessage let body: MessagePackValue; switch payload { case .response(let response): - body = MessagePackValue.map(["success": response.value]) + body = MessagePackValue.map(["success": Self.responseValue(response.value)]) case .notification(let notification): body = MessagePackValue.map(["notification": notification.value]) case .error(let error): @@ -201,6 +201,16 @@ public class IncomingMessage { // TODO: Rename to ResponseMessage return message } + static func responseValue(_ value: MessagePackValue) -> MessagePackValue { + switch value { + case .nil: return .string("none") + default: + // The flutter code doesn't read the key which is supposed to be a type, + // would still be nice to have a proper mapping. + return .map(["todo-type": value]) + } + } + public static func deserialize(_ bytes: [UInt8]) -> IncomingMessage? { guard let (id, data) = readMessageId(bytes) else { return nil diff --git a/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncSession.swift b/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncSession.swift index 798d26974..741f9a65b 100644 --- a/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncSession.swift +++ b/bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncSession.swift @@ -6,17 +6,54 @@ import Foundation import MessagePack +import OuisyncLibFFI public class OuisyncSession { - // Used to send and receive messages from the Ouisync library - let librarySender: OuisyncLibrarySenderProtocol + var sessionHandle: SessionHandle + let ffi: OuisyncFFI var nextMessageId: MessageId = 0 var pendingResponses: [MessageId: CheckedContinuation] = [:] var notificationSubscriptions: NotificationStream.State = NotificationStream.State() - public init(_ libraryClient: OuisyncLibrarySenderProtocol) { - self.librarySender = libraryClient + fileprivate init(_ sessionHandle: SessionHandle, _ ffi: OuisyncFFI) { + self.sessionHandle = sessionHandle + self.ffi = ffi + } + + public static func create(_ configPath: String, _ logPath: String, _ ffi: OuisyncFFI) throws -> OuisyncSession { + let session = OuisyncSession(0, ffi) + + let callback: FFICallback = { context, dataPointer, size in + let session: OuisyncSession = OuisyncFFI.fromUnretainedPtr(ptr: context!) + let data = Array(UnsafeBufferPointer(start: dataPointer, count: Int(exactly: size)!)) + session.onReceiveDataFromOuisyncLib(data) + } + + let result = ffi.ffiSessionCreate(ffi.sessionKindShared, configPath, logPath, OuisyncFFI.toUnretainedPtr(obj: session), callback); + + if result.errorCode != 0 { + throw SessionCreateError("Failed to create session, code:\(result.errorCode), message:\(result.errorMessage!)") + } + + session.sessionHandle = result.session + return session + } + + // Can be called from a separate thread. + public func invoke(_ requestMsg: OutgoingMessage) async -> IncomingMessage { + let responsePayload: IncomingPayload + + NSLog("AAAAAA \(requestMsg.request.functionName)") + do { + responsePayload = .response(try await sendRequest(requestMsg.request)) + } catch let e as OuisyncError { + responsePayload = .error(e) + } catch let e { + fatalError("Unhandled exception in OuisyncSession.invoke: \(e)") + } + + return IncomingMessage(requestMsg.messageId, responsePayload) } public func listRepositories() async throws -> [OuisyncRepository] { @@ -35,6 +72,7 @@ public class OuisyncSession { return NotificationStream(subscriptionId, notificationSubscriptions) } + // Can be called from a separate thread. internal func sendRequest(_ request: MessageRequest) async throws -> Response { let messageId = generateMessageId() @@ -50,6 +88,7 @@ public class OuisyncSession { return try await onResponse } + // Can be called from a separate thread. fileprivate func generateMessageId() -> MessageId { synchronized(self) { let messageId = nextMessageId @@ -59,7 +98,13 @@ public class OuisyncSession { } fileprivate func sendDataToOuisyncLib(_ data: [UInt8]) { - librarySender.sendDataToOuisyncLib(data); + //librarySender.sendDataToOuisyncLib(data); + let count = data.count; + data.withUnsafeBufferPointer({ maybePointer in + if let pointer = maybePointer.baseAddress { + ffi.ffiSessionChannelSend(sessionHandle, pointer, UInt64(count)) + } + }) } // Use this function to pass data from the backend. diff --git a/bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/include/OuisyncFFI.h b/bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/include/OuisyncFFI.h new file mode 100644 index 000000000..3bd735a0b --- /dev/null +++ b/bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/include/OuisyncFFI.h @@ -0,0 +1,23 @@ +// +// FileProviderProxy.h +// Runner +// +// Created by Peter Jankuliak on 03/04/2024. +// + +#ifndef OuisyncFFI_h +#define OuisyncFFI_h + +#include + +typedef uint64_t SessionHandle; + +struct SessionCreateResult { + SessionHandle session; + uint16_t errorCode; + const char* errorMessage; +}; + +typedef struct SessionCreateResult SessionCreateResult; + +#endif /* OuisyncFFI_h */ diff --git a/bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/ouisyncffi.c b/bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/ouisyncffi.c new file mode 100644 index 000000000..f27f00255 --- /dev/null +++ b/bindings/swift/OuisyncLib/Sources/OuisyncLibFFI/ouisyncffi.c @@ -0,0 +1 @@ +// Blank file because xcode complains if there is nothing to compile.