FBDeviceControl/Management/FBAMDevice.m (235 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBAMDevice.h"
#import "FBAMDevice+Private.h"
#import <FBControlCore/FBControlCore.h>
#include <dlfcn.h>
#import "FBAFCConnection.h"
#import "FBAMDeviceManager.h"
#import "FBAMDeviceServiceManager.h"
#import "FBAMDServiceConnection.h"
#import "FBAMRestorableDevice.h"
#import "FBDeviceActivationCommands.h"
#import "FBDeviceControlError.h"
#import "FBDeviceControlFrameworkLoader.h"
#import "FBDeviceLinkClient.h"
#pragma mark - FBAMDevice Implementation
@implementation FBAMDevice
@synthesize amDeviceRef = _amDeviceRef;
@synthesize asyncQueue = _asyncQueue;
@synthesize calls = _calls;
@synthesize contextPoolTimeout = _contextPoolTimeout;
@synthesize logger = _logger;
@synthesize workQueue = _workQueue;
#pragma mark Initializers
- (instancetype)initWithAllValues:(NSDictionary<NSString *, id> *)allValues calls:(AMDCalls)calls connectionReuseTimeout:(nullable NSNumber *)connectionReuseTimeout serviceReuseTimeout:(nullable NSNumber *)serviceReuseTimeout workQueue:(dispatch_queue_t)workQueue asyncQueue:(dispatch_queue_t)asyncQueue logger:(id<FBControlCoreLogger>)logger
{
self = [super init];
if (!self) {
return nil;
}
_allValues = allValues;
_calls = calls;
_workQueue = workQueue;
_asyncQueue = asyncQueue;
_logger = [logger withName:self.udid];
_connectionContextManager = [FBFutureContextManager managerWithQueue:workQueue delegate:self logger:logger];
_contextPoolTimeout = connectionReuseTimeout;
_serviceManager = [FBAMDeviceServiceManager managerWithAMDevice:self serviceTimeout:serviceReuseTimeout];
return self;
}
#pragma mark Properties
- (void)setAmDeviceRef:(AMDeviceRef)amDeviceRef
{
AMDeviceRef oldAMDeviceRef = _amDeviceRef;
_amDeviceRef = amDeviceRef;
if (amDeviceRef) {
self.calls.Retain(amDeviceRef);
}
if (oldAMDeviceRef) {
self.calls.Release(oldAMDeviceRef);
}
}
- (AMDeviceRef)amDevice
{
return _amDeviceRef;
}
- (NSDictionary<NSString *, id> *)extendedInformation
{
return @{
@"device": [FBCollectionOperations recursiveFilteredJSONSerializableRepresentationOfDictionary:self.allValues],
};
}
- (NSString *)uniqueIdentifier
{
return [self.allValues[FBDeviceKeyUniqueChipID] stringValue];
}
- (NSString *)udid
{
return self.allValues[FBDeviceKeyUniqueDeviceID];
}
- (NSString *)architecture
{
return self.allValues[FBDeviceKeyCPUArchitecture];
}
- (NSString *)buildVersion
{
return self.allValues[FBDeviceKeyBuildVersion];
}
- (NSString *)productVersion
{
return self.allValues[FBDeviceKeyProductVersion];
}
- (NSString *)name
{
return self.allValues[FBDeviceKeyDeviceName];
}
- (FBDeviceType *)deviceType
{
return FBiOSTargetConfiguration.productTypeToDevice[self.allValues[FBDeviceKeyProductType]];
}
- (FBOSVersion *)osVersion
{
NSString *osVersion = [FBAMDevice osVersionForDeviceClass:self.allValues[FBDeviceKeyDeviceClass] productVersion:self.productVersion];
return FBiOSTargetConfiguration.nameToOSVersion[osVersion] ?: [FBOSVersion genericWithName:osVersion];
}
- (FBiOSTargetState)state
{
return FBiOSTargetStateBooted;
}
- (FBiOSTargetType)targetType
{
return FBiOSTargetTypeDevice;
}
#pragma mark FBDevice Protocol Implementation
- (AMRecoveryModeDeviceRef)recoveryModeDeviceRef
{
return NULL;
}
- (FBDeviceActivationState)activationState
{
return FBDeviceActivationStateCoerceFromString(self.allValues[FBDeviceKeyActivationState]);
}
#pragma mark FBDeviceCommands Protocol Implementation
- (FBFutureContext<FBAMDevice *> *)connectToDeviceWithPurpose:(NSString *)format, ...
{
va_list args;
va_start(args, format);
NSString *string = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
return [self.connectionContextManager utilizeWithPurpose:string];
}
- (FBFutureContext<FBAMDServiceConnection *> *)startService:(NSString *)service
{
NSDictionary<NSString *, id> *userInfo = @{
@"CloseOnInvalidate" : @1,
@"InvalidateOnDetach" : @1,
};
// NOTE - The pop: after connectToDeviceWithPurpose: is critical to ensure we stop the AMDevice session
// immediately after the service is started. See longer description in FBAMDevice.h to understand why.
return [[[self
connectToDeviceWithPurpose:@"start_service_%@", service]
onQueue:self.workQueue pop:^ FBFuture<FBAMDServiceConnection *> * (id<FBDeviceCommands> device) {
AMDServiceConnectionRef serviceConnection;
[self.logger logFormat:@"Starting service %@", service];
int status = self.calls.SecureStartService(
device.amDeviceRef,
(__bridge CFStringRef)(service),
(__bridge CFDictionaryRef)(userInfo),
&serviceConnection
);
if (status != 0) {
NSString *errorDescription = CFBridgingRelease(self.calls.CopyErrorText(status));
return [[FBDeviceControlError
describeFormat:@"SecureStartService of %@ Failed with 0x%x %@", service, status, errorDescription]
failFuture];
}
FBAMDServiceConnection *connection = [FBAMDServiceConnection connectionWithName:service connection:serviceConnection device:device.amDeviceRef calls:self.calls logger:self.logger];
[self.logger logFormat:@"Service %@ started", service];
return [FBFuture futureWithResult:connection];
}]
onQueue:self.workQueue contextualTeardown:^(id connection, FBFutureState __) {
[self.logger logFormat:@"Invalidating service %@", service];
NSError *error = nil;
if (![connection invalidateWithError:&error]) {
[self.logger logFormat:@"Failed to invalidate service %@ with error %@", service, error];
} else {
[self.logger logFormat:@"Invalidated service %@", service];
}
return FBFuture.empty;
}];
}
- (FBFutureContext<FBDeviceLinkClient *> *)startDeviceLinkService:(NSString *)service
{
return [[self
startService:service]
onQueue:self.workQueue pend:^(FBAMDServiceConnection *connection) {
return [FBDeviceLinkClient deviceLinkClientWithConnection:connection];
}];
}
- (FBFutureContext<FBAFCConnection *> *)startAFCService:(NSString *)service
{
return [[self
startService:service]
onQueue:self.workQueue push:^(FBAMDServiceConnection *connection) {
return [FBAFCConnection afcFromServiceConnection:connection calls:FBAFCConnection.defaultCalls logger:self.logger queue:self.workQueue];
}];
}
- (FBFutureContext<FBAFCConnection *> *)houseArrestAFCConnectionForBundleID:(NSString *)bundleID afcCalls:(AFCCalls)afcCalls
{
return [[self
connectToDeviceWithPurpose:@"house_arrest"]
onQueue:self.workQueue replace:^ FBFutureContext<FBAFCConnection *> * (id<FBDeviceCommands> device) {
return [[self.serviceManager
houseArrestAFCConnectionForBundleID:bundleID afcCalls:afcCalls]
utilizeWithPurpose:self.udid];
}];
}
#pragma mark FBFutureContextManager Implementation
- (FBFuture<FBAMDevice *> *)prepare:(id<FBControlCoreLogger>)logger
{
NSError *error = nil;
if (![FBAMDeviceManager startUsing:self.amDevice calls:self.calls logger:logger error:&error]) {
return [FBFuture futureWithError:error];
}
return [FBFuture futureWithResult:self];
}
- (FBFuture<NSNull *> *)teardown:(FBAMDevice *)device logger:(id<FBControlCoreLogger>)logger;
{
NSError *error = nil;
if (![FBAMDeviceManager stopUsing:self.amDevice calls:self.calls logger:logger error:&error]) {
return [FBFuture futureWithError:error];
}
return FBFuture.empty;
}
- (NSString *)contextName
{
return [NSString stringWithFormat:@"%@_connection", self.udid];
}
- (BOOL)isContextSharable
{
return YES;
}
#pragma mark NSObject
- (id)device:(AMDeviceRef)device valueForKey:(NSString *)key
{
return CFBridgingRelease(self.calls.CopyValue(device, NULL, (__bridge CFStringRef)(key)));
}
- (NSString *)description
{
return [NSString stringWithFormat:
@"AMDevice %@ | %@",
self.udid,
self.name
];
}
#pragma mark Private
+ (NSString *)osVersionForDeviceClass:(NSString *)deviceClass productVersion:(NSString *)productVersion
{
NSDictionary<NSString *, NSString *> *deviceClassOSPrefixMapping = @{
@"iPhone" : @"iOS",
@"iPad" : @"iOS",
};
NSString *osPrefix = deviceClassOSPrefixMapping[deviceClass];
if (!osPrefix) {
return productVersion;
}
return [NSString stringWithFormat:@"%@ %@", osPrefix, productVersion];
}
@end