5 //  Created by Ben Williamson on 6/1/17.
 
  19 @property (nonatomic, strong) Config *config;
 
  21 // Names that we could choose from when adding a new item.
 
  22 // When we add an item its name is moved to usedNames.
 
  23 @property (nonatomic, strong) NSMutableArray<NSString *> *freeNames;
 
  25 // Names and persistent references of the items we have written, which we can update or delete.
 
  26 // When we delete an item its name is moved to freeNames.
 
  27 @property (nonatomic, strong) NSMutableArray<NSMutableArray *> *usedNames;
 
  32 @implementation Monkey
 
  34 - (instancetype)initWithConfig:(Config *)config
 
  36     if (self = [super init]) {
 
  38         _freeNames = [NSMutableArray arrayWithCapacity:config.distinctNames];
 
  39         _usedNames = [NSMutableArray arrayWithCapacity:config.distinctNames];
 
  40         for (unsigned i = 0; i < config.distinctNames; i++) {
 
  42                 [_freeNames addObject:[NSString stringWithFormat:@"item-%@-%u", config.name, i]];
 
  44                 [_freeNames addObject:[NSString stringWithFormat:@"item-%u", i]];
 
  51 - (void)advanceOneStep
 
  53     static const int tryLimit = 1000;
 
  54     for (int tries = 0; tries < tryLimit; tries++) {
 
  55         if ([self tryAdvanceOneStep]) {
 
  60     printf("Chose %d random actions but none of them made sense, giving up!\n", tryLimit);
 
  61     printf("This is an internal failure of the manifeststresstest tool, not the system under test.\n");
 
  62     NSLog(@"Chose %d random actions but none of them made sense, giving up!", tryLimit);
 
  66 // return YES if it actually took a step, NO if the randomly chosen action was invalid
 
  67 - (BOOL)tryAdvanceOneStep
 
  69     unsigned totalWeight =
 
  70         self.config.addItemWeight +
 
  71         self.config.updateNameWeight +
 
  72         self.config.updateDataWeight +
 
  73         self.config.updateNameAndDataWeight +
 
  74         self.config.deleteItemWeight;
 
  76     if (totalWeight == 0) {
 
  77         printf("Invalid manifeststresstest configuration:\n"
 
  78                "At least one weight must be nonzero, or else we cannot choose any action.\n");
 
  81     if (totalWeight > RAND_MAX) {
 
  82         printf("Invalid manifeststresstest configuration:\n"
 
  83                "The total of the weights must not exceed RAND_MAX == %d.\n", RAND_MAX);
 
  86     unsigned r = random() % totalWeight;
 
  88     if (r < self.config.addItemWeight) {
 
  89         return [self addItem];
 
  91     r -= self.config.addItemWeight;
 
  93     if (r < self.config.updateNameWeight) {
 
  94         return [self updateNameAndValue:NO];
 
  96     r -= self.config.updateNameWeight;
 
  98     if (r < self.config.updateDataWeight) {
 
  99         return [self updateValue];
 
 101     r -= self.config.updateDataWeight;
 
 103     if (r < self.config.updateNameAndDataWeight) {
 
 104         return [self updateNameAndValue:YES];
 
 106     r -= self.config.updateNameAndDataWeight;
 
 108     if (r < self.config.deleteItemWeight) {
 
 109         return [self deleteItem];
 
 111     NSAssert(false, @"Chosen random number was beyond totalWeight?!?");
 
 115 - (NSString *)randomValue
 
 117     if (0 == self.config.distinctValues) {
 
 118         printf("Invalid manifeststresstest configuration:\n"
 
 119                "Must allow a nonzero number of distinct values.\n");
 
 122     unsigned n = random() % self.config.distinctValues;
 
 123     return [NSString stringWithFormat:@"data-%u", n];
 
 128     return [NSString stringWithFormat:@"[step:%d items:%d dup:%d gone:%d]",
 
 131             self.addDuplicateCounter,
 
 132             self.notFoundCounter];
 
 135 - (void)unexpectedError:(OSStatus)status
 
 137     NSLog(@"Unexpected error %d at step %d", (int)status, self.step);
 
 143     if ([self.usedNames count] >= self.config.maxItems) {
 
 146     NSUInteger freeCount = [self.freeNames count];
 
 147     if (freeCount == 0) {
 
 150     NSUInteger index = random() % freeCount;
 
 151     NSString *name = self.freeNames[index];
 
 152     NSString *value = [self randomValue];
 
 154     NSLog(@"%@ addItem in %@ %@ = %@", [self prefix], self.config.view, name, value);
 
 155     NSArray *itemRef = nil;
 
 156     OSStatus status = [self.keychain addItem:name value:value view:self.config.view pRef:&itemRef];
 
 159             [self.freeNames removeObjectAtIndex:index];
 
 160             [self.usedNames addObject:[NSMutableArray arrayWithArray:@[name, itemRef]]];
 
 162             unsigned usedCount = (unsigned)[self.usedNames count];
 
 163             if (self.peakItems < usedCount) {
 
 164                 self.peakItems = usedCount;
 
 167         case errSecDuplicateItem:
 
 168             self.addDuplicateCounter++;
 
 171             [self unexpectedError:status];
 
 176 - (BOOL)updateNameAndValue:(BOOL)updateValue
 
 178     NSUInteger freeCount = [self.freeNames count];
 
 179     NSUInteger usedCount = [self.usedNames count];
 
 180     if (usedCount == 0 || freeCount == 0) {
 
 183     NSUInteger usedIndex = random() % usedCount;
 
 184     NSUInteger freeIndex = random() % freeCount;
 
 185     NSMutableArray *oldItem = self.usedNames[usedIndex];
 
 186     NSArray *oldRef = oldItem[1];
 
 187     NSString *oldName = oldItem[0];
 
 188     NSString *newName = self.freeNames[freeIndex];
 
 192         NSString *value = [self randomValue];
 
 193         NSLog(@"%@ updateNameAndValue %@(%@) -> %@ = %@\n", [self prefix], oldName, oldRef, newName, value);
 
 194         status = [self.keychain updateItem:oldRef newName:newName newValue:value];
 
 196         NSLog(@"%@ updateName %@(%@) -> %@\n", [self prefix], oldName, oldRef, newName);
 
 197         status = [self.keychain updateItem:oldRef newName:newName];
 
 201             /* Update tracking arrays */
 
 202             [self.freeNames removeObject:newName];
 
 203             [self.freeNames addObject:oldName];
 
 204             self.usedNames[usedIndex][0] = newName;
 
 206         case errSecItemNotFound:
 
 207             /* Update tracking arrays (someone else deleted this item) */
 
 208             [self.usedNames removeObject:oldItem];
 
 209             [self.freeNames addObject:oldName];
 
 210             self.notFoundCounter++;
 
 212         case errSecDuplicateItem:
 
 213             self.addDuplicateCounter++;
 
 214             // newName already exists, which means our attempted updateItem operation
 
 215             // did not rename oldName to newName, so the item still has oldName.
 
 216             // No action required.
 
 219             [self unexpectedError:status];
 
 226     NSUInteger usedCount = [self.usedNames count];
 
 227     if (usedCount == 0) {
 
 230     NSUInteger usedIndex = random() % usedCount;
 
 231     NSArray *item = self.usedNames[usedIndex];
 
 233     NSString *value = [self randomValue];
 
 235     NSLog(@"%@ updateValue %@(%@) = %@", [self prefix], item[0], item[1], value);
 
 236     OSStatus status = [self.keychain updateItem:item[1] newValue:value];
 
 240         case errSecItemNotFound:
 
 241             self.notFoundCounter++;
 
 244             [self unexpectedError:status];
 
 251     NSUInteger usedCount = [self.usedNames count];
 
 252     if (usedCount == 0) {
 
 255     NSUInteger usedIndex = random() % usedCount;
 
 256     NSArray *item = self.usedNames[usedIndex];
 
 258     NSLog(@"%@ deleteItem %@(%@)", [self prefix], item[0], item[1]);
 
 259     OSStatus status = [self.keychain deleteItem:item[1]];
 
 261         case errSecItemNotFound:
 
 262             /* someone else deleted this item */
 
 263             self.notFoundCounter++;
 
 266             [self.usedNames removeObjectAtIndex:usedIndex];
 
 267             [self.freeNames addObject:item[0]];
 
 270             [self unexpectedError:status];
 
 275 - (unsigned)itemCount
 
 277     return (unsigned)[self.usedNames count];
 
 282     for (NSArray *item in self.usedNames) {
 
 283         NSLog(@"cleanup deleting %@(%@)", item[0], item[1]);
 
 284         [self.keychain deleteItem:item[1]];