initial commit
This commit is contained in:
10
CheetahIPC/CheetahIPC.h
Normal file
10
CheetahIPC/CheetahIPC.h
Normal file
@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for CheetahIPC.
|
||||
FOUNDATION_EXPORT double CheetahIPCVersionNumber;
|
||||
|
||||
//! Project version string for CheetahIPC.
|
||||
FOUNDATION_EXPORT const unsigned char CheetahIPCVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <CheetahIPC/PublicHeader.h>
|
||||
|
||||
67
CheetahIPC/Client.swift
Normal file
67
CheetahIPC/Client.swift
Normal file
@ -0,0 +1,67 @@
|
||||
import CoreFoundation
|
||||
import UserNotifications
|
||||
|
||||
public enum IPCClientError: Error {
|
||||
case createRemoteFailure
|
||||
case sendRequestFailure(Int32)
|
||||
}
|
||||
|
||||
public class IPCClient {
|
||||
let remote: CFMessagePort
|
||||
|
||||
public init(messagePortName: String) throws {
|
||||
if let remote = CFMessagePortCreateRemote(nil, messagePortName as CFString) {
|
||||
self.remote = remote
|
||||
} else {
|
||||
throw IPCClientError.createRemoteFailure
|
||||
}
|
||||
}
|
||||
|
||||
public func sendRequest(msgid: Int32, data: Data) throws -> Data? {
|
||||
var responseData: Unmanaged<CFData>? = nil
|
||||
|
||||
let result = CFMessagePortSendRequest(
|
||||
remote,
|
||||
msgid,
|
||||
data as CFData,
|
||||
1.0, // sendTimeout
|
||||
1.0, // rcvTimeout
|
||||
CFRunLoopMode.defaultMode.rawValue,
|
||||
&responseData)
|
||||
|
||||
if result == kCFMessagePortSuccess {
|
||||
return responseData?.takeRetainedValue() as Data?
|
||||
} else {
|
||||
throw IPCClientError.sendRequestFailure(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension IPCClient {
|
||||
func encode(_ object: Encodable) throws -> Data {
|
||||
return try PropertyListEncoder().encode(object)
|
||||
}
|
||||
|
||||
func encode(_ object: NSCoding) throws -> Data {
|
||||
return try NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true)
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ data: Data) throws -> T {
|
||||
return try PropertyListDecoder().decode(T.self, from: data)
|
||||
}
|
||||
|
||||
func decode<T>(_ data: Data) throws -> T? where T: NSObject, T: NSCoding {
|
||||
return try NSKeyedUnarchiver.unarchivedObject(ofClass: T.self, from: data)
|
||||
}
|
||||
|
||||
func sendMessage(id: any RawRepresentable<Int32>, withObject object: NSCoding) throws {
|
||||
_ = try sendRequest(msgid: id.rawValue, data: encode(object))
|
||||
}
|
||||
|
||||
func sendMessage<T: Decodable>(id: any RawRepresentable<Int32>, withObject object: NSCoding) throws -> T? {
|
||||
guard let resultData = try sendRequest(msgid: id.rawValue, data: encode(object)) else {
|
||||
return nil
|
||||
}
|
||||
return try decode(resultData)
|
||||
}
|
||||
}
|
||||
17
CheetahIPC/Messages.swift
Normal file
17
CheetahIPC/Messages.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public enum MessagePortName: String {
|
||||
case browserExtensionServer = "org.phrack.Cheetah.BrowserExtensionServer"
|
||||
}
|
||||
|
||||
public enum IPCMessage: Int32 {
|
||||
case browserExtensionMessage = 1
|
||||
}
|
||||
|
||||
public struct BrowserExtensionMessage: Codable {
|
||||
public var mode: String
|
||||
public var files = [String: String]()
|
||||
public var logs = [String: String]()
|
||||
public var navigationStart: Int
|
||||
}
|
||||
95
CheetahIPC/Server.swift
Normal file
95
CheetahIPC/Server.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import CoreFoundation
|
||||
|
||||
func serverCallback(local: CFMessagePort?, msgid: Int32, data: CFData?, info: UnsafeMutableRawPointer?) -> Unmanaged<CFData>? {
|
||||
let server = Unmanaged<IPCServer>.fromOpaque(info!).takeUnretainedValue()
|
||||
let responseData = server.delegate?.handleMessageWithID(msgid, data: data! as Data)
|
||||
if let responseData = responseData as? NSData,
|
||||
let cfdata = CFDataCreate(nil, responseData.bytes, responseData.length) {
|
||||
return Unmanaged.passRetained(cfdata)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public protocol IPCServerDelegate: AnyObject {
|
||||
func handleMessageWithID(_ msgid: Int32, data: Data) -> Data?
|
||||
}
|
||||
|
||||
open class NSCodingHandler<RequestObject>: NSObject, IPCServerDelegate {
|
||||
public typealias Handler = (RequestObject?) -> Encodable?
|
||||
|
||||
public let messageID: Int32
|
||||
public var handler: Handler?
|
||||
|
||||
public init(respondsTo id: any RawRepresentable<Int32>, _ handler: Handler? = nil) {
|
||||
self.messageID = id.rawValue
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func handleMessageWithID(_ msgid: Int32, data: Data) -> Data? {
|
||||
let object = NSKeyedUnarchiver.unarchiveObject(with: data) as? RequestObject
|
||||
if let handler = handler, let result = handler(object) {
|
||||
return try? NSKeyedArchiver.archivedData(withRootObject: result, requiringSecureCoding: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class JSONHandler<RequestObject: Decodable>: IPCServerDelegate {
|
||||
public typealias Handler = (RequestObject?) -> Encodable?
|
||||
|
||||
public let messageID: Int32
|
||||
public var handler: Handler?
|
||||
|
||||
public init(respondsTo id: any RawRepresentable<Int32>, _ handler: Handler? = nil) {
|
||||
self.messageID = id.rawValue
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func handleMessageWithID(_ msgid: Int32, data: Data) -> Data? {
|
||||
let object = try? JSONDecoder().decode(RequestObject.self, from: data)
|
||||
if let object = object, let handler = handler, let result = handler(object) {
|
||||
return try? NSKeyedArchiver.archivedData(withRootObject: result, requiringSecureCoding: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IPCServer: NSObject {
|
||||
public weak var delegate: IPCServerDelegate?
|
||||
|
||||
/// Create the local message port then register an input source for it
|
||||
public func addSourceForNewLocalMessagePort(name: String, toRunLoop runLoop: CFRunLoop!) {
|
||||
if let messagePort = createMessagePort(name: name) {
|
||||
addSource(messagePort: messagePort, toRunLoop: runLoop)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a local message port with the specified name
|
||||
///
|
||||
/// Incoming messages will be routed to this object's handleMessageWithID(,data:) method.
|
||||
func createMessagePort(name: String) -> CFMessagePort? {
|
||||
var context = CFMessagePortContext(
|
||||
version: 0,
|
||||
info: Unmanaged.passUnretained(self).toOpaque(),
|
||||
retain: nil,
|
||||
release: nil,
|
||||
copyDescription: nil)
|
||||
var shouldFreeInfo: DarwinBoolean = false
|
||||
|
||||
return CFMessagePortCreateLocal(
|
||||
nil,
|
||||
name as CFString,
|
||||
serverCallback,
|
||||
&context,
|
||||
&shouldFreeInfo)
|
||||
}
|
||||
|
||||
/// Create an input source for the specified message port and add it to the specified run loop
|
||||
func addSource(messagePort: CFMessagePort, toRunLoop runLoop: CFRunLoop) {
|
||||
let source = CFMessagePortCreateRunLoopSource(nil, messagePort, 0)
|
||||
CFRunLoopAddSource(runLoop, source, CFRunLoopMode.commonModes)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user