diff --git a/AltKit/AltKit.h b/AltKit/AltKit.h index 7d1b7380..59f60252 100644 --- a/AltKit/AltKit.h +++ b/AltKit/AltKit.h @@ -8,5 +8,6 @@ #import "NSError+ALTServerError.h" #import "CFNotificationName+AltStore.h" +#import "ALTConnection.h" extern uint16_t ALTDeviceListeningSocket; diff --git a/AltKit/CFNotificationName+AltStore.h b/AltKit/Categories/CFNotificationName+AltStore.h similarity index 100% rename from AltKit/CFNotificationName+AltStore.h rename to AltKit/Categories/CFNotificationName+AltStore.h diff --git a/AltKit/CFNotificationName+AltStore.m b/AltKit/Categories/CFNotificationName+AltStore.m similarity index 100% rename from AltKit/CFNotificationName+AltStore.m rename to AltKit/Categories/CFNotificationName+AltStore.m diff --git a/AltKit/NSError+ALTServerError.h b/AltKit/Categories/NSError+ALTServerError.h similarity index 100% rename from AltKit/NSError+ALTServerError.h rename to AltKit/Categories/NSError+ALTServerError.h diff --git a/AltKit/NSError+ALTServerError.m b/AltKit/Categories/NSError+ALTServerError.m similarity index 100% rename from AltKit/NSError+ALTServerError.m rename to AltKit/Categories/NSError+ALTServerError.m diff --git a/AltKit/Connections/ALTConnection.h b/AltKit/Connections/ALTConnection.h new file mode 100644 index 00000000..348573d7 --- /dev/null +++ b/AltKit/Connections/ALTConnection.h @@ -0,0 +1,23 @@ +// +// ALTConnection.h +// AltKit +// +// Created by Riley Testut on 6/1/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(Connection) +@protocol ALTConnection + +- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler NS_REFINED_FOR_SWIFT; +- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler NS_SWIFT_NAME(__receiveData(expectedSize:completionHandler:)); + +- (void)disconnect; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltKit/Connections/Connection.swift b/AltKit/Connections/Connection.swift new file mode 100644 index 00000000..24388501 --- /dev/null +++ b/AltKit/Connections/Connection.swift @@ -0,0 +1,113 @@ +// +// Connection.swift +// AltKit +// +// Created by Riley Testut on 6/1/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation +import Network + +public extension Connection +{ + func send(_ data: Data, completionHandler: @escaping (Result) -> Void) + { + self.__send(data) { (success, error) in + let result = Result(success, error).mapError { (error) -> ALTServerError in + guard let nwError = error as? NWError else { return ALTServerError(error) } + return ALTServerError(.lostConnection, underlyingError: nwError) + } + + completionHandler(result) + } + } + + func receiveData(expectedSize: Int, completionHandler: @escaping (Result) -> Void) + { + self.__receiveData(expectedSize: expectedSize) { (data, error) in + let result = Result(data, error).mapError { (error) -> ALTServerError in + guard let nwError = error as? NWError else { return ALTServerError(error) } + return ALTServerError(.lostConnection, underlyingError: nwError) + } + + completionHandler(result) + } + } + + func send(_ response: T, shouldDisconnect: Bool = false, completionHandler: @escaping (Result) -> Void) + { + func finish(_ result: Result) + { + 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) } + + self.send(responseSize) { (result) in + switch result + { + case .failure(let error): finish(.failure(error)) + case .success: + self.send(data) { (result) in + switch result + { + case .failure(let error): finish(.failure(error)) + case .success: finish(.success(())) + } + } + } + } + } + catch + { + finish(.failure(.init(.invalidResponse, underlyingError: error))) + } + } + + func receiveRequest(completionHandler: @escaping (Result) -> Void) + { + let size = MemoryLayout.size + + print("Receiving request size from connection:", self) + 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) + + print("Received request:", request) + completionHandler(.success(request)) + } + catch + { + completionHandler(.failure(ALTServerError(error))) + } + } + } + catch + { + completionHandler(.failure(ALTServerError(error))) + } + } + } +} diff --git a/AltKit/Connections/ConnectionManager.swift b/AltKit/Connections/ConnectionManager.swift new file mode 100644 index 00000000..9d3c054b --- /dev/null +++ b/AltKit/Connections/ConnectionManager.swift @@ -0,0 +1,160 @@ +// +// ConnectionManager.swift +// AltServer +// +// Created by Riley Testut on 5/23/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +import Foundation +import Network + +public protocol RequestHandler +{ + func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) + func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) + + func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection, + completionHandler: @escaping (Result) -> Void) + func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection, + completionHandler: @escaping (Result) -> Void) + + func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) +} + +public protocol ConnectionHandler: AnyObject +{ + var connectionHandler: ((Connection) -> Void)? { get set } + var disconnectionHandler: ((Connection) -> Void)? { get set } + + func startListening() + func stopListening() +} + +public class ConnectionManager +{ + public let requestHandler: RequestHandlerType + public let connectionHandlers: [ConnectionHandler] + + public var isStarted = false + + private var connections = [Connection]() + + public init(requestHandler: RequestHandlerType, connectionHandlers: [ConnectionHandler]) + { + 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 !self.isStarted else { return } + + for connectionHandler in self.connectionHandlers + { + connectionHandler.startListening() + } + + self.isStarted = true + } + + public func stop() + { + guard self.isStarted else { return } + + for connectionHandler in self.connectionHandlers + { + connectionHandler.stopListening() + } + + self.isStarted = false + } +} + +private extension ConnectionManager +{ + func prepare(_ connection: Connection) + { + guard !self.connections.contains(where: { $0 === connection }) else { return } + self.connections.append(connection) + + self.handleRequest(for: connection) + } + + func disconnect(_ connection: Connection) + { + guard let index = self.connections.firstIndex(where: { $0 === connection }) else { return } + self.connections.remove(at: index) + } + + func handleRequest(for connection: Connection) + { + func finish(_ result: Result) + { + do + { + let response = try result.get() + connection.send(response, shouldDisconnect: true) { (result) in + print("Sent response \(response) with result:", result) + } + } + catch + { + let response = ErrorResponse(error: ALTServerError(error)) + connection.send(response, shouldDisconnect: true) { (result) in + print("Sent error response \(response) with result:", result) + } + } + } + + connection.receiveRequest() { (result) in + print("Received request with result:", result) + + switch result + { + case .failure(let error): finish(Result.failure(error)) + + case .success(.anisetteData(let request)): + self.requestHandler.handleAnisetteDataRequest(request, for: connection) { (result) in + finish(result) + } + + case .success(.prepareApp(let request)): + self.requestHandler.handlePrepareAppRequest(request, for: connection) { (result) in + finish(result) + } + + case .success(.beginInstallation): break + + case .success(.installProvisioningProfiles(let request)): + self.requestHandler.handleInstallProvisioningProfilesRequest(request, for: connection) { (result) in + finish(result) + } + + case .success(.removeProvisioningProfiles(let request)): + self.requestHandler.handleRemoveProvisioningProfilesRequest(request, for: connection) { (result) in + finish(result) + } + + case .success(.removeApp(let request)): + self.requestHandler.handleRemoveAppRequest(request, for: connection) { (result) in + finish(result) + } + + case .success(.unknown): + finish(Result.failure(ALTServerError(.unknownRequest))) + } + } + } +} diff --git a/AltKit/Connections/NetworkConnection.swift b/AltKit/Connections/NetworkConnection.swift new file mode 100644 index 00000000..041a648f --- /dev/null +++ b/AltKit/Connections/NetworkConnection.swift @@ -0,0 +1,50 @@ +// +// NetworkConnection.swift +// AltKit +// +// Created by Riley Testut on 6/1/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation +import Network + +public class NetworkConnection: NSObject, Connection +{ + public let nwConnection: NWConnection + + public init(_ nwConnection: NWConnection) + { + self.nwConnection = nwConnection + } + + public func __send(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) + { + self.nwConnection.send(content: data, completion: .contentProcessed { (error) in + completionHandler(error == nil, error) + }) + } + + public func __receiveData(expectedSize: Int, completionHandler: @escaping (Data?, Error?) -> Void) + { + self.nwConnection.receive(minimumIncompleteLength: expectedSize, maximumLength: expectedSize) { (data, context, isComplete, error) in + completionHandler(data, error) + } + } + + public func disconnect() + { + switch self.nwConnection.state + { + case .cancelled, .failed: break + default: self.nwConnection.cancel() + } + } +} + +extension NetworkConnection +{ + override public var description: String { + return "\(self.nwConnection.endpoint) (Network)" + } +} diff --git a/AltKit/Extensions/ALTServerError+Conveniences.swift b/AltKit/Extensions/ALTServerError+Conveniences.swift new file mode 100644 index 00000000..0c596d39 --- /dev/null +++ b/AltKit/Extensions/ALTServerError+Conveniences.swift @@ -0,0 +1,36 @@ +// +// ALTServerError+Conveniences.swift +// AltKit +// +// Created by Riley Testut on 6/4/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation + +public extension ALTServerError +{ + init(_ error: E) + { + switch error + { + case let error as ALTServerError: self = error + case is DecodingError: self = ALTServerError(.invalidRequest, underlyingError: error) + case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error) + case let error as NSError: + var userInfo = error.userInfo + if !userInfo.keys.contains(NSUnderlyingErrorKey) + { + // Assign underlying error (if there isn't already one). + userInfo[NSUnderlyingErrorKey] = error + } + + self = ALTServerError(.unknown, userInfo: error.userInfo) + } + } + + init(_ code: ALTServerError.Code, underlyingError: E) + { + self = ALTServerError(.invalidRequest, userInfo: [NSUnderlyingErrorKey: underlyingError]) + } +} diff --git a/AltKit/Bundle+AltStore.swift b/AltKit/Extensions/Bundle+AltStore.swift similarity index 100% rename from AltKit/Bundle+AltStore.swift rename to AltKit/Extensions/Bundle+AltStore.swift diff --git a/AltKit/Result+Conveniences.swift b/AltKit/Extensions/Result+Conveniences.swift similarity index 100% rename from AltKit/Result+Conveniences.swift rename to AltKit/Extensions/Result+Conveniences.swift diff --git a/AltKit/CodableServerError.swift b/AltKit/Server Protocol/CodableServerError.swift similarity index 100% rename from AltKit/CodableServerError.swift rename to AltKit/Server Protocol/CodableServerError.swift diff --git a/AltKit/ServerProtocol.swift b/AltKit/Server Protocol/ServerProtocol.swift similarity index 100% rename from AltKit/ServerProtocol.swift rename to AltKit/Server Protocol/ServerProtocol.swift diff --git a/AltServer/Connections/ALTWiredConnection+Private.h b/AltServer/Connections/ALTWiredConnection+Private.h index 24dc2c85..a6c64d14 100644 --- a/AltServer/Connections/ALTWiredConnection+Private.h +++ b/AltServer/Connections/ALTWiredConnection+Private.h @@ -14,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ALTWiredConnection () +@property (nonatomic, readwrite, getter=isConnected) BOOL connected; + @property (nonatomic, readonly) idevice_connection_t connection; - (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection; diff --git a/AltServer/Connections/ALTWiredConnection.h b/AltServer/Connections/ALTWiredConnection.h index 05801f98..2a588873 100644 --- a/AltServer/Connections/ALTWiredConnection.h +++ b/AltServer/Connections/ALTWiredConnection.h @@ -8,10 +8,14 @@ #import +#import "AltKit.h" + NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(WiredConnection) -@interface ALTWiredConnection : NSObject +@interface ALTWiredConnection : NSObject + +@property (nonatomic, readonly, getter=isConnected) BOOL connected; @property (nonatomic, copy, readonly) ALTDevice *device; diff --git a/AltServer/Connections/ALTWiredConnection.m b/AltServer/Connections/ALTWiredConnection.m index 75efac24..32ecda98 100644 --- a/AltServer/Connections/ALTWiredConnection.m +++ b/AltServer/Connections/ALTWiredConnection.m @@ -30,8 +30,15 @@ - (void)disconnect { + if (![self isConnected]) + { + return; + } + idevice_disconnect(self.connection); _connection = nil; + + self.connected = NO; } - (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler @@ -98,4 +105,11 @@ }); } +#pragma mark - NSObject - + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ (Wired)", self.device.name]; +} + @end diff --git a/AltServer/Connections/ClientConnection.swift b/AltServer/Connections/ClientConnection.swift deleted file mode 100644 index 33b5562c..00000000 --- a/AltServer/Connections/ClientConnection.swift +++ /dev/null @@ -1,231 +0,0 @@ -// -// ClientConnection.swift -// AltServer -// -// Created by Riley Testut on 1/9/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation -import Network - -import AltKit -import AltSign - -extension ClientConnection -{ - enum Connection - { - case wireless(NWConnection) - case wired(WiredConnection) - } -} - -class ClientConnection -{ - let connection: Connection - - init(connection: Connection) - { - self.connection = connection - } - - func disconnect() - { - switch self.connection - { - case .wireless(let connection): - switch connection.state - { - case .cancelled, .failed: - print("Disconnecting from \(connection.endpoint)...") - - default: - // State update handler might call this method again. - connection.cancel() - } - - case .wired(let connection): - connection.disconnect() - } - } - - func send(_ response: T, shouldDisconnect: Bool = false, completionHandler: @escaping (Result) -> Void) - { - func finish(_ result: Result) - { - 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) } - - self.send(responseSize) { (result) in - switch result - { - case .failure: finish(.failure(.init(.lostConnection))) - case .success: - - self.send(data) { (result) in - switch result - { - case .failure: finish(.failure(.init(.lostConnection))) - case .success: finish(.success(())) - } - } - } - } - } - catch - { - finish(.failure(.init(.invalidResponse))) - } - } - - func receiveRequest(completionHandler: @escaping (Result) -> Void) - { - let size = MemoryLayout.size - - print("Receiving request size") - self.receiveData(expectedBytes: size) { (result) in - do - { - let data = try result.get() - - print("Receiving request...") - let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) }) - self.receiveData(expectedBytes: expectedBytes) { (result) in - do - { - let data = try result.get() - let request = try JSONDecoder().decode(ServerRequest.self, from: data) - - print("Received installation request:", request) - completionHandler(.success(request)) - } - catch - { - completionHandler(.failure(ALTServerError(error))) - } - } - } - catch - { - completionHandler(.failure(ALTServerError(error))) - } - } - } - - func send(_ data: Data, completionHandler: @escaping (Result) -> Void) - { - switch self.connection - { - case .wireless(let connection): - connection.send(content: data, completion: .contentProcessed { (error) in - if let error = error - { - completionHandler(.failure(error)) - } - else - { - completionHandler(.success(())) - } - }) - - case .wired(let connection): - connection.send(data) { (success, error) in - if !success - { - completionHandler(.failure(ALTServerError(.lostConnection))) - } - else - { - completionHandler(.success(())) - } - } - } - } - - func receiveData(expectedBytes: Int, completionHandler: @escaping (Result) -> Void) - { - func finish(data: Data?, error: Error?) - { - do - { - let data = try self.process(data: data, error: error) - completionHandler(.success(data)) - } - catch - { - completionHandler(.failure(ALTServerError(error))) - } - } - - switch self.connection - { - case .wireless(let connection): - connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in - finish(data: data, error: error) - } - - case .wired(let connection): - connection.receiveData(withExpectedSize: expectedBytes) { (data, error) in - finish(data: data, error: error) - } - } - } -} - -extension ClientConnection: CustomStringConvertible -{ - var description: String { - switch self.connection - { - case .wireless(let connection): return "\(connection.endpoint) (Wireless)" - case .wired(let connection): return "\(connection.device.name) (Wired)" - } - } -} - -private extension ClientConnection -{ - func process(data: Data?, error: Error?) throws -> Data - { - do - { - do - { - guard let data = data else { throw error ?? ALTServerError(.unknown) } - return data - } - catch let error as NWError - { - print("Error receiving data from connection \(connection)", error) - - throw ALTServerError(.lostConnection) - } - catch - { - throw error - } - } - catch let error as ALTServerError - { - throw error - } - catch - { - preconditionFailure("A non-ALTServerError should never be thrown from this method.") - } - } -} diff --git a/AltServer/Connections/ConnectionManager.swift b/AltServer/Connections/ConnectionManager.swift deleted file mode 100644 index fa665e8d..00000000 --- a/AltServer/Connections/ConnectionManager.swift +++ /dev/null @@ -1,535 +0,0 @@ -// -// ConnectionManager.swift -// AltServer -// -// Created by Riley Testut on 5/23/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import Foundation -import Network -import AppKit - -import AltKit - -extension ALTServerError -{ - init(_ error: E) - { - switch error - { - case let error as ALTServerError: self = error - case is DecodingError: self = ALTServerError(.invalidRequest) - case is EncodingError: self = ALTServerError(.invalidResponse) - case let error as NSError: - self = ALTServerError(.unknown, userInfo: error.userInfo) - } - } -} - -extension ConnectionManager -{ - enum State - { - case notRunning - case connecting - case running(NWListener.Service) - case failed(Swift.Error) - } -} - -class ConnectionManager -{ - static let shared = ConnectionManager() - - var stateUpdateHandler: ((State) -> Void)? - - private(set) var state: State = .notRunning { - didSet { - self.stateUpdateHandler?(self.state) - } - } - - private lazy var listener = self.makeListener() - private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltServer.connections", qos: .utility) - - private var connections = [ClientConnection]() - private var notificationConnections = [ALTDevice: NotificationConnection]() - - private init() - { - NotificationCenter.default.addObserver(self, selector: #selector(ConnectionManager.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(ConnectionManager.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil) - } - - func start() - { - switch self.state - { - case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue) - default: break - } - } - - func stop() - { - switch self.state - { - case .running: self.listener.cancel() - default: break - } - } - - func disconnect(_ connection: ClientConnection) - { - connection.disconnect() - - if let index = self.connections.firstIndex(where: { $0 === connection }) - { - self.connections.remove(at: index) - } - } -} - -private extension ConnectionManager -{ - func makeListener() -> NWListener - { - let listener = try! NWListener(using: .tcp) - - let service: NWListener.Service - - if let serverID = UserDefaults.standard.serverID?.data(using: .utf8) - { - let txtDictionary = ["serverID": serverID] - let txtData = NetService.data(fromTXTRecord: txtDictionary) - - service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData) - } - else - { - service = NWListener.Service(type: ALTServerServiceType) - } - - listener.service = service - - listener.serviceRegistrationUpdateHandler = { (serviceChange) in - switch serviceChange - { - case .add(.service(let name, let type, let domain, _)): - let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil) - self.state = .running(service) - - default: break - } - } - - listener.stateUpdateHandler = { (state) in - switch state - { - case .ready: break - case .waiting, .setup: self.state = .connecting - case .cancelled: self.state = .notRunning - case .failed(let error): - self.state = .failed(error) - self.start() - - @unknown default: break - } - } - - listener.newConnectionHandler = { [weak self] (connection) in - self?.prepare(connection) - } - - return listener - } - - func prepare(_ connection: NWConnection) - { - let clientConnection = ClientConnection(connection: .wireless(connection)) - - guard !self.connections.contains(where: { $0 === clientConnection }) else { return } - self.connections.append(clientConnection) - - connection.stateUpdateHandler = { [weak self] (state) in - switch state - { - case .setup, .preparing: break - - case .ready: - print("Connected to client:", connection.endpoint) - self?.handleRequest(for: clientConnection) - - case .waiting: - print("Waiting for connection...") - - case .failed(let error): - print("Failed to connect to service \(connection.endpoint).", error) - self?.disconnect(clientConnection) - - case .cancelled: - self?.disconnect(clientConnection) - - @unknown default: break - } - } - - connection.start(queue: self.dispatchQueue) - } -} - -private extension ConnectionManager -{ - func startNotificationConnection(to device: ALTDevice) - { - ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in - guard let connection = connection else { return } - - let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest] - connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in - guard success else { return } - - connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in - guard let self = self, let connection = connection else { return } - self.handle(notification, for: connection) - } - - self.notificationConnections[device] = connection - } - } - } - - func stopNotificationConnection(to device: ALTDevice) - { - guard let connection = self.notificationConnections[device] else { return } - connection.disconnect() - - self.notificationConnections[device] = nil - } - - func handle(_ notification: CFNotificationName, for connection: NotificationConnection) - { - switch notification - { - case .wiredServerConnectionAvailableRequest: - connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in - if let error = error, !success - { - print("Error sending wired server connection response.", error) - } - else - { - print("Sent wired server connection available response!") - } - } - - case .wiredServerConnectionStartRequest: - ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in - if let wiredConnection = wiredConnection - { - print("Started wired server connection!") - - let clientConnection = ClientConnection(connection: .wired(wiredConnection)) - self.handleRequest(for: clientConnection) - } - else if let error = error - { - print("Error starting wired server connection.", error) - } - } - - default: break - } - } -} - -private extension ConnectionManager -{ - func handleRequest(for connection: ClientConnection) - { - connection.receiveRequest() { (result) in - print("Received initial request with result:", result) - - switch result - { - case .failure(let error): - let response = ErrorResponse(error: ALTServerError(error)) - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent error response with result:", result) - } - - case .success(.anisetteData(let request)): - self.handleAnisetteDataRequest(request, for: connection) - - case .success(.prepareApp(let request)): - self.handlePrepareAppRequest(request, for: connection) - - case .success(.beginInstallation): break - - case .success(.installProvisioningProfiles(let request)): - self.handleInstallProvisioningProfilesRequest(request, for: connection) - - case .success(.removeProvisioningProfiles(let request)): - self.handleRemoveProvisioningProfilesRequest(request, for: connection) - - case .success(.removeApp(let request)): - self.handleRemoveAppRequest(request, for: connection) - - case .success(.unknown): - let response = ErrorResponse(error: ALTServerError(.unknownRequest)) - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent unknown request response with result:", result) - } - } - } - } - - func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: ClientConnection) - { - AnisetteDataManager.shared.requestAnisetteData { (result) in - switch result - { - case .failure(let error): - let errorResponse = ErrorResponse(error: ALTServerError(error)) - connection.send(errorResponse, shouldDisconnect: true) { (result) in - print("Sent anisette data error response with result:", result) - } - - case .success(let anisetteData): - let response = AnisetteDataResponse(anisetteData: anisetteData) - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent anisette data response with result:", result) - } - } - } - } - - func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: ClientConnection) - { - var temporaryURL: URL? - - func finish(_ result: Result) - { - if let temporaryURL = temporaryURL - { - do { try FileManager.default.removeItem(at: temporaryURL) } - catch { print("Failed to remove .ipa.", error) } - } - - switch result - { - case .failure(let error): - print("Failed to process request from \(connection).", error) - - let response = ErrorResponse(error: ALTServerError(error)) - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent install app error response to \(connection) with result:", result) - } - - case .success: - print("Processed request from \(connection).") - - let response = InstallationProgressResponse(progress: 1.0) - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent install app response to \(connection) with result:", result) - } - } - } - - self.receiveApp(for: request, from: connection) { (result) in - print("Received app with result:", result) - - switch result - { - case .failure(let error): finish(.failure(error)) - case .success(let fileURL): - temporaryURL = fileURL - - print("Awaiting begin installation request...") - - connection.receiveRequest() { (result) in - print("Received begin installation request with result:", result) - - switch result - { - case .failure(let error): finish(.failure(error)) - case .success(.beginInstallation(let installRequest)): - print("Installing to device \(request.udid)...") - - self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in - print("Installed to device with result:", result) - switch result - { - case .failure(let error): finish(.failure(error)) - case .success: finish(.success(())) - } - } - - case .success: - let response = ErrorResponse(error: ALTServerError(.unknownRequest)) - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent unknown request error response to \(connection) with result:", result) - } - } - } - } - } - } - - func receiveApp(for request: PrepareAppRequest, from connection: ClientConnection, completionHandler: @escaping (Result) -> Void) - { - connection.receiveData(expectedBytes: request.contentSize) { (result) in - do - { - print("Received app data!") - - let data = try result.get() - - guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) } - - print("Writing app data...") - - let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa") - try data.write(to: temporaryURL, options: .atomic) - - print("Wrote app to URL:", temporaryURL) - - completionHandler(.success(temporaryURL)) - } - catch - { - print("Error processing app data:", error) - - completionHandler(.failure(ALTServerError(error))) - } - } - } - - func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set?, connection: ClientConnection, completionHandler: @escaping (Result) -> Void) - { - let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default) - var isSending = false - - var observation: NSKeyValueObservation? - - let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in - print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription) - - if let error = error.map({ $0 as? ALTServerError ?? ALTServerError(.unknown) }) - { - completionHandler(.failure(error)) - } - else - { - completionHandler(.success(())) - } - - observation?.invalidate() - observation = nil - } - - observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in - serialQueue.async { - guard !isSending else { return } - isSending = true - - print("Progress:", progress.fractionCompleted) - let response = InstallationProgressResponse(progress: progress.fractionCompleted) - - connection.send(response) { (result) in - serialQueue.async { - isSending = false - } - } - } - }) - } - - func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: ClientConnection) - { - ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in - if let error = error, !success - { - print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error) - - let errorResponse = ErrorResponse(error: ALTServerError(error)) - connection.send(errorResponse, shouldDisconnect: true) { (result) in - print("Sent install profiles error response with result:", result) - } - } - else - { - print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier }) - - let response = InstallProvisioningProfilesResponse() - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent install profiles response to \(connection) with result:", result) - } - } - } - } - - func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: ClientConnection) - { - ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in - if let error = error, !success - { - print("Failed to remove profiles \(request.bundleIdentifiers):", error) - - let errorResponse = ErrorResponse(error: ALTServerError(error)) - connection.send(errorResponse, shouldDisconnect: true) { (result) in - print("Sent remove profiles error response with result:", result) - } - } - else - { - print("Removed profiles:", request.bundleIdentifiers) - - let response = RemoveProvisioningProfilesResponse() - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent remove profiles success response to \(connection) with result:", result) - } - } - } - } - - func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: ClientConnection) - { - ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in - if let error = error, !success - { - print("Failed to remove app \(request.bundleIdentifier):", error) - - let errorResponse = ErrorResponse(error: ALTServerError(error)) - connection.send(errorResponse, shouldDisconnect: true) { (result) in - print("Sent remove a[[ error response with result:", result) - } - } - else - { - print("Removed app:", request.bundleIdentifier) - - let response = RemoveAppResponse() - connection.send(response, shouldDisconnect: true) { (result) in - print("Sent remove app success response to \(connection) with result:", result) - } - } - } - } -} - -private extension ConnectionManager -{ - @objc func deviceDidConnect(_ notification: Notification) - { - guard let device = notification.object as? ALTDevice else { return } - self.startNotificationConnection(to: device) - } - - @objc func deviceDidDisconnect(_ notification: Notification) - { - guard let device = notification.object as? ALTDevice else { return } - self.stopNotificationConnection(to: device) - } -} diff --git a/AltServer/Connections/RequestHandler.swift b/AltServer/Connections/RequestHandler.swift new file mode 100644 index 00000000..ee58cd1e --- /dev/null +++ b/AltServer/Connections/RequestHandler.swift @@ -0,0 +1,219 @@ +// +// RequestHandler.swift +// AltServer +// +// Created by Riley Testut on 5/23/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +import Foundation +import AltKit + +typealias ConnectionManager = AltKit.ConnectionManager + +private let connectionManager = ConnectionManager(requestHandler: RequestHandler(), + connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()]) + +extension ConnectionManager +{ + static var shared: ConnectionManager { + return connectionManager + } +} + +struct RequestHandler: AltKit.RequestHandler +{ + func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) + { + AnisetteDataManager.shared.requestAnisetteData { (result) in + switch result + { + case .failure(let error): completionHandler(.failure(error)) + case .success(let anisetteData): + let response = AnisetteDataResponse(anisetteData: anisetteData) + completionHandler(.success(response)) + } + } + } + + func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) + { + var temporaryURL: URL? + + func finish(_ result: Result) + { + if let temporaryURL = temporaryURL + { + do { try FileManager.default.removeItem(at: temporaryURL) } + catch { print("Failed to remove .ipa.", error) } + } + + completionHandler(result) + } + + self.receiveApp(for: request, from: connection) { (result) in + print("Received app with result:", result) + + switch result + { + case .failure(let error): finish(.failure(error)) + case .success(let fileURL): + temporaryURL = fileURL + + print("Awaiting begin installation request...") + + connection.receiveRequest() { (result) in + print("Received begin installation request with result:", result) + + switch result + { + case .failure(let error): finish(.failure(error)) + case .success(.beginInstallation(let installRequest)): + print("Installing app to device \(request.udid)...") + + self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in + print("Installed app to device with result:", result) + switch result + { + case .failure(let error): finish(.failure(error)) + case .success: + let response = InstallationProgressResponse(progress: 1.0) + finish(.success(response)) + } + } + + case .success: finish(.failure(ALTServerError(.unknownRequest))) + } + } + } + } + } + + func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection, + completionHandler: @escaping (Result) -> Void) + { + ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in + if let error = error, !success + { + print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error) + completionHandler(.failure(ALTServerError(error))) + } + else + { + print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier }) + + let response = InstallProvisioningProfilesResponse() + completionHandler(.success(response)) + } + } + } + + func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection, + completionHandler: @escaping (Result) -> Void) + { + ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in + if let error = error, !success + { + print("Failed to remove profiles \(request.bundleIdentifiers):", error) + completionHandler(.failure(ALTServerError(error))) + } + else + { + print("Removed profiles:", request.bundleIdentifiers) + + let response = RemoveProvisioningProfilesResponse() + completionHandler(.success(response)) + } + } + } + + func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) + { + ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in + if let error = error, !success + { + print("Failed to remove app \(request.bundleIdentifier):", error) + completionHandler(.failure(ALTServerError(error))) + } + else + { + print("Removed app:", request.bundleIdentifier) + + let response = RemoveAppResponse() + completionHandler(.success(response)) + } + } + } +} + +private extension RequestHandler +{ + func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result) -> Void) + { + connection.receiveData(expectedSize: request.contentSize) { (result) in + do + { + print("Received app data!") + + let data = try result.get() + + guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) } + + print("Writing app data...") + + let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa") + try data.write(to: temporaryURL, options: .atomic) + + print("Wrote app to URL:", temporaryURL) + + completionHandler(.success(temporaryURL)) + } + catch + { + print("Error processing app data:", error) + + completionHandler(.failure(ALTServerError(error))) + } + } + } + + func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set?, connection: Connection, completionHandler: @escaping (Result) -> Void) + { + let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default) + var isSending = false + + var observation: NSKeyValueObservation? + + let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in + print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription) + + if let error = error.map({ $0 as? ALTServerError ?? ALTServerError(.unknown) }) + { + completionHandler(.failure(error)) + } + else + { + completionHandler(.success(())) + } + + observation?.invalidate() + observation = nil + } + + observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in + serialQueue.async { + guard !isSending else { return } + isSending = true + + print("Progress:", progress.fractionCompleted) + let response = InstallationProgressResponse(progress: progress.fractionCompleted) + + connection.send(response) { (result) in + serialQueue.async { + isSending = false + } + } + } + }) + } +} diff --git a/AltServer/Connections/WiredConnectionHandler.swift b/AltServer/Connections/WiredConnectionHandler.swift new file mode 100644 index 00000000..55fde198 --- /dev/null +++ b/AltServer/Connections/WiredConnectionHandler.swift @@ -0,0 +1,116 @@ +// +// WiredConnectionHandler.swift +// AltServer +// +// Created by Riley Testut on 6/1/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation +import AltKit + +class WiredConnectionHandler: ConnectionHandler +{ + var connectionHandler: ((Connection) -> Void)? + var disconnectionHandler: ((Connection) -> Void)? + + private var notificationConnections = [ALTDevice: NotificationConnection]() + + func startListening() + { + NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil) + } + + func stopListening() + { + NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil) + NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil) + } +} + +private extension WiredConnectionHandler +{ + func startNotificationConnection(to device: ALTDevice) + { + ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in + guard let connection = connection else { return } + + let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest] + connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in + guard success else { return } + + connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in + guard let self = self, let connection = connection else { return } + self.handle(notification, for: connection) + } + + self.notificationConnections[device] = connection + } + } + } + + func stopNotificationConnection(to device: ALTDevice) + { + guard let connection = self.notificationConnections[device] else { return } + connection.disconnect() + + self.notificationConnections[device] = nil + } + + func handle(_ notification: CFNotificationName, for connection: NotificationConnection) + { + switch notification + { + case .wiredServerConnectionAvailableRequest: + connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in + if let error = error, !success + { + print("Error sending wired server connection response.", error) + } + else + { + print("Sent wired server connection available response!") + } + } + + case .wiredServerConnectionStartRequest: + ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in + if let wiredConnection = wiredConnection + { + print("Started wired server connection!") + self.connectionHandler?(wiredConnection) + + var observation: NSKeyValueObservation? + observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in + guard !connection.isConnected else { return } + self?.disconnectionHandler?(connection) + + observation?.invalidate() + } + } + else if let error = error + { + print("Error starting wired server connection.", error) + } + } + + default: break + } + } +} + +private extension WiredConnectionHandler +{ + @objc func deviceDidConnect(_ notification: Notification) + { + guard let device = notification.object as? ALTDevice else { return } + self.startNotificationConnection(to: device) + } + + @objc func deviceDidDisconnect(_ notification: Notification) + { + guard let device = notification.object as? ALTDevice else { return } + self.stopNotificationConnection(to: device) + } +} diff --git a/AltServer/Connections/WirelessConnectionHandler.swift b/AltServer/Connections/WirelessConnectionHandler.swift new file mode 100644 index 00000000..d377a456 --- /dev/null +++ b/AltServer/Connections/WirelessConnectionHandler.swift @@ -0,0 +1,150 @@ +// +// WirelessConnectionHandler.swift +// AltKit +// +// Created by Riley Testut on 6/1/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation +import Network + +import AltKit + +extension WirelessConnectionHandler +{ + public enum State + { + case notRunning + case connecting + case running(NWListener.Service) + case failed(Swift.Error) + } +} + +public class WirelessConnectionHandler: ConnectionHandler +{ + public var connectionHandler: ((Connection) -> Void)? + public var disconnectionHandler: ((Connection) -> Void)? + + public var stateUpdateHandler: ((State) -> Void)? + + public private(set) var state: State = .notRunning { + didSet { + self.stateUpdateHandler?(self.state) + } + } + + private lazy var listener = self.makeListener() + private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility) + + public func startListening() + { + switch self.state + { + case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue) + default: break + } + } + + public func stopListening() + { + switch self.state + { + case .running: self.listener.cancel() + default: break + } + } +} + +private extension WirelessConnectionHandler +{ + func makeListener() -> NWListener + { + let listener = try! NWListener(using: .tcp) + + let service: NWListener.Service + + if let serverID = UserDefaults.standard.serverID?.data(using: .utf8) + { + let txtDictionary = ["serverID": serverID] + let txtData = NetService.data(fromTXTRecord: txtDictionary) + + service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData) + } + else + { + service = NWListener.Service(type: ALTServerServiceType) + } + + listener.service = service + + listener.serviceRegistrationUpdateHandler = { (serviceChange) in + switch serviceChange + { + case .add(.service(let name, let type, let domain, _)): + let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil) + self.state = .running(service) + + default: break + } + } + + listener.stateUpdateHandler = { (state) in + switch state + { + case .ready: break + case .waiting, .setup: self.state = .connecting + case .cancelled: self.state = .notRunning + case .failed(let error): self.state = .failed(error) + @unknown default: break + } + } + + listener.newConnectionHandler = { [weak self] (connection) in + self?.prepare(connection) + } + + return listener + } + + func prepare(_ nwConnection: NWConnection) + { + print("Preparing:", nwConnection) + + // Use same instance for all callbacks. + let connection = NetworkConnection(nwConnection) + + nwConnection.stateUpdateHandler = { [weak self] (state) in + switch state + { + case .setup, .preparing: break + + case .ready: + print("Connected to client:", connection) + self?.connectionHandler?(connection) + + case .waiting: + print("Waiting for connection...") + + case .failed(let error): + print("Failed to connect to service \(nwConnection.endpoint).", error) + self?.disconnect(connection) + + case .cancelled: + self?.disconnect(connection) + + @unknown default: break + } + } + + nwConnection.start(queue: self.dispatchQueue) + } + + func disconnect(_ connection: Connection) + { + connection.disconnect() + + self.disconnectionHandler?(connection) + } +} diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 8724c742..5fcd3c7e 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -24,7 +24,10 @@ BF100C50232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */; }; BF100C54232D7DAE006A8926 /* StoreAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */; }; BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B0F022E25DF9005C4CF5 /* ToastView.swift */; }; - BF1E312B229F474900370A3C /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* ConnectionManager.swift */; }; + BF18BFF32485828200DD5981 /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; }; + BF18BFF724858BDE00DD5981 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; }; + BF18BFFD2485A1E400DD5981 /* WiredConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFFC2485A1E400DD5981 /* WiredConnectionHandler.swift */; }; + BF1E312B229F474900370A3C /* RequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* RequestHandler.swift */; }; BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; }; BF1E315822A061F900370A3C /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; }; BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; }; @@ -130,7 +133,6 @@ BF580482246A28F7008AE704 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580481246A28F7008AE704 /* ViewController.swift */; }; BF580487246A28F9008AE704 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF580486246A28F9008AE704 /* Assets.xcassets */; }; BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; }; - BF580492246A2C5C008AE704 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; }; BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; }; BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; }; BF58049B246A432D008AE704 /* NSError+LocalizedFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+LocalizedFailure.swift */; }; @@ -158,7 +160,6 @@ BF7C627423DBB78C00515A2D /* InstalledAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */; }; BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C122E659F700049BA1 /* AppContentViewController.swift */; }; BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C322E662D300049BA1 /* AppViewController.swift */; }; - BF9A03C623C7DD0D000D08DB /* ClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9A03C523C7DD0D000D08DB /* ClientConnection.swift */; }; BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */; }; BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */; }; BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */; }; @@ -262,6 +263,9 @@ BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B695232242D3007A79E1 /* LicensesViewController.swift */; }; BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */; }; BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */; }; + BFF767C82489A74E0097E58C /* WirelessConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */; }; + BFF767CC2489AB5C0097E58C /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; }; + BFF767CE2489ABE90097E58C /* NetworkConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CD2489ABE90097E58C /* NetworkConnection.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -346,8 +350,12 @@ BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStoreToAltStore2.xcmappingmodel; sourceTree = ""; }; BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreAppPolicy.swift; sourceTree = ""; }; BF18B0F022E25DF9005C4CF5 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; + BF18BFF22485828200DD5981 /* ConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; }; + BF18BFF624858BDE00DD5981 /* Connection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; + BF18BFFC2485A1E400DD5981 /* WiredConnectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiredConnectionHandler.swift; sourceTree = ""; }; + BF18BFFE2485A42800DD5981 /* ALTConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConnection.h; sourceTree = ""; }; BF1E3128229F474900370A3C /* ServerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = ""; }; - BF1E3129229F474900370A3C /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; }; + BF1E3129229F474900370A3C /* RequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestHandler.swift; sourceTree = ""; }; BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AltStore.swift"; sourceTree = ""; }; BF1E314722A060F300370A3C /* AltStore-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AltStore-Bridging-Header.h"; sourceTree = ""; }; BF1E314822A060F400370A3C /* NSError+ALTServerError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+ALTServerError.h"; sourceTree = ""; }; @@ -504,7 +512,6 @@ BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledAppPolicy.swift; sourceTree = ""; }; BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = ""; }; BF8F69C322E662D300049BA1 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = ""; }; - BF9A03C523C7DD0D000D08DB /* ClientConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConnection.swift; sourceTree = ""; }; BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = ""; }; BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseCollectionViewCell.swift; sourceTree = ""; }; BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotCollectionViewCell.swift; sourceTree = ""; }; @@ -617,6 +624,9 @@ BFF0B695232242D3007A79E1 /* LicensesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesViewController.swift; sourceTree = ""; }; BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsViewController.swift; sourceTree = ""; }; BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+CompactHeight.swift"; sourceTree = ""; }; + BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WirelessConnectionHandler.swift; sourceTree = ""; }; + BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTServerError+Conveniences.swift"; sourceTree = ""; }; + BFF767CD2489ABE90097E58C /* NetworkConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnection.swift; sourceTree = ""; }; EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = ""; }; FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -735,19 +745,24 @@ path = Policies; sourceTree = ""; }; + BF18BFFF2485A75F00DD5981 /* Server Protocol */ = { + isa = PBXGroup; + children = ( + BF1E3128229F474900370A3C /* ServerProtocol.swift */, + BFD44605241188C300EAB90A /* CodableServerError.swift */, + ); + path = "Server Protocol"; + sourceTree = ""; + }; BF1E315122A0616100370A3C /* AltKit */ = { isa = PBXGroup; children = ( BFD52BD222A06EFB000B7ED1 /* AltKit.h */, BF718BD723C93DB700A89F2D /* AltKit.m */, - BFBAC8852295C90300587369 /* Result+Conveniences.swift */, - BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */, - BF1E3128229F474900370A3C /* ServerProtocol.swift */, - BFD44605241188C300EAB90A /* CodableServerError.swift */, - BF1E314822A060F400370A3C /* NSError+ALTServerError.h */, - BF1E314922A060F400370A3C /* NSError+ALTServerError.m */, - BF718BC723C919CC00A89F2D /* CFNotificationName+AltStore.h */, - BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */, + BF18BFFF2485A75F00DD5981 /* Server Protocol */, + BFF767CF2489AC240097E58C /* Connections */, + BFF767C32489A6800097E58C /* Extensions */, + BFF767C42489A6980097E58C /* Categories */, ); path = AltKit; sourceTree = ""; @@ -1232,8 +1247,9 @@ BFD52BDC22A0A659000B7ED1 /* Connections */ = { isa = PBXGroup; children = ( - BF1E3129229F474900370A3C /* ConnectionManager.swift */, - BF9A03C523C7DD0D000D08DB /* ClientConnection.swift */, + BF1E3129229F474900370A3C /* RequestHandler.swift */, + BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */, + BF18BFFC2485A1E400DD5981 /* WiredConnectionHandler.swift */, BF718BCF23C91BD300A89F2D /* ALTWiredConnection.h */, BF718BD223C91C7000A89F2D /* ALTWiredConnection+Private.h */, BF718BD023C91BD300A89F2D /* ALTWiredConnection.m */, @@ -1320,6 +1336,38 @@ path = Authentication; sourceTree = ""; }; + BFF767C32489A6800097E58C /* Extensions */ = { + isa = PBXGroup; + children = ( + BFBAC8852295C90300587369 /* Result+Conveniences.swift */, + BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */, + BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + BFF767C42489A6980097E58C /* Categories */ = { + isa = PBXGroup; + children = ( + BF1E314822A060F400370A3C /* NSError+ALTServerError.h */, + BF1E314922A060F400370A3C /* NSError+ALTServerError.m */, + BF718BC723C919CC00A89F2D /* CFNotificationName+AltStore.h */, + BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */, + ); + path = Categories; + sourceTree = ""; + }; + BFF767CF2489AC240097E58C /* Connections */ = { + isa = PBXGroup; + children = ( + BF18BFF22485828200DD5981 /* ConnectionManager.swift */, + BF18BFFE2485A42800DD5981 /* ALTConnection.h */, + BF18BFF624858BDE00DD5981 /* Connection.swift */, + BFF767CD2489ABE90097E58C /* NetworkConnection.swift */, + ); + path = Connections; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1729,10 +1777,14 @@ buildActionMask = 2147483647; files = ( BF718BD823C93DB700A89F2D /* AltKit.m in Sources */, + BFF767CE2489ABE90097E58C /* NetworkConnection.swift in Sources */, + BFF767CC2489AB5C0097E58C /* ALTServerError+Conveniences.swift in Sources */, BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */, BF1E315822A061F900370A3C /* Result+Conveniences.swift in Sources */, BF718BC923C919E300A89F2D /* CFNotificationName+AltStore.m in Sources */, BFD44606241188C400EAB90A /* CodableServerError.swift in Sources */, + BF18BFF32485828200DD5981 /* ConnectionManager.swift in Sources */, + BF18BFF724858BDE00DD5981 /* Connection.swift in Sources */, BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */, BF4E8456246F16D700ECCBD4 /* Bundle+AltStore.swift in Sources */, ); @@ -1742,10 +1794,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BFF767C82489A74E0097E58C /* WirelessConnectionHandler.swift in Sources */, BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */, + BF18BFFD2485A1E400DD5981 /* WiredConnectionHandler.swift in Sources */, BF718BD523C928A300A89F2D /* ALTNotificationConnection.m in Sources */, - BF1E312B229F474900370A3C /* ConnectionManager.swift in Sources */, - BF9A03C623C7DD0D000D08DB /* ClientConnection.swift in Sources */, + BF1E312B229F474900370A3C /* RequestHandler.swift in Sources */, BF718BD123C91BD300A89F2D /* ALTWiredConnection.m in Sources */, BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */, BF4586C52298CDB800BD7491 /* ALTDeviceManager.mm in Sources */, @@ -1819,7 +1872,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BF580492246A2C5C008AE704 /* Bundle+AltStore.swift in Sources */, BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */, BF580482246A28F7008AE704 /* ViewController.swift in Sources */, BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,