[Both] Adds support for installing apps over USB

This commit is contained in:
Riley Testut
2020-01-13 10:17:30 -08:00
parent e0a899ee9a
commit ae98105772
23 changed files with 1044 additions and 210 deletions

View File

@@ -3,3 +3,6 @@
//
#import "ALTDeviceManager.h"
#import "ALTWiredConnection.h"
#import "ALTNotificationConnection.h"
#import "AltKit.h"

View File

@@ -59,7 +59,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
UserDefaults.standard.registerDefaults()
UNUserNotificationCenter.current().delegate = self
ConnectionManager.shared.start()
ALTDeviceManager.shared.start()
let item = NSStatusBar.system.statusItem(withLength: -1)
guard let button = item.button else { return }

View File

@@ -0,0 +1,24 @@
//
// ALTNotificationConnection+Private.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTNotificationConnection.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/notification_proxy.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTNotificationConnection ()
@property (nonatomic, readonly) np_client_t client;
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,30 @@
//
// ALTNotificationConnection.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import <AltSign/AltSign.h>
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(NotificationConnection)
@interface ALTNotificationConnection : NSObject
@property (nonatomic, copy, readonly) ALTDevice *device;
@property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification);
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)sendNotification:(CFNotificationName)notification
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,93 @@
//
// ALTNotificationConnection.m
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTNotificationConnection+Private.h"
#import "AltKit.h"
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
@implementation ALTNotificationConnection
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client
{
self = [super init];
if (self)
{
_device = [device copy];
_client = client;
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
np_client_free(self.client);
_client = nil;
}
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *));
for (int i = 0; i < notifications.count; i++)
{
NSString *name = notifications[i];
notificationNames[i] = name.UTF8String;
}
notificationNames[notifications.count] = NULL; // Must have terminating NULL entry.
np_error_t result = np_observe_notifications(self.client, notificationNames);
if (result != NP_E_SUCCESS)
{
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self);
if (result != NP_E_SUCCESS)
{
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
completionHandler(YES, nil);
free(notificationNames);
});
}
- (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]);
if (result == NP_E_SUCCESS)
{
completionHandler(YES, nil);
}
else
{
completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
});
}
@end
void ALTDeviceReceivedNotification(const char *notification, void *user_data)
{
ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data;
if (connection.receivedNotificationHandler)
{
connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification));
}
}

View File

@@ -0,0 +1,23 @@
//
// ALTWiredConnection+Private.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTWiredConnection.h"
#include <libimobiledevice/libimobiledevice.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTWiredConnection ()
@property (nonatomic, readonly) idevice_connection_t connection;
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,25 @@
//
// ALTWiredConnection.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import <AltSign/AltSign.h>
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(WiredConnection)
@interface ALTWiredConnection : NSObject
@property (nonatomic, copy, readonly) ALTDevice *device;
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler;
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,101 @@
//
// ALTWiredConnection.m
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTWiredConnection+Private.h"
#import "AltKit.h"
@implementation ALTWiredConnection
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection
{
self = [super init];
if (self)
{
_device = [device copy];
_connection = connection;
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
idevice_disconnect(self.connection);
_connection = nil;
}
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
void (^finish)(NSError *error) = ^(NSError *error) {
if (error != nil)
{
NSLog(@"Send Error: %@", error);
completionHandler(NO, error);
}
else
{
completionHandler(YES, nil);
}
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
NSMutableData *mutableData = [data mutableCopy];
while (mutableData.length > 0)
{
uint32_t sentBytes = 0;
if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
[mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0];
}
finish(nil);
});
}
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler
{
void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) {
if (error != nil)
{
NSLog(@"Receive Data Error: %@", error);
}
completionHandler(data, error);
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
char bytes[4096];
NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize];
while (receivedData.length < expectedSize)
{
uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length);
uint32_t receivedBytes = 0;
if (idevice_connection_receive(self.connection, bytes, size, &receivedBytes) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO];
[receivedData appendData:data];
}
finish(receivedData, nil);
});
}
@end

View File

@@ -0,0 +1,231 @@
//
// 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<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) }
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<ServerRequest, ALTServerError>) -> Void)
{
let size = MemoryLayout<Int32>.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, Error>) -> 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<Data, Error>) -> 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.")
}
}
}

View File

@@ -54,10 +54,13 @@ class ConnectionManager
private lazy var listener = self.makeListener()
private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltServer.connections", qos: .utility)
private var connections = [NWConnection]()
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()
@@ -77,6 +80,16 @@ class ConnectionManager
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
@@ -127,67 +140,18 @@ private extension ConnectionManager
}
listener.newConnectionHandler = { [weak self] (connection) in
self?.awaitRequest(from: connection)
self?.prepare(connection)
}
return listener
}
func disconnect(_ connection: NWConnection)
func prepare(_ connection: NWConnection)
{
switch connection.state
{
case .cancelled, .failed:
print("Disconnecting from \(connection.endpoint)...")
if let index = self.connections.firstIndex(where: { $0 === connection })
{
self.connections.remove(at: index)
}
default:
// State update handler will call this method again.
connection.cancel()
}
}
func process(data: Data?, error: NWError?, from connection: NWConnection) 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.")
}
}
}
private extension ConnectionManager
{
func awaitRequest(from connection: NWConnection)
{
guard !self.connections.contains(where: { $0 === connection }) else { return }
self.connections.append(connection)
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
@@ -196,17 +160,17 @@ private extension ConnectionManager
case .ready:
print("Connected to client:", connection.endpoint)
self?.handleRequest(for: connection)
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(connection)
self?.disconnect(clientConnection)
case .cancelled:
self?.disconnect(connection)
self?.disconnect(clientConnection)
@unknown default: break
}
@@ -214,17 +178,85 @@ private extension ConnectionManager
connection.start(queue: self.dispatchQueue)
}
func handleRequest(for connection: NWConnection)
}
private extension ConnectionManager
{
func startNotificationConnection(to device: ALTDevice)
{
self.receiveRequest(from: connection) { (result) in
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))
self.send(response, to: connection, shouldDisconnect: true) { (result) in
connection.send(response, shouldDisconnect: true) { (result) in
print("Sent error response with result:", result)
}
@@ -236,34 +268,34 @@ private extension ConnectionManager
case .success:
let response = ErrorResponse(error: ALTServerError(.unknownRequest))
self.send(response, to: connection, shouldDisconnect: true) { (result) in
connection.send(response, shouldDisconnect: true) { (result) in
print("Sent unknown request response with result:", result)
}
}
}
}
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: NWConnection)
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: ClientConnection)
{
AnisetteDataManager.shared.requestAnisetteData { (result) in
switch result
{
case .failure(let error):
let errorResponse = ErrorResponse(error: ALTServerError(error))
self.send(errorResponse, to: connection, shouldDisconnect: true) { (result) in
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)
self.send(response, to: connection, shouldDisconnect: true) { (result) in
connection.send(response, shouldDisconnect: true) { (result) in
print("Sent anisette data response with result:", result)
}
}
}
}
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: NWConnection)
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: ClientConnection)
{
var temporaryURL: URL?
@@ -278,19 +310,19 @@ private extension ConnectionManager
switch result
{
case .failure(let error):
print("Failed to process request from \(connection.endpoint).", error)
print("Failed to process request from \(connection).", error)
let response = ErrorResponse(error: ALTServerError(error))
self.send(response, to: connection, shouldDisconnect: true) { (result) in
print("Sent install app error response to \(connection.endpoint) with result:", result)
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.endpoint).")
print("Processed request from \(connection).")
let response = InstallationProgressResponse(progress: 1.0)
self.send(response, to: connection, shouldDisconnect: true) { (result) in
print("Sent install app response to \(connection.endpoint) with result:", result)
connection.send(response, shouldDisconnect: true) { (result) in
print("Sent install app response to \(connection) with result:", result)
}
}
}
@@ -306,7 +338,7 @@ private extension ConnectionManager
print("Awaiting begin installation request...")
self.receiveRequest(from: connection) { (result) in
connection.receiveRequest() { (result) in
print("Received begin installation request with result:", result)
switch result
@@ -326,8 +358,8 @@ private extension ConnectionManager
case .success:
let response = ErrorResponse(error: ALTServerError(.unknownRequest))
self.send(response, to: connection, shouldDisconnect: true) { (result) in
print("Sent unknown request error response to \(connection.endpoint) with result:", result)
connection.send(response, shouldDisconnect: true) { (result) in
print("Sent unknown request error response to \(connection) with result:", result)
}
}
}
@@ -335,17 +367,15 @@ private extension ConnectionManager
}
}
func receiveApp(for request: PrepareAppRequest, from connection: NWConnection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
func receiveApp(for request: PrepareAppRequest, from connection: ClientConnection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
{
connection.receive(minimumIncompleteLength: request.contentSize, maximumLength: request.contentSize) { (data, _, _, error) in
connection.receiveData(expectedBytes: request.contentSize) { (result) in
do
{
print("Received app data!")
let data = try self.process(data: data, error: error, from: connection)
print("Processed 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...")
@@ -366,7 +396,7 @@ private extension ConnectionManager
}
}
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, connection: ClientConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
{
let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
var isSending = false
@@ -397,7 +427,7 @@ private extension ConnectionManager
print("Progress:", progress.fractionCompleted)
let response = InstallationProgressResponse(progress: progress.fractionCompleted)
self.send(response, to: connection) { (result) in
connection.send(response) { (result) in
serialQueue.async {
isSending = false
}
@@ -405,92 +435,19 @@ private extension ConnectionManager
}
})
}
}
func send<T: Encodable>(_ response: T, to connection: NWConnection, shouldDisconnect: Bool = false, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
private extension ConnectionManager
{
@objc func deviceDidConnect(_ notification: Notification)
{
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(connection)
}
}
}
do
{
let data = try JSONEncoder().encode(response)
let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) }
connection.send(content: responseSize, completion: .contentProcessed { (error) in
do
{
if let error = error
{
throw error
}
connection.send(content: data, completion: .contentProcessed { (error) in
if error != nil
{
finish(.failure(.init(.lostConnection)))
}
else
{
finish(.success(()))
}
})
}
catch
{
finish(.failure(.init(.lostConnection)))
}
})
}
catch
{
finish(.failure(.init(.invalidResponse)))
}
guard let device = notification.object as? ALTDevice else { return }
self.startNotificationConnection(to: device)
}
func receiveRequest(from connection: NWConnection, completionHandler: @escaping (Result<ServerRequest, ALTServerError>) -> Void)
@objc func deviceDidDisconnect(_ notification: Notification)
{
let size = MemoryLayout<Int32>.size
print("Receiving request size")
connection.receive(minimumIncompleteLength: size, maximumLength: size) { (data, _, _, error) in
do
{
let data = try self.process(data: data, error: error, from: connection)
print("Receiving request...")
let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in
do
{
let data = try self.process(data: data, error: error, from: connection)
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)))
}
}
guard let device = notification.object as? ALTDevice else { return }
self.stopNotificationConnection(to: device)
}
}

View File

@@ -9,8 +9,14 @@
#import <Foundation/Foundation.h>
#import <AltSign/AltSign.h>
@class ALTWiredConnection;
@class ALTNotificationConnection;
NS_ASSUME_NONNULL_BEGIN
extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect);
extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect);
@interface ALTDeviceManager : NSObject
@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager;
@@ -18,8 +24,15 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) NSArray<ALTDevice *> *connectedDevices;
@property (nonatomic, readonly) NSArray<ALTDevice *> *availableDevices;
- (void)start;
/* App Installation */
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
/* Connections */
- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -7,7 +7,10 @@
//
#import "ALTDeviceManager.h"
#import "NSError+ALTServerError.h"
#import "AltKit.h"
#import "ALTWiredConnection+Private.h"
#import "ALTNotificationConnection+Private.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
@@ -17,8 +20,10 @@
#include <libimobiledevice/misagent.h>
void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *udid);
void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data);
NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification = @"ALTDeviceManagerDeviceDidConnectNotification";
NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALTDeviceManagerDeviceDidDisconnectNotification";
@interface ALTDeviceManager ()
@@ -26,6 +31,8 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, NSProgress *> *installationProgress;
@property (nonatomic, readonly) dispatch_queue_t installationQueue;
@property (nonatomic, readonly) NSMutableSet<ALTDevice *> *cachedDevices;
@end
@implementation ALTDeviceManager
@@ -50,11 +57,20 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
_installationProgress = [NSMutableDictionary dictionary];
_installationQueue = dispatch_queue_create("com.rileytestut.AltServer.InstallationQueue", DISPATCH_QUEUE_SERIAL);
_cachedDevices = [NSMutableSet set];
}
return self;
}
- (void)start
{
idevice_event_subscribe(ALTDeviceDidChangeConnectionStatus, nil);
}
#pragma mark - App Installation -
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
NSProgress *progress = [NSProgress discreteProgressWithTotalUnitCount:4];
@@ -527,6 +543,89 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
return success;
}
#pragma mark - Connections -
- (void)startWiredConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTWiredConnection * _Nullable, NSError * _Nullable))completionHandler
{
void (^finish)(ALTWiredConnection *connection, NSError *error) = ^(ALTWiredConnection *connection, NSError *error) {
if (error != nil)
{
NSLog(@"Wired Connection Error: %@", error);
}
completionHandler(connection, error);
};
idevice_t device = NULL;
idevice_connection_t connection = NULL;
/* Find Device */
if (idevice_new_ignore_network(&device, altDevice.identifier.UTF8String) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
}
/* Connect to Listening Socket */
if (idevice_connect(device, ALTDeviceListeningSocket, &connection) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
idevice_free(device);
ALTWiredConnection *wiredConnection = [[ALTWiredConnection alloc] initWithDevice:altDevice connection:connection];
finish(wiredConnection, nil);
}
- (void)startNotificationConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTNotificationConnection * _Nullable, NSError * _Nullable))completionHandler
{
void (^finish)(ALTNotificationConnection *, NSError *) = ^(ALTNotificationConnection *connection, NSError *error) {
if (error != nil)
{
NSLog(@"Notification Connection Error: %@", error);
}
completionHandler(connection, error);
};
idevice_t device = NULL;
lockdownd_client_t lockdownClient = NULL;
lockdownd_service_descriptor_t service = NULL;
np_client_t client = NULL;
/* Find Device */
if (idevice_new_ignore_network(&device, altDevice.identifier.UTF8String) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
}
/* Connect to Device */
if (lockdownd_client_new_with_handshake(device, &lockdownClient, "altserver") != LOCKDOWN_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
/* Connect to Notification Proxy */
if ((lockdownd_start_service(lockdownClient, "com.apple.mobile.notification_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
/* Connect to Client */
if (np_client_new(device, service, &client) != NP_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
lockdownd_service_descriptor_free(service);
lockdownd_client_free(lockdownClient);
idevice_free(device);
ALTNotificationConnection *notificationConnection = [[ALTNotificationConnection alloc] initWithDevice:altDevice client:client];
completionHandler(notificationConnection, nil);
}
#pragma mark - Getters -
- (NSArray<ALTDevice *> *)connectedDevices
@@ -683,3 +782,49 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
NSLog(@"Installation Progress: %@", @(percent));
}
}
void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data)
{
ALTDevice * (^deviceForUDID)(NSString *, NSArray<ALTDevice *> *) = ^ALTDevice *(NSString *udid, NSArray<ALTDevice *> *devices) {
for (ALTDevice *device in devices)
{
if ([device.identifier isEqualToString:udid])
{
return device;
}
}
return nil;
};
switch (event->event)
{
case IDEVICE_DEVICE_ADD:
{
ALTDevice *device = deviceForUDID(@(event->udid), ALTDeviceManager.sharedManager.connectedDevices);
[[NSNotificationCenter defaultCenter] postNotificationName:ALTDeviceManagerDeviceDidConnectNotification object:device];
if (device)
{
[ALTDeviceManager.sharedManager.cachedDevices addObject:device];
}
break;
}
case IDEVICE_DEVICE_REMOVE:
{
ALTDevice *device = deviceForUDID(@(event->udid), ALTDeviceManager.sharedManager.cachedDevices.allObjects);
[[NSNotificationCenter defaultCenter] postNotificationName:ALTDeviceManagerDeviceDidDisconnectNotification object:device];
if (device)
{
[ALTDeviceManager.sharedManager.cachedDevices removeObject:device];
}
break;
}
default: break;
}
}