FBDeviceControl/Commands/FBDeviceEraseCommands.m (157 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 "FBDeviceEraseCommands.h"
#import "FBDevice+Private.h"
#import "FBAMDevice+Private.h"
#import "FBDeviceControlError.h"
#import "FBAMRestorableDeviceManager.h"
@interface FBDeviceEraseOperation : NSObject <FBiOSTargetSetDelegate>
@property (nonatomic, copy, readonly) NSString *udid;
@property (nonatomic, copy, readonly) NSString *ecid;
@property (nonatomic, assign, readonly) AMDCalls calls;
@property (nonatomic, strong, readonly) id<FBControlCoreLogger> logger;
@property (nonatomic, strong, readonly) FBAMRestorableDeviceManager *deviceManager;
@property (nonatomic, strong, readonly) FBMutableFuture<NSNumber *> *eraseCallbackResult;
@property (nonatomic, strong, readonly) FBMutableFuture<NSNull *> *deviceDetected;
@property (nonatomic, strong, readonly) FBMutableFuture<NSNull *> *deviceWentAway;
@property (nonatomic, strong, readonly) FBMutableFuture<NSNull *> *deviceCameBack;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@end
static int const EraseCallbackValueGood = -10;
static NSTimeInterval const DetectTimeout = 10;
static NSTimeInterval const APICallbackTimeout = 15;
static NSTimeInterval const OfflineTimeout = 20;
static NSTimeInterval const OnlineTimeout = 300;
static int EraseCallback(NSString *identifier, int progress, void *context)
{
FBDeviceEraseOperation *operation = (__bridge FBDeviceEraseOperation *)(context);
[operation.logger logFormat:@"Erase Callback is %d", progress];
[operation.eraseCallbackResult resolveWithResult:@(progress)];
return 0;
}
@implementation FBDeviceEraseOperation
+ (instancetype)operationWithDevice:(FBDevice *)device logger:(id<FBControlCoreLogger>)logger
{
dispatch_queue_t queue = dispatch_queue_create("com.facebook.fbdeviceerase", DISPATCH_QUEUE_SERIAL);
return [[self alloc] initWithUDID:device.udid ecid:device.uniqueIdentifier calls:device.calls queue:queue logger:logger];
}
- (instancetype)initWithUDID:(NSString *)udid ecid:(NSString *)ecid calls:(AMDCalls)calls queue:(dispatch_queue_t)queue logger:(id<FBControlCoreLogger>)logger
{
self = [super init];
if (!self) {
return nil;
}
_udid = udid;
_ecid = ecid;
_logger = logger;
_calls = calls;
_queue = queue;
_deviceManager = [[FBAMRestorableDeviceManager alloc] initWithCalls:calls workQueue:queue asyncQueue:queue ecidFilter:ecid logger:logger];
_deviceManager.delegate = self;
_eraseCallbackResult = FBMutableFuture.future;
_deviceDetected = FBMutableFuture.future;
_deviceWentAway = FBMutableFuture.future;
_deviceCameBack = FBMutableFuture.future;
return self;
}
#pragma mark Erase
- (FBFuture<NSNull *> *)erase
{
FBAMRestorableDeviceManager *deviceManager = self.deviceManager;
FBFuture<NSNull *> *deviceCameBack = self.deviceCameBack;
FBFuture<NSNull *> *deviceWentAway = self.deviceWentAway;
id<FBControlCoreLogger> logger = self.logger;
return [[[[FBFuture
onQueue:self.queue resolve:^ FBFuture<NSNull *> * {
NSError *error = nil;
if (![deviceManager startListeningWithError:&error]) {
return [FBFuture futureWithError:error];
}
return [[self deviceDetected] timeout:DetectTimeout waitingFor:@"Device to be detected the first time"];
}]
onQueue:self.queue fmap:^ FBFuture<NSNull *> * (id _) {
[logger logFormat:@"Device has been detected, starting erase API Call"];
return [[self startErase] timeout:APICallbackTimeout waitingFor:@"Device erase API call to resolve"];
}]
onQueue:self.queue fmap:^ FBFuture<NSNull *> * (NSNumber *eraseCallbackValueNumber) {
const int eraseCallbackValue = eraseCallbackValueNumber.intValue;
if (eraseCallbackValue != EraseCallbackValueGood) {
return [[FBDeviceControlError
describeFormat:@"Erase callback was %d, not %d. Perhaps the device is not activated?", eraseCallbackValue, EraseCallbackValueGood]
failFuture];
}
[logger logFormat:@"Device API call finished, waiting for device to go offline"];
return [deviceWentAway timeout:OfflineTimeout waitingFor:@"Device to go offline"];
}]
onQueue:self.queue fmap:^ FBFuture<NSNull *> * (id _) {
[logger logFormat:@"Device has gone offline, waiting for it to come back online"];
return [deviceCameBack timeout:OnlineTimeout waitingFor:@"Device to come back"];
}];
}
- (FBFuture<NSNumber *> *)startErase
{
AMDCalls calls = self.calls;
FBFuture<NSNumber *> *eraseCallbackResult = self.eraseCallbackResult;
id<FBControlCoreLogger> logger = self.logger;
NSString *udid = self.udid;
return [FBFuture
onQueue:self.queue resolve:^ FBFuture<NSNumber *> * {
calls.AMSInitialize(0);
int status = calls.AMSEraseDevice((__bridge CFStringRef)(udid), EraseCallback, (__bridge void *)(self));
[logger logFormat:@"AMSEraseDevice had status %d", status];
return eraseCallbackResult;
}];
}
#pragma mark FBiOSTargetSetDelegate
- (void)targetAdded:(id<FBiOSTargetInfo>)targetInfo inTargetSet:(id<FBiOSTargetSet>)targetSet
{
if (self.deviceDetected.state == FBFutureStateRunning) {
[self.logger logFormat:@"Got target %@ added for the first time", targetInfo];
[self.deviceDetected resolveWithResult:NSNull.null];
} else {
[self.logger logFormat:@"Got target %@ added", targetInfo];
[self.deviceCameBack resolveWithResult:NSNull.null];
}
}
- (void)targetRemoved:(id<FBiOSTargetInfo>)targetInfo inTargetSet:(id<FBiOSTargetSet>)targetSet
{
[self.logger logFormat:@"Got target %@ removed", targetInfo];
[self.deviceWentAway resolveWithResult:NSNull.null];
}
- (void)targetUpdated:(id<FBiOSTargetInfo>)targetInfo inTargetSet:(id<FBiOSTargetSet>)targetSet
{
}
@end
@interface FBDeviceEraseCommands ()
@property (nonatomic, weak, readonly) FBDevice *device;
@property (nonatomic, strong, readwrite) FBAMRestorableDeviceManager *restorableDeviceManager;
@end
@implementation FBDeviceEraseCommands
#pragma mark Initializers
+ (instancetype)commandsWithTarget:(FBDevice *)target
{
return [[self alloc] initWithDevice:target];
}
- (instancetype)initWithDevice:(FBDevice *)device
{
self = [super init];
if (!self) {
return nil;
}
_device = device;
return self;
}
#pragma mark FBEraseCommands Implementation
- (FBFuture<NSNull *> *)erase
{
id<FBControlCoreLogger> logger = [self.device.logger withName:[NSString stringWithFormat:@"erase_%@", self.device.udid]];
return [[self.device
activate]
onQueue:self.device.workQueue fmap:^(id _) {
FBDeviceEraseOperation *operation = [FBDeviceEraseOperation operationWithDevice:self.device logger:logger];
return [[operation
erase]
onQueue:self.device.workQueue doOnResolved:^(id __) {
[logger logFormat:@"Device erase finished successfully %@", operation];
}];
}];
}
@end