mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-11 15:53:30 +01:00
spm: complex refactor, document of package
This commit is contained in:
10
SideStoreApp/Sources/SideStoreCore/AltConstants.swift
Normal file
10
SideStoreApp/Sources/SideStoreCore/AltConstants.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// AltConstants.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Mattiello on 2/28/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public let ALTDeviceListeningSocket: UInt16 = 28151
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// CFNotificationName+SideStore.swift
|
||||
// SideKit
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public let ALTWiredServerConnectionAvailableRequest = CFNotificationName("io.altstore.Request.WiredServerConnectionAvailable" as CFString)
|
||||
public let ALTWiredServerConnectionAvailableResponse = CFNotificationName("io.altstore.Response.WiredServerConnectionAvailable" as CFString)
|
||||
public let ALTWiredServerConnectionStartRequest = CFNotificationName("io.altstore.Request.WiredServerConnectionStart" as CFString)
|
||||
102
SideStoreApp/Sources/SideStoreCore/Connections/Connection.swift
Normal file
102
SideStoreApp/Sources/SideStoreCore/Connections/Connection.swift
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Connection.swift
|
||||
// AltKit
|
||||
//
|
||||
// Created by Riley Testut on 6/1/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import SideKit
|
||||
import os.log
|
||||
|
||||
public protocol SideConnection: Connection {
|
||||
func __send(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void)
|
||||
func __receiveData(expectedSize: Int, completionHandler: @escaping (Data?, Error?) -> Void)
|
||||
}
|
||||
|
||||
public extension SideConnection {
|
||||
func send(_ data: Data, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void) {
|
||||
__send(data) { success, error in
|
||||
let result = Result(success, error).mapError { (failure: Error) -> ALTServerError in
|
||||
guard let nwError = failure as? NWError else { return ALTServerError(failure) }
|
||||
return ALTServerError.lostConnection(underlyingError: nwError)
|
||||
}
|
||||
|
||||
completionHandler(result)
|
||||
}
|
||||
}
|
||||
|
||||
func receiveData(expectedSize: Int, completionHandler: @escaping (Result<Data, ALTServerError>) -> Void) {
|
||||
__receiveData(expectedSize: expectedSize) { data, error in
|
||||
let result = Result(data, error).mapError { (failure: Error) -> ALTServerError in
|
||||
guard let nwError = failure as? NWError else { return ALTServerError(failure) }
|
||||
return ALTServerError.lostConnection(underlyingError: nwError)
|
||||
}
|
||||
|
||||
completionHandler(result)
|
||||
}
|
||||
}
|
||||
|
||||
func send<T: Encodable>(_ response: T, shouldDisconnect: Bool = false, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void) {
|
||||
func finish(_ result: Result<Void, ALTServerError>) {
|
||||
completionHandler(result)
|
||||
|
||||
if shouldDisconnect {
|
||||
// Add short delay to prevent us from dropping connection too quickly.
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||
self.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try JSONEncoder().encode(response)
|
||||
let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) }
|
||||
|
||||
send(responseSize) { result in
|
||||
switch result {
|
||||
case let .failure(error): finish(.failure(error))
|
||||
case .success:
|
||||
self.send(data) { result in
|
||||
switch result {
|
||||
case let .failure(error): finish(.failure(error))
|
||||
case .success: finish(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
finish(.failure(.invalidResponse(underlyingError: error)))
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequest(completionHandler: @escaping (Result<ServerRequest, ALTServerError>) -> Void) {
|
||||
let size = MemoryLayout<Int32>.size
|
||||
|
||||
os_log("Receiving request size from connection: %@", type: .info , String(describing: self))
|
||||
receiveData(expectedSize: size) { result in
|
||||
do {
|
||||
let data = try result.get()
|
||||
|
||||
let expectedSize = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
|
||||
print("Receiving request from connection: \(self)... (\(expectedSize) bytes)")
|
||||
|
||||
self.receiveData(expectedSize: expectedSize) { result in
|
||||
do {
|
||||
let data = try result.get()
|
||||
let request = try JSONDecoder().decode(ServerRequest.self, from: data)
|
||||
|
||||
os_log("Received request: %@", type: .info , String(describing: request))
|
||||
completionHandler(.success(request))
|
||||
} catch {
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// ConnectionManager.swift
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 5/23/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import SideKit
|
||||
import os.log
|
||||
|
||||
public protocol RequestHandler {
|
||||
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
|
||||
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result<InstallationProgressResponse, Error>) -> Void)
|
||||
|
||||
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
|
||||
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
|
||||
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
|
||||
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
|
||||
|
||||
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
|
||||
|
||||
func handleEnableUnsignedCodeExecutionRequest(_ request: EnableUnsignedCodeExecutionRequest, for connection: Connection, completionHandler: @escaping (Result<EnableUnsignedCodeExecutionResponse, Error>) -> Void)
|
||||
}
|
||||
|
||||
public protocol ConnectionHandler: AnyObject {
|
||||
associatedtype ConnectionType = Connection
|
||||
var connectionHandler: ((ConnectionType) -> Void)? { get set }
|
||||
var disconnectionHandler: ((ConnectionType) -> Void)? { get set }
|
||||
|
||||
func startListening()
|
||||
func stopListening()
|
||||
}
|
||||
|
||||
public class ConnectionManager<RequestHandlerType: RequestHandler, ConnectionType: NetworkConnection & AnyObject, ConnectionHandlerType: ConnectionHandler> where ConnectionHandlerType.ConnectionType == ConnectionType {
|
||||
public let requestHandler: RequestHandlerType
|
||||
public let connectionHandlers: [ConnectionHandlerType]
|
||||
|
||||
public var isStarted = false
|
||||
|
||||
private var connections = [ConnectionType]()
|
||||
private let connectionsLock = NSLock()
|
||||
|
||||
public init(requestHandler: RequestHandlerType, connectionHandlers: [ConnectionHandlerType]) {
|
||||
self.requestHandler = requestHandler
|
||||
self.connectionHandlers = connectionHandlers
|
||||
|
||||
for handler in connectionHandlers {
|
||||
handler.connectionHandler = { [weak self] connection in
|
||||
self?.prepare(connection)
|
||||
}
|
||||
|
||||
handler.disconnectionHandler = { [weak self] connection in
|
||||
self?.disconnect(connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func start() {
|
||||
guard !isStarted else { return }
|
||||
|
||||
for connectionHandler in connectionHandlers {
|
||||
connectionHandler.startListening()
|
||||
}
|
||||
|
||||
isStarted = true
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
guard isStarted else { return }
|
||||
|
||||
for connectionHandler in connectionHandlers {
|
||||
connectionHandler.stopListening()
|
||||
}
|
||||
|
||||
isStarted = false
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConnectionManager {
|
||||
func prepare(_ connection: ConnectionType) {
|
||||
connectionsLock.lock()
|
||||
defer { self.connectionsLock.unlock() }
|
||||
|
||||
guard !connections.contains(where: { $0 === connection }) else { return }
|
||||
connections.append(connection)
|
||||
|
||||
handleRequest(for: connection)
|
||||
}
|
||||
|
||||
func disconnect(_ connection: ConnectionType) {
|
||||
connectionsLock.lock()
|
||||
defer { self.connectionsLock.unlock() }
|
||||
|
||||
guard let index = connections.firstIndex(where: { $0 === connection }) else { return }
|
||||
connections.remove(at: index)
|
||||
}
|
||||
|
||||
func handleRequest(for connection: ConnectionType) {
|
||||
func finish<T: ServerMessageProtocol>(_ result: Result<T, Error>) {
|
||||
do {
|
||||
let response = try result.get()
|
||||
connection.send(response, shouldDisconnect: true) { result in
|
||||
os_log("Sent response %@ with result: %@", type: .error , response.identifier, String(describing: result))
|
||||
}
|
||||
} catch {
|
||||
let response = ErrorResponse(error: ALTServerError(error))
|
||||
connection.send(response, shouldDisconnect: true) { result in
|
||||
os_log("Sent error response %@ with result: %@", type: .error , response.error.localizedDescription, String(describing: result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connection.receiveRequest { result in
|
||||
os_log("Received request with result: %@", type: .info, String(describing: result))
|
||||
|
||||
switch result {
|
||||
case let .failure(error): finish(Result<ErrorResponse, Error>.failure(error))
|
||||
|
||||
case let .success(.anisetteData(request)):
|
||||
self.requestHandler.handleAnisetteDataRequest(request, for: connection) { result in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case let .success(.prepareApp(request)):
|
||||
self.requestHandler.handlePrepareAppRequest(request, for: connection) { result in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case .success(.beginInstallation): break
|
||||
|
||||
case let .success(.installProvisioningProfiles(request)):
|
||||
self.requestHandler.handleInstallProvisioningProfilesRequest(request, for: connection) { result in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case let .success(.removeProvisioningProfiles(request)):
|
||||
self.requestHandler.handleRemoveProvisioningProfilesRequest(request, for: connection) { result in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case let .success(.removeApp(request)):
|
||||
self.requestHandler.handleRemoveAppRequest(request, for: connection) { result in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case let .success(.enableUnsignedCodeExecution(request)):
|
||||
self.requestHandler.handleEnableUnsignedCodeExecutionRequest(request, for: connection) { result in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case .success(.unknown):
|
||||
finish(Result<ErrorResponse, Error>.failure(ALTServerError.unknownRequest))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// NetworkConnection.swift
|
||||
// AltKit
|
||||
//
|
||||
// Created by Riley Testut on 6/1/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import SideKit
|
||||
|
||||
public class NetworkConnection: NSObject, SideConnection {
|
||||
public let nwConnection: NWConnection
|
||||
|
||||
public init(_ nwConnection: NWConnection) {
|
||||
self.nwConnection = nwConnection
|
||||
}
|
||||
|
||||
public func __send(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) {
|
||||
nwConnection.send(content: data, completion: .contentProcessed { error in
|
||||
completionHandler(error == nil, error)
|
||||
})
|
||||
}
|
||||
|
||||
public func __receiveData(expectedSize: Int, completionHandler: @escaping (Data?, Error?) -> Void) {
|
||||
nwConnection.receive(minimumIncompleteLength: expectedSize, maximumLength: expectedSize) { data, _, _, error in
|
||||
guard data != nil || error != nil else {
|
||||
return completionHandler(nil, ALTServerError.lostConnection(underlyingError: error))
|
||||
}
|
||||
|
||||
completionHandler(data, error)
|
||||
}
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
switch nwConnection.state {
|
||||
case .cancelled, .failed: break
|
||||
default: nwConnection.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension NetworkConnection {
|
||||
override var description: String {
|
||||
"\(nwConnection.endpoint) (Network)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
//
|
||||
// ServerProtocol.swift
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 5/24/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AltSign
|
||||
import SideKit
|
||||
|
||||
public let ALTServerServiceType = "_altserver._tcp"
|
||||
|
||||
protocol ServerMessageProtocol: Codable
|
||||
{
|
||||
var version: Int { get }
|
||||
var identifier: String { get }
|
||||
}
|
||||
|
||||
public enum ServerRequest: Decodable
|
||||
{
|
||||
case anisetteData(AnisetteDataRequest)
|
||||
case prepareApp(PrepareAppRequest)
|
||||
case beginInstallation(BeginInstallationRequest)
|
||||
case installProvisioningProfiles(InstallProvisioningProfilesRequest)
|
||||
case removeProvisioningProfiles(RemoveProvisioningProfilesRequest)
|
||||
case removeApp(RemoveAppRequest)
|
||||
case enableUnsignedCodeExecution(EnableUnsignedCodeExecutionRequest)
|
||||
case unknown(identifier: String, version: Int)
|
||||
|
||||
var identifier: String {
|
||||
switch self
|
||||
{
|
||||
case .anisetteData(let request): return request.identifier
|
||||
case .prepareApp(let request): return request.identifier
|
||||
case .beginInstallation(let request): return request.identifier
|
||||
case .installProvisioningProfiles(let request): return request.identifier
|
||||
case .removeProvisioningProfiles(let request): return request.identifier
|
||||
case .removeApp(let request): return request.identifier
|
||||
case .enableUnsignedCodeExecution(let request): return request.identifier
|
||||
case .unknown(let identifier, _): return identifier
|
||||
}
|
||||
}
|
||||
|
||||
var version: Int {
|
||||
switch self
|
||||
{
|
||||
case .anisetteData(let request): return request.version
|
||||
case .prepareApp(let request): return request.version
|
||||
case .beginInstallation(let request): return request.version
|
||||
case .installProvisioningProfiles(let request): return request.version
|
||||
case .removeProvisioningProfiles(let request): return request.version
|
||||
case .removeApp(let request): return request.version
|
||||
case .enableUnsignedCodeExecution(let request): return request.version
|
||||
case .unknown(_, let version): return version
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case identifier
|
||||
case version
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let version = try container.decode(Int.self, forKey: .version)
|
||||
|
||||
let identifier = try container.decode(String.self, forKey: .identifier)
|
||||
switch identifier
|
||||
{
|
||||
case "AnisetteDataRequest":
|
||||
let request = try AnisetteDataRequest(from: decoder)
|
||||
self = .anisetteData(request)
|
||||
|
||||
case "PrepareAppRequest":
|
||||
let request = try PrepareAppRequest(from: decoder)
|
||||
self = .prepareApp(request)
|
||||
|
||||
case "BeginInstallationRequest":
|
||||
let request = try BeginInstallationRequest(from: decoder)
|
||||
self = .beginInstallation(request)
|
||||
|
||||
case "InstallProvisioningProfilesRequest":
|
||||
let request = try InstallProvisioningProfilesRequest(from: decoder)
|
||||
self = .installProvisioningProfiles(request)
|
||||
|
||||
case "RemoveProvisioningProfilesRequest":
|
||||
let request = try RemoveProvisioningProfilesRequest(from: decoder)
|
||||
self = .removeProvisioningProfiles(request)
|
||||
|
||||
case "RemoveAppRequest":
|
||||
let request = try RemoveAppRequest(from: decoder)
|
||||
self = .removeApp(request)
|
||||
|
||||
case "EnableUnsignedCodeExecutionRequest":
|
||||
let request = try EnableUnsignedCodeExecutionRequest(from: decoder)
|
||||
self = .enableUnsignedCodeExecution(request)
|
||||
|
||||
default:
|
||||
self = .unknown(identifier: identifier, version: version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServerResponse: Decodable
|
||||
{
|
||||
case anisetteData(AnisetteDataResponse)
|
||||
case installationProgress(InstallationProgressResponse)
|
||||
case installProvisioningProfiles(InstallProvisioningProfilesResponse)
|
||||
case removeProvisioningProfiles(RemoveProvisioningProfilesResponse)
|
||||
case removeApp(RemoveAppResponse)
|
||||
case enableUnsignedCodeExecution(EnableUnsignedCodeExecutionResponse)
|
||||
case error(ErrorResponse)
|
||||
case unknown(identifier: String, version: Int)
|
||||
|
||||
var identifier: String {
|
||||
switch self
|
||||
{
|
||||
case .anisetteData(let response): return response.identifier
|
||||
case .installationProgress(let response): return response.identifier
|
||||
case .installProvisioningProfiles(let response): return response.identifier
|
||||
case .removeProvisioningProfiles(let response): return response.identifier
|
||||
case .removeApp(let response): return response.identifier
|
||||
case .enableUnsignedCodeExecution(let response): return response.identifier
|
||||
case .error(let response): return response.identifier
|
||||
case .unknown(let identifier, _): return identifier
|
||||
}
|
||||
}
|
||||
|
||||
var version: Int {
|
||||
switch self
|
||||
{
|
||||
case .anisetteData(let response): return response.version
|
||||
case .installationProgress(let response): return response.version
|
||||
case .installProvisioningProfiles(let response): return response.version
|
||||
case .removeProvisioningProfiles(let response): return response.version
|
||||
case .removeApp(let response): return response.version
|
||||
case .enableUnsignedCodeExecution(let response): return response.version
|
||||
case .error(let response): return response.version
|
||||
case .unknown(_, let version): return version
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case identifier
|
||||
case version
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let version = try container.decode(Int.self, forKey: .version)
|
||||
|
||||
let identifier = try container.decode(String.self, forKey: .identifier)
|
||||
switch identifier
|
||||
{
|
||||
case "AnisetteDataResponse":
|
||||
let response = try AnisetteDataResponse(from: decoder)
|
||||
self = .anisetteData(response)
|
||||
|
||||
case "InstallationProgressResponse":
|
||||
let response = try InstallationProgressResponse(from: decoder)
|
||||
self = .installationProgress(response)
|
||||
|
||||
case "InstallProvisioningProfilesResponse":
|
||||
let response = try InstallProvisioningProfilesResponse(from: decoder)
|
||||
self = .installProvisioningProfiles(response)
|
||||
|
||||
case "RemoveProvisioningProfilesResponse":
|
||||
let response = try RemoveProvisioningProfilesResponse(from: decoder)
|
||||
self = .removeProvisioningProfiles(response)
|
||||
|
||||
case "RemoveAppResponse":
|
||||
let response = try RemoveAppResponse(from: decoder)
|
||||
self = .removeApp(response)
|
||||
|
||||
case "EnableUnsignedCodeExecutionResponse":
|
||||
let response = try EnableUnsignedCodeExecutionResponse(from: decoder)
|
||||
self = .enableUnsignedCodeExecution(response)
|
||||
|
||||
case "ErrorResponse":
|
||||
let response = try ErrorResponse(from: decoder)
|
||||
self = .error(response)
|
||||
|
||||
default:
|
||||
self = .unknown(identifier: identifier, version: version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// _Don't_ provide generic SuccessResponse, as that would prevent us
|
||||
// from easily changing response format for a request in the future.
|
||||
public struct ErrorResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 2
|
||||
public var identifier = "ErrorResponse"
|
||||
|
||||
public var error: ALTServerError {
|
||||
return (self.serverError?.underlyingError as? ALTServerError)!
|
||||
}
|
||||
private var serverError: ALTServerError?
|
||||
|
||||
// Legacy (v1)
|
||||
private var errorCode: ALTServerError.RawValue
|
||||
|
||||
public init(error: ALTServerError)
|
||||
{
|
||||
self.serverError = ALTServerError(error)
|
||||
self.errorCode = error.errorCode
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnisetteDataRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "AnisetteDataRequest"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnisetteDataResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "AnisetteDataResponse"
|
||||
|
||||
public var anisetteData: ALTAnisetteData
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case identifier
|
||||
case version
|
||||
case anisetteData
|
||||
}
|
||||
|
||||
public init(anisetteData: ALTAnisetteData)
|
||||
{
|
||||
self.anisetteData = anisetteData
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.version = try container.decode(Int.self, forKey: .version)
|
||||
self.identifier = try container.decode(String.self, forKey: .identifier)
|
||||
|
||||
let json = try container.decode([String: String].self, forKey: .anisetteData)
|
||||
|
||||
// if let anisetteData = ALTAnisetteData() //(json: json)
|
||||
// {
|
||||
// self.anisetteData = anisetteData
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
throw DecodingError.dataCorruptedError(forKey: CodingKeys.anisetteData, in: container, debugDescription: "Couuld not parse anisette data from JSON")
|
||||
// }
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws
|
||||
{
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.version, forKey: .version)
|
||||
try container.encode(self.identifier, forKey: .identifier)
|
||||
|
||||
// let json = self.anisetteData.json()
|
||||
// try container.encode(json, forKey: .anisetteData)
|
||||
}
|
||||
}
|
||||
|
||||
public struct PrepareAppRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "PrepareAppRequest"
|
||||
|
||||
public var udid: String
|
||||
public var contentSize: Int
|
||||
|
||||
public var fileURL: URL?
|
||||
|
||||
public init(udid: String, contentSize: Int, fileURL: URL? = nil)
|
||||
{
|
||||
self.udid = udid
|
||||
self.contentSize = contentSize
|
||||
self.fileURL = fileURL
|
||||
}
|
||||
}
|
||||
|
||||
public struct BeginInstallationRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 3
|
||||
public var identifier = "BeginInstallationRequest"
|
||||
|
||||
// If activeProfiles is non-nil, then AltServer should remove all profiles except active ones.
|
||||
public var activeProfiles: Set<String>?
|
||||
|
||||
public var bundleIdentifier: String?
|
||||
|
||||
public init(activeProfiles: Set<String>?, bundleIdentifier: String?)
|
||||
{
|
||||
self.activeProfiles = activeProfiles
|
||||
self.bundleIdentifier = bundleIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstallationProgressResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "InstallationProgressResponse"
|
||||
|
||||
public var progress: Double
|
||||
|
||||
public init(progress: Double)
|
||||
{
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstallProvisioningProfilesRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "InstallProvisioningProfilesRequest"
|
||||
|
||||
public var udid: String
|
||||
public var provisioningProfiles: Set<ALTProvisioningProfile>
|
||||
|
||||
// If activeProfiles is non-nil, then AltServer should remove all profiles except active ones.
|
||||
public var activeProfiles: Set<String>?
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case identifier
|
||||
case version
|
||||
case udid
|
||||
case provisioningProfiles
|
||||
case activeProfiles
|
||||
}
|
||||
|
||||
public init(udid: String, provisioningProfiles: Set<ALTProvisioningProfile>, activeProfiles: Set<String>?)
|
||||
{
|
||||
self.udid = udid
|
||||
self.provisioningProfiles = provisioningProfiles
|
||||
self.activeProfiles = activeProfiles
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.version = try container.decode(Int.self, forKey: .version)
|
||||
self.identifier = try container.decode(String.self, forKey: .identifier)
|
||||
self.udid = try container.decode(String.self, forKey: .udid)
|
||||
|
||||
let rawProvisioningProfiles = try container.decode([Data].self, forKey: .provisioningProfiles)
|
||||
let provisioningProfiles = try rawProvisioningProfiles.map { (data) -> ALTProvisioningProfile in
|
||||
guard let profile = ALTProvisioningProfile(data: data) else {
|
||||
throw DecodingError.dataCorruptedError(forKey: CodingKeys.provisioningProfiles, in: container, debugDescription: "Could not parse provisioning profile from data.")
|
||||
}
|
||||
return profile
|
||||
}
|
||||
|
||||
self.provisioningProfiles = Set(provisioningProfiles)
|
||||
self.activeProfiles = try container.decodeIfPresent(Set<String>.self, forKey: .activeProfiles)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws
|
||||
{
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.version, forKey: .version)
|
||||
try container.encode(self.identifier, forKey: .identifier)
|
||||
try container.encode(self.udid, forKey: .udid)
|
||||
|
||||
try container.encode(self.provisioningProfiles.map { $0.data }, forKey: .provisioningProfiles)
|
||||
try container.encodeIfPresent(self.activeProfiles, forKey: .activeProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstallProvisioningProfilesResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "InstallProvisioningProfilesResponse"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveProvisioningProfilesRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "RemoveProvisioningProfilesRequest"
|
||||
|
||||
public var udid: String
|
||||
public var bundleIdentifiers: Set<String>
|
||||
|
||||
public init(udid: String, bundleIdentifiers: Set<String>)
|
||||
{
|
||||
self.udid = udid
|
||||
self.bundleIdentifiers = bundleIdentifiers
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveProvisioningProfilesResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "RemoveProvisioningProfilesResponse"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveAppRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "RemoveAppRequest"
|
||||
|
||||
public var udid: String
|
||||
public var bundleIdentifier: String
|
||||
|
||||
public init(udid: String, bundleIdentifier: String)
|
||||
{
|
||||
self.udid = udid
|
||||
self.bundleIdentifier = bundleIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveAppResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "RemoveAppResponse"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct EnableUnsignedCodeExecutionRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "EnableUnsignedCodeExecutionRequest"
|
||||
|
||||
public var udid: String
|
||||
public var processID: Int?
|
||||
public var processName: String?
|
||||
|
||||
public init(udid: String, processID: Int? = nil, processName: String? = nil)
|
||||
{
|
||||
self.udid = udid
|
||||
self.processID = processID
|
||||
self.processName = processName
|
||||
}
|
||||
}
|
||||
|
||||
public struct EnableUnsignedCodeExecutionResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "EnableUnsignedCodeExecutionResponse"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// XPCConnection.swift
|
||||
// AltKit
|
||||
//
|
||||
// Created by Riley Testut on 6/15/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SideKit
|
||||
import os.log
|
||||
|
||||
@objc private protocol XPCConnectionProxy {
|
||||
func ping(completionHandler: @escaping () -> Void)
|
||||
func receive(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void)
|
||||
}
|
||||
|
||||
public extension XPCConnection {
|
||||
static let unc0verMachServiceName = "cy:io.altstore.altdaemon"
|
||||
static let odysseyMachServiceName = "lh:io.altstore.altdaemon"
|
||||
|
||||
static let machServiceNames = [unc0verMachServiceName, odysseyMachServiceName]
|
||||
}
|
||||
|
||||
public class XPCConnection: NSObject, SideConnection {
|
||||
public let xpcConnection: NSXPCConnection
|
||||
|
||||
private let queue = DispatchQueue(label: "io.altstore.XPCConnection")
|
||||
private let dispatchGroup = DispatchGroup()
|
||||
private var semaphore: DispatchSemaphore?
|
||||
private var buffer = Data(capacity: 1024)
|
||||
|
||||
private var error: Error?
|
||||
|
||||
public init(_ xpcConnection: NSXPCConnection) {
|
||||
let proxyInterface = NSXPCInterface(with: XPCConnectionProxy.self)
|
||||
xpcConnection.remoteObjectInterface = proxyInterface
|
||||
xpcConnection.exportedInterface = proxyInterface
|
||||
|
||||
self.xpcConnection = xpcConnection
|
||||
|
||||
super.init()
|
||||
|
||||
xpcConnection.interruptionHandler = {
|
||||
self.error = ALTServerError.lostConnection(underlyingError: nil)
|
||||
}
|
||||
|
||||
xpcConnection.exportedObject = self
|
||||
xpcConnection.resume()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private extension XPCConnection {
|
||||
func makeProxy(errorHandler: @escaping (Error) -> Void) -> XPCConnectionProxy {
|
||||
let proxy = xpcConnection.remoteObjectProxyWithErrorHandler { error in
|
||||
os_log("Error messaging remote object proxy: %@", type: .error , error.localizedDescription)
|
||||
self.error = error
|
||||
errorHandler(error)
|
||||
} as! XPCConnectionProxy
|
||||
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
public extension XPCConnection {
|
||||
func connect(completionHandler: @escaping (Result<Void, Error>) -> Void) {
|
||||
let proxy = makeProxy { error in
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
|
||||
proxy.ping {
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
xpcConnection.invalidate()
|
||||
}
|
||||
|
||||
func __send(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) {
|
||||
guard error == nil else { return completionHandler(false, error) }
|
||||
|
||||
let proxy = makeProxy { error in
|
||||
completionHandler(false, error)
|
||||
}
|
||||
|
||||
proxy.receive(data) { success, error in
|
||||
completionHandler(success, error)
|
||||
}
|
||||
}
|
||||
|
||||
func __receiveData(expectedSize: Int, completionHandler: @escaping (Data?, Error?) -> Void) {
|
||||
guard error == nil else { return completionHandler(nil, error) }
|
||||
|
||||
queue.async {
|
||||
let copiedBuffer = self.buffer // Copy buffer to prevent runtime crashes.
|
||||
guard copiedBuffer.count >= expectedSize else {
|
||||
self.semaphore = DispatchSemaphore(value: 0)
|
||||
DispatchQueue.global().async {
|
||||
_ = self.semaphore?.wait(timeout: .now() + 1.0)
|
||||
self.__receiveData(expectedSize: expectedSize, completionHandler: completionHandler)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let data = copiedBuffer.prefix(expectedSize)
|
||||
self.buffer = copiedBuffer.dropFirst(expectedSize)
|
||||
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension XPCConnection {
|
||||
override var description: String {
|
||||
"\(xpcConnection.endpoint) (XPC)"
|
||||
}
|
||||
}
|
||||
|
||||
extension XPCConnection: XPCConnectionProxy {
|
||||
fileprivate func ping(completionHandler: @escaping () -> Void) {
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
fileprivate func receive(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) {
|
||||
queue.async {
|
||||
self.buffer.append(data)
|
||||
|
||||
self.semaphore?.signal()
|
||||
self.semaphore = nil
|
||||
|
||||
completionHandler(true, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Bundle+AltStore.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 5/30/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension Bundle {
|
||||
enum Info {
|
||||
public static let deviceID = "ALTDeviceID"
|
||||
public static let serverID = "ALTServerID"
|
||||
public static let certificateID = "ALTCertificateID"
|
||||
public static let appGroups = "ALTAppGroups"
|
||||
public static let altBundleID = "ALTBundleIdentifier"
|
||||
public static let orgbundleIdentifier = "com.SideStore"
|
||||
public static let appbundleIdentifier = orgbundleIdentifier + ".SideStore"
|
||||
public static let devicePairingString = "ALTPairingFile"
|
||||
public static let urlTypes = "CFBundleURLTypes"
|
||||
public static let exportedUTIs = "UTExportedTypeDeclarations"
|
||||
|
||||
public static let untetherURL = "ALTFugu14UntetherURL"
|
||||
public static let untetherRequired = "ALTFugu14UntetherRequired"
|
||||
public static let untetherMinimumiOSVersion = "ALTFugu14UntetherMinimumVersion"
|
||||
public static let untetherMaximumiOSVersion = "ALTFugu14UntetherMaximumVersion"
|
||||
}
|
||||
}
|
||||
|
||||
public extension Bundle {
|
||||
var infoPlistURL: URL {
|
||||
let infoPlistURL = bundleURL.appendingPathComponent("Info.plist")
|
||||
return infoPlistURL
|
||||
}
|
||||
|
||||
var provisioningProfileURL: URL {
|
||||
let provisioningProfileURL = bundleURL.appendingPathComponent("embedded.mobileprovision")
|
||||
return provisioningProfileURL
|
||||
}
|
||||
|
||||
var certificateURL: URL {
|
||||
let certificateURL = bundleURL.appendingPathComponent("ALTCertificate.p12")
|
||||
return certificateURL
|
||||
}
|
||||
|
||||
var altstorePlistURL: URL {
|
||||
let altstorePlistURL = bundleURL.appendingPathComponent("AltStore.plist")
|
||||
return altstorePlistURL
|
||||
}
|
||||
}
|
||||
|
||||
public extension Bundle {
|
||||
static var baseAltStoreAppGroupID = "group.com.SideStore.SideStore"
|
||||
|
||||
var appGroups: [String] {
|
||||
infoDictionary?[Bundle.Info.appGroups] as? [String] ?? []
|
||||
}
|
||||
|
||||
var altstoreAppGroup: String? {
|
||||
let appGroup = appGroups.first { $0.contains(Bundle.baseAltStoreAppGroupID) }
|
||||
return appGroup
|
||||
}
|
||||
|
||||
var completeInfoDictionary: [String: Any]? {
|
||||
let infoPlistURL = self.infoPlistURL
|
||||
return NSDictionary(contentsOf: infoPlistURL) as? [String: Any]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// NSError+AltStore.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 3/11/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension NSError {
|
||||
@objc(alt_localizedFailure)
|
||||
var localizedFailure: String? {
|
||||
let localizedFailure = (userInfo[NSLocalizedFailureErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: domain)?(self, NSLocalizedFailureErrorKey) as? String)
|
||||
return localizedFailure
|
||||
}
|
||||
|
||||
@objc(alt_localizedDebugDescription)
|
||||
var localizedDebugDescription: String? {
|
||||
let debugDescription = (userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: domain)?(self, NSDebugDescriptionErrorKey) as? String)
|
||||
return debugDescription
|
||||
}
|
||||
|
||||
@objc(alt_errorWithLocalizedFailure:)
|
||||
func withLocalizedFailure(_ failure: String) -> NSError {
|
||||
var userInfo = self.userInfo
|
||||
userInfo[NSLocalizedFailureErrorKey] = failure
|
||||
|
||||
if let failureReason = localizedFailureReason {
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason
|
||||
} else if localizedFailure == nil && localizedFailureReason == nil && localizedDescription.contains(localizedErrorCode) {
|
||||
// Default localizedDescription, so replace with just the localized error code portion.
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = "(\(localizedErrorCode).)"
|
||||
} else {
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = localizedDescription
|
||||
}
|
||||
|
||||
if let localizedDescription = NSError.userInfoValueProvider(forDomain: domain)?(self, NSLocalizedDescriptionKey) as? String {
|
||||
userInfo[NSLocalizedDescriptionKey] = localizedDescription
|
||||
}
|
||||
|
||||
// Don't accidentally remove localizedDescription from dictionary
|
||||
// userInfo[NSLocalizedDescriptionKey] = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
|
||||
|
||||
if let recoverySuggestion = localizedRecoverySuggestion {
|
||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion
|
||||
}
|
||||
|
||||
let error = NSError(domain: domain, code: code, userInfo: userInfo)
|
||||
return error
|
||||
}
|
||||
|
||||
func sanitizedForCoreData() -> NSError {
|
||||
var userInfo = self.userInfo
|
||||
userInfo[NSLocalizedFailureErrorKey] = localizedFailure
|
||||
userInfo[NSLocalizedDescriptionKey] = localizedDescription
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = localizedFailureReason
|
||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = localizedRecoverySuggestion
|
||||
|
||||
// Remove userInfo values that don't conform to NSSecureEncoding.
|
||||
userInfo = userInfo.filter { _, value in
|
||||
(value as AnyObject) is NSSecureCoding
|
||||
}
|
||||
|
||||
// Sanitize underlying errors.
|
||||
if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error {
|
||||
let sanitizedError = (underlyingError as NSError).sanitizedForCoreData()
|
||||
userInfo[NSUnderlyingErrorKey] = sanitizedError
|
||||
}
|
||||
|
||||
if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error] {
|
||||
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForCoreData() }
|
||||
userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors
|
||||
}
|
||||
|
||||
let error = NSError(domain: domain, code: code, userInfo: userInfo)
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
public extension Error {
|
||||
var underlyingError: Error? {
|
||||
let underlyingError = (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
|
||||
return underlyingError
|
||||
}
|
||||
|
||||
var localizedErrorCode: String {
|
||||
let localizedErrorCode = String(format: NSLocalizedString("%@ error %@", comment: ""), (self as NSError).domain, (self as NSError).code as NSNumber)
|
||||
return localizedErrorCode
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ALTLocalizedError: LocalizedError, CustomNSError {
|
||||
var failure: String? { get }
|
||||
|
||||
var underlyingError: Error? { get }
|
||||
}
|
||||
|
||||
public extension ALTLocalizedError {
|
||||
var errorUserInfo: [String: Any] {
|
||||
let userInfo = ([
|
||||
NSLocalizedDescriptionKey: errorDescription,
|
||||
NSLocalizedFailureReasonErrorKey: failureReason,
|
||||
NSLocalizedFailureErrorKey: failure,
|
||||
NSUnderlyingErrorKey: underlyingError
|
||||
] as [String: Any?]).compactMapValues { $0 }
|
||||
return userInfo
|
||||
}
|
||||
|
||||
var underlyingError: Error? {
|
||||
// Error's default implementation calls errorUserInfo,
|
||||
// but ALTLocalizedError.errorUserInfo calls underlyingError.
|
||||
// Return nil to prevent infinite recursion.
|
||||
nil
|
||||
}
|
||||
|
||||
var errorDescription: String? {
|
||||
guard let errorFailure = failure else { return (underlyingError as NSError?)?.localizedDescription }
|
||||
guard let failureReason = failureReason else { return errorFailure }
|
||||
|
||||
let errorDescription = errorFailure + " " + failureReason
|
||||
return errorDescription
|
||||
}
|
||||
|
||||
var failureReason: String? { (underlyingError as NSError?)?.localizedDescription }
|
||||
var recoverySuggestion: String? { (underlyingError as NSError?)?.localizedRecoverySuggestion }
|
||||
var helpAnchor: String? { (underlyingError as NSError?)?.helpAnchor }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// NSXPCConnection+MachServices.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/22/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc private protocol XPCPrivateAPI {
|
||||
init(machServiceName: String)
|
||||
init(machServiceName: String, options: NSXPCConnection.Options)
|
||||
}
|
||||
|
||||
public extension NSXPCConnection {
|
||||
class func makeConnection(machServiceName: String) -> NSXPCConnection {
|
||||
let connection = unsafeBitCast(self, to: XPCPrivateAPI.Type.self).init(machServiceName: machServiceName, options: .privileged)
|
||||
return unsafeBitCast(connection, to: NSXPCConnection.self)
|
||||
}
|
||||
}
|
||||
|
||||
public extension NSXPCListener {
|
||||
class func makeListener(machServiceName: String) -> NSXPCListener {
|
||||
let listener = unsafeBitCast(self, to: XPCPrivateAPI.Type.self).init(machServiceName: machServiceName)
|
||||
return unsafeBitCast(listener, to: NSXPCListener.self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Result+Conveniences.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 5/22/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension Result {
|
||||
var value: Success? {
|
||||
switch self {
|
||||
case let .success(value): return value
|
||||
case .failure: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var error: Failure? {
|
||||
switch self {
|
||||
case .success: return nil
|
||||
case let .failure(error): return error
|
||||
}
|
||||
}
|
||||
|
||||
init(_ value: Success?, _ error: Failure?) {
|
||||
switch (value, error) {
|
||||
case let (value?, _): self = .success(value)
|
||||
case let (_, error?): self = .failure(error)
|
||||
case (nil, nil): preconditionFailure("Either value or error must be non-nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Result where Success == Void {
|
||||
init(_ success: Bool, _ error: Failure?) {
|
||||
if success {
|
||||
self = .success(())
|
||||
} else if let error = error {
|
||||
self = .failure(error)
|
||||
} else {
|
||||
preconditionFailure("Error must be non-nil if success is false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Result {
|
||||
init<T, U>(_ values: (T?, U?), _ error: Failure?) where Success == (T, U) {
|
||||
if let value1 = values.0, let value2 = values.1 {
|
||||
self = .success((value1, value2))
|
||||
} else if let error = error {
|
||||
self = .failure(error)
|
||||
} else {
|
||||
preconditionFailure("Error must be non-nil if either provided values are nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
21
SideStoreApp/Sources/SideStoreCore/XPC/AltXPCProtocol.swift
Normal file
21
SideStoreApp/Sources/SideStoreCore/XPC/AltXPCProtocol.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AltXPCProtocol.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public typealias AltXPCProtocol = SideXPCProtocol
|
||||
|
||||
@objc
|
||||
public protocol SideXPCProtocol {
|
||||
func ping(completionHandler: @escaping () -> Void)
|
||||
func requestAnisetteData(completionHandler: @escaping (ALTAnisetteData?, Error?) -> Void)
|
||||
}
|
||||
|
||||
@objc public class ALTAnisetteData: NSObject {
|
||||
// implementation
|
||||
}
|
||||
Reference in New Issue
Block a user