[AltKit] Replaces dedicated AltKit module with shared files across targets

Treating AltKit as a full module resulted in more complexity than necessary, when we really just wanted to share some files between different targets. Now we can share individual files across modules as-needed without AltKit overhead.
This commit is contained in:
Riley Testut
2020-08-31 13:58:44 -07:00
parent 3def65f501
commit e75d184194
51 changed files with 115 additions and 282 deletions

11
Shared/ALTConstants.h Normal file
View File

@@ -0,0 +1,11 @@
//
// ALTConstants.h
// AltKit
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
extern uint16_t ALTDeviceListeningSocket;

11
Shared/ALTConstants.m Normal file
View File

@@ -0,0 +1,11 @@
//
// ALTConstants.m
// AltKit
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
uint16_t ALTDeviceListeningSocket = 28151;

View File

@@ -0,0 +1,21 @@
//
// CFNotificationName+AltStore.h
// AltKit
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
extern CFNotificationName const ALTWiredServerConnectionAvailableRequest NS_SWIFT_NAME(wiredServerConnectionAvailableRequest);
extern CFNotificationName const ALTWiredServerConnectionAvailableResponse NS_SWIFT_NAME(wiredServerConnectionAvailableResponse);
extern CFNotificationName const ALTWiredServerConnectionStartRequest NS_SWIFT_NAME(wiredServerConnectionStartRequest);
extern CFNotificationName const ALTLocalServerConnectionAvailableRequest NS_SWIFT_NAME(localServerConnectionAvailableRequest);
extern CFNotificationName const ALTLocalServerConnectionAvailableResponse NS_SWIFT_NAME(localServerConnectionAvailableResponse);
extern CFNotificationName const ALTLocalServerConnectionStartRequest NS_SWIFT_NAME(localServerConnectionStartRequest);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,17 @@
//
// CFNotificationName+AltStore.m
// AltKit
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "CFNotificationName+AltStore.h"
CFNotificationName const ALTWiredServerConnectionAvailableRequest = CFSTR("io.altstore.Request.WiredServerConnectionAvailable");
CFNotificationName const ALTWiredServerConnectionAvailableResponse = CFSTR("io.altstore.Response.WiredServerConnectionAvailable");
CFNotificationName const ALTWiredServerConnectionStartRequest = CFSTR("io.altstore.Request.WiredServerConnectionStart");
CFNotificationName const ALTLocalServerConnectionAvailableRequest = CFSTR("io.altstore.Request.LocalServerConnectionAvailable");
CFNotificationName const ALTLocalServerConnectionAvailableResponse = CFSTR("io.altstore.Response.LocalServerConnectionAvailable");
CFNotificationName const ALTLocalServerConnectionStartRequest = CFSTR("io.altstore.Request.LocalServerConnectionStart");

View File

@@ -0,0 +1,52 @@
//
// NSError+ALTServerError.h
// AltStore
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSErrorDomain const AltServerErrorDomain;
extern NSErrorDomain const AltServerInstallationErrorDomain;
extern NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey;
extern NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey;
typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError)
{
ALTServerErrorUnderlyingError = -1,
ALTServerErrorUnknown = 0,
ALTServerErrorConnectionFailed = 1,
ALTServerErrorLostConnection = 2,
ALTServerErrorDeviceNotFound = 3,
ALTServerErrorDeviceWriteFailed = 4,
ALTServerErrorInvalidRequest = 5,
ALTServerErrorInvalidResponse = 6,
ALTServerErrorInvalidApp = 7,
ALTServerErrorInstallationFailed = 8,
ALTServerErrorMaximumFreeAppLimitReached = 9,
ALTServerErrorUnsupportediOSVersion = 10,
ALTServerErrorUnknownRequest = 11,
ALTServerErrorUnknownResponse = 12,
ALTServerErrorInvalidAnisetteData = 13,
ALTServerErrorPluginNotFound = 14,
ALTServerErrorProfileNotFound = 15,
ALTServerErrorAppDeletionFailed = 16,
};
NS_ASSUME_NONNULL_BEGIN
@interface NSError (ALTServerError)
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,150 @@
//
// NSError+ALTServerError.m
// AltStore
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "NSError+ALTServerError.h"
NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer";
NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation";
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdentifier";
@implementation NSError (ALTServerError)
+ (void)load
{
[NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
{
return [error altserver_localizedFailureReason];
}
if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey])
{
return [error altserver_localizedRecoverySuggestion];
}
return nil;
}];
}
- (nullable NSString *)altserver_localizedFailureReason
{
switch ((ALTServerError)self.code)
{
case ALTServerErrorUnderlyingError:
{
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
if (underlyingError.localizedFailureReason != nil)
{
return underlyingError.localizedFailureReason;
}
NSString *underlyingErrorCode = self.userInfo[ALTUnderlyingErrorCodeErrorKey];
if (underlyingErrorCode != nil)
{
return [NSString stringWithFormat:NSLocalizedString(@"Error code: %@", @""), underlyingErrorCode];
}
return NSLocalizedString(@"An internal error occured.", @"");
}
case ALTServerErrorUnknown:
return NSLocalizedString(@"An unknown error occured.", @"");
case ALTServerErrorConnectionFailed:
#if TARGET_OS_OSX
return NSLocalizedString(@"Could not connect to device.", @"");
#else
return NSLocalizedString(@"Could not connect to AltServer.", @"");
#endif
case ALTServerErrorLostConnection:
return NSLocalizedString(@"Lost connection to AltServer.", @"");
case ALTServerErrorDeviceNotFound:
return NSLocalizedString(@"AltServer could not find this device.", @"");
case ALTServerErrorDeviceWriteFailed:
return NSLocalizedString(@"Failed to write app data to device.", @"");
case ALTServerErrorInvalidRequest:
return NSLocalizedString(@"AltServer received an invalid request.", @"");
case ALTServerErrorInvalidResponse:
return NSLocalizedString(@"AltServer sent an invalid response.", @"");
case ALTServerErrorInvalidApp:
return NSLocalizedString(@"The app is invalid.", @"");
case ALTServerErrorInstallationFailed:
return NSLocalizedString(@"An error occured while installing the app.", @"");
case ALTServerErrorMaximumFreeAppLimitReached:
return NSLocalizedString(@"Cannot activate more than 3 apps and app extensions.", @"");
case ALTServerErrorUnsupportediOSVersion:
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install AltStore.", @"");
case ALTServerErrorUnknownRequest:
return NSLocalizedString(@"AltServer does not support this request.", @"");
case ALTServerErrorUnknownResponse:
return NSLocalizedString(@"Received an unknown response from AltServer.", @"");
case ALTServerErrorInvalidAnisetteData:
return NSLocalizedString(@"Invalid anisette data.", @"");
case ALTServerErrorPluginNotFound:
return NSLocalizedString(@"Could not connect to Mail plug-in.", @"");
case ALTServerErrorProfileNotFound:
return [self profileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not find profile", "")];
case ALTServerErrorAppDeletionFailed:
return NSLocalizedString(@"An error occured while removing the app.", @"");
}
}
- (nullable NSString *)altserver_localizedRecoverySuggestion
{
switch ((ALTServerError)self.code)
{
case ALTServerErrorConnectionFailed:
case ALTServerErrorDeviceNotFound:
return NSLocalizedString(@"Make sure you have trusted this device with your computer and WiFi sync is enabled.", @"");
case ALTServerErrorPluginNotFound:
return NSLocalizedString(@"Make sure Mail is running and the plug-in is enabled in Mail's preferences.", @"");
case ALTServerErrorMaximumFreeAppLimitReached:
return NSLocalizedString(@"Make sure “Offload Unused Apps” is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps.", @"");
default:
return nil;
}
}
- (NSString *)profileErrorLocalizedDescriptionWithBaseDescription:(NSString *)baseDescription
{
NSString *localizedDescription = nil;
NSString *bundleID = self.userInfo[ALTProvisioningProfileBundleIDErrorKey];
if (bundleID)
{
localizedDescription = [NSString stringWithFormat:@"%@ “%@”", baseDescription, bundleID];
}
else
{
localizedDescription = [NSString stringWithFormat:@"%@.", baseDescription];
}
return localizedDescription;
}
@end

View File

@@ -0,0 +1,23 @@
//
// ALTConnection.h
// AltKit
//
// Created by Riley Testut on 6/1/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(Connection)
@protocol ALTConnection <NSObject>
- (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

View File

@@ -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, ALTServerError>) -> 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<Data, ALTServerError>) -> 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<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(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<ServerRequest, ALTServerError>) -> Void)
{
let size = MemoryLayout<Int32>.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)))
}
}
}
}

View File

@@ -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<AnisetteDataResponse, Error>) -> Void)
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result<InstallationProgressResponse, Error>) -> Void)
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
}
public protocol ConnectionHandler: AnyObject
{
var connectionHandler: ((Connection) -> Void)? { get set }
var disconnectionHandler: ((Connection) -> Void)? { get set }
func startListening()
func stopListening()
}
public class ConnectionManager<RequestHandlerType: RequestHandler>
{
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<T: ServerMessageProtocol>(_ result: Result<T, Error>)
{
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<ErrorResponse, Error>.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<ErrorResponse, Error>.failure(ALTServerError(.unknownRequest)))
}
}
}
}

View File

@@ -0,0 +1,54 @@
//
// 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
guard data != nil || error != nil else {
return completionHandler(nil, ALTServerError(.lostConnection))
}
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)"
}
}

View File

@@ -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<E: Error>(_ 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(.underlyingError, userInfo: userInfo)
}
}
init<E: Error>(_ code: ALTServerError.Code, underlyingError: E)
{
self = ALTServerError(code, userInfo: [NSUnderlyingErrorKey: underlyingError])
}
}

View File

@@ -0,0 +1,58 @@
//
// Bundle+AltStore.swift
// AltStore
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
public extension Bundle
{
struct Info
{
public static let deviceID = "ALTDeviceID"
public static let serverID = "ALTServerID"
public static let certificateID = "ALTCertificateID"
public static let appGroups = "ALTAppGroups"
public static let altBundleID = "ALTBundleIdentifier"
public static let urlTypes = "CFBundleURLTypes"
public static let exportedUTIs = "UTExportedTypeDeclarations"
}
}
public extension Bundle
{
static var baseAltStoreAppGroupID = "group.com.rileytestut.AltStore"
var infoPlistURL: URL {
let infoPlistURL = self.bundleURL.appendingPathComponent("Info.plist")
return infoPlistURL
}
var provisioningProfileURL: URL {
let infoPlistURL = self.bundleURL.appendingPathComponent("embedded.mobileprovision")
return infoPlistURL
}
var certificateURL: URL {
let infoPlistURL = self.bundleURL.appendingPathComponent("ALTCertificate.p12")
return infoPlistURL
}
var appGroups: [String] {
return self.infoDictionary?[Bundle.Info.appGroups] as? [String] ?? []
}
var altstoreAppGroup: String? {
let appGroup = self.appGroups.first { $0.contains(Bundle.baseAltStoreAppGroupID) }
return appGroup
}
var completeInfoDictionary: [String : Any]? {
let infoPlistURL = self.infoPlistURL
return NSDictionary(contentsOf: infoPlistURL) as? [String : Any]
}
}

View File

@@ -0,0 +1,76 @@
//
// Result+Conveniences.swift
// AltStore
//
// Created by Riley Testut on 5/22/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
public extension Result
{
var value: Success? {
switch self
{
case .success(let value): return value
case .failure: return nil
}
}
var error: Failure? {
switch self
{
case .success: return nil
case .failure(let error): return error
}
}
init(_ value: Success?, _ error: Failure?)
{
switch (value, error)
{
case (let value?, _): self = .success(value)
case (_, let error?): self = .failure(error)
case (nil, nil): preconditionFailure("Either value or error must be non-nil")
}
}
}
public extension Result where Success == Void
{
init(_ success: Bool, _ error: Failure?)
{
if success
{
self = .success(())
}
else if let error = error
{
self = .failure(error)
}
else
{
preconditionFailure("Error must be non-nil if success is false")
}
}
}
public extension Result
{
init<T, U>(_ values: (T?, U?), _ error: Failure?) where Success == (T, U)
{
if let value1 = values.0, let value2 = values.1
{
self = .success((value1, value2))
}
else if let error = error
{
self = .failure(error)
}
else
{
preconditionFailure("Error must be non-nil if either provided values are nil")
}
}
}

View File

@@ -0,0 +1,126 @@
//
// CodableServerError.swift
// AltKit
//
// Created by Riley Testut on 3/5/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
extension ALTServerError.Code: Codable {}
extension CodableServerError
{
enum UserInfoValue: Codable
{
case string(String)
case error(NSError)
public init(from decoder: Decoder) throws
{
let container = try decoder.singleValueContainer()
if
let data = try? container.decode(Data.self),
let error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: data)
{
self = .error(error)
}
else if let string = try? container.decode(String.self)
{
self = .string(string)
}
else
{
throw DecodingError.dataCorruptedError(in: container, debugDescription: "UserInfoValue value cannot be decoded")
}
}
func encode(to encoder: Encoder) throws
{
var container = encoder.singleValueContainer()
switch self
{
case .string(let string): try container.encode(string)
case .error(let error):
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: error, requiringSecureCoding: true) else {
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "UserInfoValue value \(self) cannot be encoded")
throw EncodingError.invalidValue(self, context)
}
try container.encode(data)
}
}
}
}
struct CodableServerError: Codable
{
var error: ALTServerError {
return ALTServerError(self.errorCode, userInfo: self.userInfo ?? [:])
}
private var errorCode: ALTServerError.Code
private var userInfo: [String: Any]?
private enum CodingKeys: String, CodingKey
{
case errorCode
case userInfo
}
init(error: ALTServerError)
{
self.errorCode = error.code
var userInfo = error.userInfo
if let localizedRecoverySuggestion = (error as NSError).localizedRecoverySuggestion
{
userInfo[NSLocalizedRecoverySuggestionErrorKey] = localizedRecoverySuggestion
}
if !userInfo.isEmpty
{
self.userInfo = userInfo
}
}
init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
let errorCode = try container.decode(Int.self, forKey: .errorCode)
self.errorCode = ALTServerError.Code(rawValue: errorCode) ?? .unknown
let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .userInfo)
let userInfo = rawUserInfo?.mapValues { (value) -> Any in
switch value
{
case .string(let string): return string
case .error(let error): return error
}
}
self.userInfo = userInfo
}
func encode(to encoder: Encoder) throws
{
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.error.code.rawValue, forKey: .errorCode)
let rawUserInfo = self.userInfo?.compactMapValues { (value) -> UserInfoValue? in
switch value
{
case let string as String: return .string(string)
case let error as NSError: return .error(error)
default: return nil
}
}
try container.encodeIfPresent(rawUserInfo, forKey: .userInfo)
}
}

View File

@@ -0,0 +1,426 @@
//
// ServerProtocol.swift
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import AltSign
public let ALTServerServiceType = "_altserver._tcp"
protocol ServerMessageProtocol: Codable
{
var version: Int { get }
var identifier: String { get }
}
public enum ServerRequest: Decodable
{
case anisetteData(AnisetteDataRequest)
case prepareApp(PrepareAppRequest)
case beginInstallation(BeginInstallationRequest)
case installProvisioningProfiles(InstallProvisioningProfilesRequest)
case removeProvisioningProfiles(RemoveProvisioningProfilesRequest)
case removeApp(RemoveAppRequest)
case unknown(identifier: String, version: Int)
var identifier: String {
switch self
{
case .anisetteData(let request): return request.identifier
case .prepareApp(let request): return request.identifier
case .beginInstallation(let request): return request.identifier
case .installProvisioningProfiles(let request): return request.identifier
case .removeProvisioningProfiles(let request): return request.identifier
case .removeApp(let request): return request.identifier
case .unknown(let identifier, _): return identifier
}
}
var version: Int {
switch self
{
case .anisetteData(let request): return request.version
case .prepareApp(let request): return request.version
case .beginInstallation(let request): return request.version
case .installProvisioningProfiles(let request): return request.version
case .removeProvisioningProfiles(let request): return request.version
case .removeApp(let request): return request.version
case .unknown(_, let version): return version
}
}
private enum CodingKeys: String, CodingKey
{
case identifier
case version
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
let version = try container.decode(Int.self, forKey: .version)
let identifier = try container.decode(String.self, forKey: .identifier)
switch identifier
{
case "AnisetteDataRequest":
let request = try AnisetteDataRequest(from: decoder)
self = .anisetteData(request)
case "PrepareAppRequest":
let request = try PrepareAppRequest(from: decoder)
self = .prepareApp(request)
case "BeginInstallationRequest":
let request = try BeginInstallationRequest(from: decoder)
self = .beginInstallation(request)
case "InstallProvisioningProfilesRequest":
let request = try InstallProvisioningProfilesRequest(from: decoder)
self = .installProvisioningProfiles(request)
case "RemoveProvisioningProfilesRequest":
let request = try RemoveProvisioningProfilesRequest(from: decoder)
self = .removeProvisioningProfiles(request)
case "RemoveAppRequest":
let request = try RemoveAppRequest(from: decoder)
self = .removeApp(request)
default:
self = .unknown(identifier: identifier, version: version)
}
}
}
public enum ServerResponse: Decodable
{
case anisetteData(AnisetteDataResponse)
case installationProgress(InstallationProgressResponse)
case installProvisioningProfiles(InstallProvisioningProfilesResponse)
case removeProvisioningProfiles(RemoveProvisioningProfilesResponse)
case removeApp(RemoveAppResponse)
case error(ErrorResponse)
case unknown(identifier: String, version: Int)
var identifier: String {
switch self
{
case .anisetteData(let response): return response.identifier
case .installationProgress(let response): return response.identifier
case .installProvisioningProfiles(let response): return response.identifier
case .removeProvisioningProfiles(let response): return response.identifier
case .removeApp(let response): return response.identifier
case .error(let response): return response.identifier
case .unknown(let identifier, _): return identifier
}
}
var version: Int {
switch self
{
case .anisetteData(let response): return response.version
case .installationProgress(let response): return response.version
case .installProvisioningProfiles(let response): return response.version
case .removeProvisioningProfiles(let response): return response.version
case .removeApp(let response): return response.version
case .error(let response): return response.version
case .unknown(_, let version): return version
}
}
private enum CodingKeys: String, CodingKey
{
case identifier
case version
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
let version = try container.decode(Int.self, forKey: .version)
let identifier = try container.decode(String.self, forKey: .identifier)
switch identifier
{
case "AnisetteDataResponse":
let response = try AnisetteDataResponse(from: decoder)
self = .anisetteData(response)
case "InstallationProgressResponse":
let response = try InstallationProgressResponse(from: decoder)
self = .installationProgress(response)
case "InstallProvisioningProfilesResponse":
let response = try InstallProvisioningProfilesResponse(from: decoder)
self = .installProvisioningProfiles(response)
case "RemoveProvisioningProfilesResponse":
let response = try RemoveProvisioningProfilesResponse(from: decoder)
self = .removeProvisioningProfiles(response)
case "RemoveAppResponse":
let response = try RemoveAppResponse(from: decoder)
self = .removeApp(response)
case "ErrorResponse":
let response = try ErrorResponse(from: decoder)
self = .error(response)
default:
self = .unknown(identifier: identifier, version: version)
}
}
}
// _Don't_ provide generic SuccessResponse, as that would prevent us
// from easily changing response format for a request in the future.
public struct ErrorResponse: ServerMessageProtocol
{
public var version = 2
public var identifier = "ErrorResponse"
public var error: ALTServerError {
return self.serverError?.error ?? ALTServerError(self.errorCode)
}
private var serverError: CodableServerError?
// Legacy (v1)
private var errorCode: ALTServerError.Code
public init(error: ALTServerError)
{
self.serverError = CodableServerError(error: error)
self.errorCode = error.code
}
}
public struct AnisetteDataRequest: ServerMessageProtocol
{
public var version = 1
public var identifier = "AnisetteDataRequest"
public init()
{
}
}
public struct AnisetteDataResponse: ServerMessageProtocol
{
public var version = 1
public var identifier = "AnisetteDataResponse"
public var anisetteData: ALTAnisetteData
private enum CodingKeys: String, CodingKey
{
case identifier
case version
case anisetteData
}
public init(anisetteData: ALTAnisetteData)
{
self.anisetteData = anisetteData
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.version = try container.decode(Int.self, forKey: .version)
self.identifier = try container.decode(String.self, forKey: .identifier)
let json = try container.decode([String: String].self, forKey: .anisetteData)
if let anisetteData = ALTAnisetteData(json: json)
{
self.anisetteData = anisetteData
}
else
{
throw DecodingError.dataCorruptedError(forKey: CodingKeys.anisetteData, in: container, debugDescription: "Couuld not parse anisette data from JSON")
}
}
public func encode(to encoder: Encoder) throws
{
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.version, forKey: .version)
try container.encode(self.identifier, forKey: .identifier)
let json = self.anisetteData.json()
try container.encode(json, forKey: .anisetteData)
}
}
public struct PrepareAppRequest: ServerMessageProtocol
{
public var version = 1
public var identifier = "PrepareAppRequest"
public var udid: String
public var contentSize: Int
public var fileURL: URL?
public init(udid: String, contentSize: Int, fileURL: URL? = nil)
{
self.udid = udid
self.contentSize = contentSize
self.fileURL = fileURL
}
}
public struct BeginInstallationRequest: ServerMessageProtocol
{
public var version = 3
public var identifier = "BeginInstallationRequest"
// If activeProfiles is non-nil, then AltServer should remove all profiles except active ones.
public var activeProfiles: Set<String>?
public var bundleIdentifier: String?
public init(activeProfiles: Set<String>?, bundleIdentifier: String?)
{
self.activeProfiles = activeProfiles
self.bundleIdentifier = bundleIdentifier
}
}
public struct InstallationProgressResponse: ServerMessageProtocol
{
public var version = 1
public var identifier = "InstallationProgressResponse"
public var progress: Double
public init(progress: Double)
{
self.progress = progress
}
}
public struct InstallProvisioningProfilesRequest: ServerMessageProtocol
{
public var version = 1
public var identifier = "InstallProvisioningProfilesRequest"
public var udid: String
public var provisioningProfiles: Set<ALTProvisioningProfile>
// If activeProfiles is non-nil, then AltServer should remove all profiles except active ones.
public var activeProfiles: Set<String>?
private enum CodingKeys: String, CodingKey
{
case identifier
case version
case udid
case provisioningProfiles
case activeProfiles
}
public init(udid: String, provisioningProfiles: Set<ALTProvisioningProfile>, activeProfiles: Set<String>?)
{
self.udid = udid
self.provisioningProfiles = provisioningProfiles
self.activeProfiles = activeProfiles
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.version = try container.decode(Int.self, forKey: .version)
self.identifier = try container.decode(String.self, forKey: .identifier)
self.udid = try container.decode(String.self, forKey: .udid)
let rawProvisioningProfiles = try container.decode([Data].self, forKey: .provisioningProfiles)
let provisioningProfiles = try rawProvisioningProfiles.map { (data) -> ALTProvisioningProfile in
guard let profile = ALTProvisioningProfile(data: data) else {
throw DecodingError.dataCorruptedError(forKey: CodingKeys.provisioningProfiles, in: container, debugDescription: "Could not parse provisioning profile from data.")
}
return profile
}
self.provisioningProfiles = Set(provisioningProfiles)
self.activeProfiles = try container.decodeIfPresent(Set<String>.self, forKey: .activeProfiles)
}
public func encode(to encoder: Encoder) throws
{
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.version, forKey: .version)
try container.encode(self.identifier, forKey: .identifier)
try container.encode(self.udid, forKey: .udid)
try container.encode(self.provisioningProfiles.map { $0.data }, forKey: .provisioningProfiles)
try container.encodeIfPresent(self.activeProfiles, forKey: .activeProfiles)
}
}
public struct InstallProvisioningProfilesResponse: ServerMessageProtocol
{
public var version = 1
public var identifier = "InstallProvisioningProfilesResponse"
public init()
{
}
}
public struct RemoveProvisioningProfilesRequest: ServerMessageProtocol
{
public var version = 1
public var identifier = "RemoveProvisioningProfilesRequest"
public var udid: String
public var bundleIdentifiers: Set<String>
public init(udid: String, bundleIdentifiers: Set<String>)
{
self.udid = udid
self.bundleIdentifiers = bundleIdentifiers
}
}
public struct RemoveProvisioningProfilesResponse: ServerMessageProtocol
{
public var version = 1
public var identifier = "RemoveProvisioningProfilesResponse"
public init()
{
}
}
public struct RemoveAppRequest: ServerMessageProtocol
{
public var version = 1
public var identifier = "RemoveAppRequest"
public var udid: String
public var bundleIdentifier: String
public init(udid: String, bundleIdentifier: String)
{
self.udid = udid
self.bundleIdentifier = bundleIdentifier
}
}
public struct RemoveAppResponse: ServerMessageProtocol
{
public var version = 1
public var identifier = "RemoveAppResponse"
public init()
{
}
}