[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

@@ -2,7 +2,8 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NSError+ALTServerError.h"
#import "AltKit.h"
#import "ALTAppPermission.h"
#import "ALTPatreonBenefitType.h"
#import "ALTSourceUserInfoKey.h"

View File

@@ -40,15 +40,23 @@ class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
ServerManager.shared.connect(to: server) { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .failure(let error):
self.finish(.failure(error))
case .success(let connection):
print("Sending anisette data request...")
let request = AnisetteDataRequest()
connection.send(request) { (result) in
print("Sent anisette data request!")
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success:
print("Waiting for anisette data...")
connection.receiveResponse() { (result) in
print("Receiving anisette data:", result)
switch result
{
case .failure(let error): self.finish(.failure(error))

View File

@@ -7,13 +7,26 @@
//
import Foundation
import AltKit
import Roxas
private extension Notification.Name
{
static let didReceiveWiredServerConnectionResponse = Notification.Name("io.altstore.didReceiveWiredServerConnectionResponse")
}
private let ReceivedWiredServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
{ (center, observer, name, object, userInfo) in
NotificationCenter.default.post(name: .didReceiveWiredServerConnectionResponse, object: nil)
}
@objc(FindServerOperation)
class FindServerOperation: ResultOperation<Server>
{
let group: OperationGroup
private var isWiredServerConnectionAvailable = false
init(group: OperationGroup)
{
self.group = group
@@ -31,21 +44,49 @@ class FindServerOperation: ResultOperation<Server>
return
}
if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
{
// Preferred server.
self.finish(.success(server))
}
else if let server = ServerManager.shared.discoveredServers.first
{
// Any available server.
self.finish(.success(server))
}
else
{
// No servers.
self.finish(.failure(ConnectionError.serverNotFound))
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
// Prepare observers to receive callback from wired server (if connected).
CFNotificationCenterAddObserver(notificationCenter, nil, ReceivedWiredServerConnectionResponse, CFNotificationName.wiredServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately)
NotificationCenter.default.addObserver(self, selector: #selector(FindServerOperation.didReceiveWiredServerConnectionResponse(_:)), name: .didReceiveWiredServerConnectionResponse, object: nil)
// Post notification.
CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionAvailableRequest, nil, nil, true)
// Wait for either callback or timeout.
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
if self.isWiredServerConnectionAvailable
{
let server = Server(isWiredConnection: true)
self.finish(.success(server))
}
else
{
if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
{
// Preferred server.
self.finish(.success(server))
}
else if let server = ServerManager.shared.discoveredServers.first
{
// Any available server.
self.finish(.success(server))
}
else
{
// No servers.
self.finish(.failure(ConnectionError.serverNotFound))
}
}
}
}
}
private extension FindServerOperation
{
@objc func didReceiveWiredServerConnectionResponse(_ notification: Notification)
{
self.isWiredServerConnectionAvailable = true
}
}

View File

@@ -44,17 +44,22 @@ enum ConnectionError: LocalizedError
struct Server: Equatable
{
var identifier: String
var service: NetService
var identifier: String? = nil
var service: NetService? = nil
var isPreferred = false
var isWiredConnection = false
}
extension Server
{
// Defined in extension so we can still use the automatically synthesized initializer.
init?(service: NetService, txtData: Data)
{
{
let txtDictionary = NetService.dictionary(fromTXTRecord: txtData)
guard let identifierData = txtDictionary["serverID"], let identifier = String(data: identifierData, encoding: .utf8) else { return nil }
self.identifier = identifier
self.service = service
self.identifier = identifier
}
}

View File

@@ -19,11 +19,14 @@ class ServerManager: NSObject
private(set) var discoveredServers = [Server]()
private let serviceBrowser = NetServiceBrowser()
private var services = Set<NetService>()
private let dispatchQueue = DispatchQueue(label: "io.altstore.ServerManager")
private lazy var connectionListener = self.makeListener()
private var incomingConnections = [NWConnection]()
private let incomingConnectionsSemaphore = DispatchSemaphore(value: 0)
private override init()
{
super.init()
@@ -41,6 +44,8 @@ extension ServerManager
self.isDiscovering = true
self.serviceBrowser.searchForServices(ofType: ALTServerServiceType, inDomain: "")
self.connectionListener.start(queue: self.dispatchQueue)
}
func stopDiscovering()
@@ -55,30 +60,62 @@ extension ServerManager
func connect(to server: Server, completion: @escaping (Result<ServerConnection, Error>) -> Void)
{
let connection = NWConnection(to: .service(name: server.service.name, type: server.service.type, domain: server.service.domain, interface: nil), using: .tcp)
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
DispatchQueue.global().async {
func finish(_ result: Result<ServerConnection, Error>)
{
case .failed(let error):
print("Failed to connect to service \(server.service.name).", error)
completion(.failure(ConnectionError.connectionFailed))
completion(result)
}
func start(_ connection: NWConnection)
{
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
{
case .failed(let error):
print("Failed to connect to service \(server.service?.name ?? "").", error)
finish(.failure(ConnectionError.connectionFailed))
case .cancelled:
finish(.failure(OperationError.cancelled))
case .ready:
let connection = ServerConnection(server: server, connection: connection)
finish(.success(connection))
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
}
}
case .cancelled:
completion(.failure(OperationError.cancelled))
connection.start(queue: self.dispatchQueue)
}
if server.isWiredConnection
{
print("Waiting for new wired connection...")
case .ready:
let connection = ServerConnection(server: server, connection: connection)
completion(.success(connection))
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true)
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
_ = self.incomingConnectionsSemaphore.wait(timeout: .now() + 10.0)
if let connection = self.incomingConnections.popLast()
{
start(connection)
}
else
{
finish(.failure(ALTServerError(.connectionFailed)))
}
}
else if let service = server.service
{
let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp)
start(connection)
}
}
connection.start(queue: self.dispatchQueue)
}
}
@@ -93,6 +130,27 @@ private extension ServerManager
self.discoveredServers.append(server)
}
func makeListener() -> NWListener
{
let listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(rawValue: ALTDeviceListeningSocket)!)
listener.newConnectionHandler = { [weak self] (connection) in
self?.incomingConnections.append(connection)
self?.incomingConnectionsSemaphore.signal()
}
listener.stateUpdateHandler = { (state) in
switch state
{
case .ready: break
case .waiting, .setup: print("Listener socket waiting...")
case .cancelled: print("Listener socket cancelled.")
case .failed(let error): print("Listener socket failed:", error)
@unknown default: break
}
}
return listener
}
}
extension ServerManager: NetServiceBrowserDelegate