mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 14:53:25 +01:00
Installs apps from AltStore via AltServer
This commit is contained in:
256
AltStore/Server/Server.swift
Normal file
256
AltStore/Server/Server.swift
Normal file
@@ -0,0 +1,256 @@
|
||||
//
|
||||
// Server.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 5/30/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Network
|
||||
|
||||
import AltKit
|
||||
|
||||
extension ALTServerError
|
||||
{
|
||||
init<E: Error>(_ error: E)
|
||||
{
|
||||
switch error
|
||||
{
|
||||
case let error as ALTServerError: self = error
|
||||
case is DecodingError: self = ALTServerError(.invalidResponse)
|
||||
case is EncodingError: self = ALTServerError(.invalidRequest)
|
||||
default:
|
||||
assertionFailure("Caught unknown error type")
|
||||
self = ALTServerError(.unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum InstallError: Error
|
||||
{
|
||||
case unknown
|
||||
case cancelled
|
||||
case invalidApp
|
||||
case noUDID
|
||||
case server(ALTServerError)
|
||||
}
|
||||
|
||||
struct Server: Equatable
|
||||
{
|
||||
var service: NetService
|
||||
|
||||
private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltStore.server", qos: .utility)
|
||||
|
||||
func install(_ app: App, completionHandler: @escaping (Result<Void, InstallError>) -> Void)
|
||||
{
|
||||
let ipaURL = app.ipaURL
|
||||
let appID = app.identifier
|
||||
|
||||
var isFinished = false
|
||||
|
||||
var serverConnection: NWConnection?
|
||||
|
||||
func finish(error: InstallError?)
|
||||
{
|
||||
// Prevent duplicate callbacks if connection is lost.
|
||||
guard !isFinished else { return }
|
||||
isFinished = true
|
||||
|
||||
if let connection = serverConnection
|
||||
{
|
||||
connection.cancel()
|
||||
}
|
||||
|
||||
if let error = error
|
||||
{
|
||||
print("Failed to install \(appID).", error)
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Installed \(appID)!")
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
self.connect { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(error: error)
|
||||
case .success(let connection):
|
||||
serverConnection = connection
|
||||
|
||||
self.sendApp(at: ipaURL, via: connection) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(error: error)
|
||||
case .success:
|
||||
|
||||
self.receiveResponse(from: connection) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .success: finish(error: nil)
|
||||
case .failure(let error): finish(error: .server(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Server
|
||||
{
|
||||
func connect(completionHandler: @escaping (Result<NWConnection, InstallError>) -> Void)
|
||||
{
|
||||
let connection = NWConnection(to: .service(name: self.service.name, type: self.service.type, domain: self.service.domain, interface: nil), using: .tcp)
|
||||
|
||||
connection.stateUpdateHandler = { [weak service, unowned connection] (state) in
|
||||
switch state
|
||||
{
|
||||
case .ready: completionHandler(.success(connection))
|
||||
case .cancelled: completionHandler(.failure(.cancelled))
|
||||
|
||||
case .failed(let error):
|
||||
print("Failed to connect to service \(service?.name ?? "").", error)
|
||||
completionHandler(.failure(.server(.init(.connectionFailed))))
|
||||
|
||||
case .waiting: break
|
||||
case .setup: break
|
||||
case .preparing: break
|
||||
@unknown default: break
|
||||
}
|
||||
}
|
||||
|
||||
connection.start(queue: self.dispatchQueue)
|
||||
}
|
||||
|
||||
func sendApp(at fileURL: URL, via connection: NWConnection, completionHandler: @escaping (Result<Void, InstallError>) -> Void)
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let appData = try? Data(contentsOf: fileURL) else { throw InstallError.invalidApp }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw InstallError.noUDID }
|
||||
|
||||
let request = ServerRequest(udid: udid, contentSize: appData.count)
|
||||
let requestData = try JSONEncoder().encode(request)
|
||||
|
||||
let requestSize = Int32(requestData.count)
|
||||
let requestSizeData = withUnsafeBytes(of: requestSize) { Data($0) }
|
||||
|
||||
// Send request data size.
|
||||
connection.send(content: requestSizeData, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
completionHandler(.failure(.server(.init(.lostConnection))))
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send request.
|
||||
connection.send(content: requestData, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
completionHandler(.failure(.server(.init(.lostConnection))))
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send app data.
|
||||
connection.send(content: appData, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
completionHandler(.failure(.server(.init(.lostConnection))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
catch is EncodingError
|
||||
{
|
||||
completionHandler(.failure(.server(.init(.invalidRequest))))
|
||||
}
|
||||
catch let error as InstallError
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
catch
|
||||
{
|
||||
assertionFailure("Unknown error type. \(error)")
|
||||
completionHandler(.failure(.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
func receiveResponse(from connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
||||
{
|
||||
let size = MemoryLayout<Int32>.size
|
||||
|
||||
connection.receive(minimumIncompleteLength: size, maximumLength: size) { (data, _, _, error) in
|
||||
do
|
||||
{
|
||||
let data = try self.process(data: data, error: error, from: connection)
|
||||
|
||||
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 response = try JSONDecoder().decode(ServerResponse.self, from: data)
|
||||
|
||||
if let error = response.error
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user