FCompoundHash.m 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FCompoundHash.h"
  17. #import "FLeafNode.h"
  18. #import "FStringUtilities.h"
  19. #import "FSnapshotUtilities.h"
  20. #import "FChildrenNode.h"
  21. @interface FCompoundHashBuilder ()
  22. @property (nonatomic, strong) FCompoundHashSplitStrategy splitStrategy;
  23. @property (nonatomic, strong) NSMutableArray *currentPaths;
  24. @property (nonatomic, strong) NSMutableArray *currentHashes;
  25. @end
  26. @implementation FCompoundHashBuilder {
  27. // NOTE: We use the existence of this to know if we've started building a range (i.e. encountered a leaf node).
  28. NSMutableString *optHashValueBuilder;
  29. // The current path as a stack. This is used in combination with currentPathDepth to simultaneously store the
  30. // last leaf node path. The depth is changed when descending and ascending, at the same time the current key
  31. // is set for the current depth. Because the keys are left unchanged for ascending the path will also contain
  32. // the path of the last visited leaf node (using lastLeafDepth elements)
  33. NSMutableArray *currentPath;
  34. NSInteger lastLeafDepth;
  35. NSInteger currentPathDepth;
  36. BOOL needsComma;
  37. }
  38. - (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy {
  39. self = [super init];
  40. if (self != nil) {
  41. self->_splitStrategy = strategy;
  42. self->optHashValueBuilder = nil;
  43. self->currentPath = [NSMutableArray array];
  44. self->lastLeafDepth = -1;
  45. self->currentPathDepth = 0;
  46. self->needsComma = YES;
  47. self->_currentPaths = [NSMutableArray array];
  48. self->_currentHashes = [NSMutableArray array];
  49. }
  50. return self;
  51. }
  52. - (BOOL)isBuildingRange {
  53. return self->optHashValueBuilder != nil;
  54. }
  55. - (NSUInteger)currentHashLength {
  56. return self->optHashValueBuilder.length;
  57. }
  58. - (FPath *)currentPath {
  59. return [self currentPathWithDepth:self->currentPathDepth];
  60. }
  61. - (FPath *)currentPathWithDepth:(NSInteger)depth {
  62. NSArray *pieces = [self->currentPath subarrayWithRange:NSMakeRange(0, depth)];
  63. return [[FPath alloc] initWithPieces:pieces andPieceNum:0];
  64. }
  65. - (void)enumerateCurrentPathToDepth:(NSInteger)depth withBlock:(void (^) (NSString *key))block {
  66. for (NSInteger i = 0; i < depth; i++) {
  67. block(self->currentPath[i]);
  68. }
  69. }
  70. - (void)appendKey:(NSString *)key toString:(NSMutableString *)string {
  71. [FSnapshotUtilities appendHashV2RepresentationForString:key toString:string];
  72. }
  73. - (void)ensureRange {
  74. if (![self isBuildingRange]) {
  75. optHashValueBuilder = [NSMutableString string];
  76. [optHashValueBuilder appendString:@"("];
  77. [self enumerateCurrentPathToDepth:self->currentPathDepth withBlock:^(NSString *key) {
  78. [self appendKey:key toString:self->optHashValueBuilder];
  79. [self->optHashValueBuilder appendString:@":("];
  80. }];
  81. self->needsComma = NO;
  82. }
  83. }
  84. - (void)processLeaf:(FLeafNode *)leafNode {
  85. [self ensureRange];
  86. self->lastLeafDepth = self->currentPathDepth;
  87. [FSnapshotUtilities appendHashRepresentationForLeafNode:leafNode
  88. toString:self->optHashValueBuilder
  89. hashVersion:FDataHashVersionV2];
  90. self->needsComma = YES;
  91. if (self.splitStrategy(self)) {
  92. [self endRange];
  93. }
  94. }
  95. - (void)startChild:(NSString *)key {
  96. [self ensureRange];
  97. if (self->needsComma) {
  98. [self->optHashValueBuilder appendString:@","];
  99. }
  100. [self appendKey:key toString:self->optHashValueBuilder];
  101. [self->optHashValueBuilder appendString:@":("];
  102. if (self->currentPathDepth == currentPath.count) {
  103. [self->currentPath addObject:key];
  104. } else {
  105. self->currentPath[self->currentPathDepth] = key;
  106. }
  107. self->currentPathDepth++;
  108. self->needsComma = NO;
  109. }
  110. - (void)endChild {
  111. self->currentPathDepth--;
  112. if ([self isBuildingRange]) {
  113. [self->optHashValueBuilder appendString:@")"];
  114. }
  115. self->needsComma = YES;
  116. }
  117. - (void)finishHashing {
  118. NSAssert(self->currentPathDepth == 0, @"Can't finish hashing in the middle of processing a child");
  119. if ([self isBuildingRange] ) {
  120. [self endRange];
  121. }
  122. // Always close with the empty hash for the remaining range to allow simple appending
  123. [self.currentHashes addObject:@""];
  124. }
  125. - (void)endRange {
  126. NSAssert([self isBuildingRange], @"Can't end range without starting a range!");
  127. // Add closing parenthesis for current depth
  128. for (NSUInteger i = 0; i < currentPathDepth; i++) {
  129. [self->optHashValueBuilder appendString:@")"];
  130. }
  131. [self->optHashValueBuilder appendString:@")"];
  132. FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth];
  133. NSString *hash = [FStringUtilities base64EncodedSha1:self->optHashValueBuilder];
  134. [self.currentHashes addObject:hash];
  135. [self.currentPaths addObject:lastLeafPath];
  136. self->optHashValueBuilder = nil;
  137. }
  138. @end
  139. @interface FCompoundHash ()
  140. @property (nonatomic, strong, readwrite) NSArray *posts;
  141. @property (nonatomic, strong, readwrite) NSArray *hashes;
  142. @end
  143. @implementation FCompoundHash
  144. - (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes {
  145. self = [super init];
  146. if (self != nil) {
  147. if (posts.count != hashes.count - 1) {
  148. [NSException raise:NSInvalidArgumentException format:@"Number of posts need to be n-1 for n hashes in FCompoundHash"];
  149. }
  150. self.posts = posts;
  151. self.hashes = hashes;
  152. }
  153. return self;
  154. }
  155. + (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id<FNode>)node {
  156. NSUInteger estimatedSize = [FSnapshotUtilities estimateSerializedNodeSize:node];
  157. // Splits for
  158. // 1k -> 512 (2 parts)
  159. // 5k -> 715 (7 parts)
  160. // 100k -> 3.2k (32 parts)
  161. // 500k -> 7k (71 parts)
  162. // 5M -> 23k (228 parts)
  163. NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100));
  164. return ^BOOL(FCompoundHashBuilder *builder) {
  165. // Never split on priorities
  166. return [builder currentHashLength] > splitThreshold && ![[[builder currentPath] getBack] isEqualToString:@".priority"];
  167. };
  168. }
  169. + (FCompoundHash *)fromNode:(id<FNode>)node {
  170. return [FCompoundHash fromNode:node splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
  171. }
  172. + (FCompoundHash *)fromNode:(id<FNode>)node splitStrategy:(FCompoundHashSplitStrategy)strategy {
  173. if ([node isEmpty]) {
  174. return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[@""]];
  175. } else {
  176. FCompoundHashBuilder *builder = [[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy];
  177. [FCompoundHash processNode:node builder:builder];
  178. [builder finishHashing];
  179. return [[FCompoundHash alloc] initWithPosts:builder.currentPaths hashes:builder.currentHashes];
  180. }
  181. }
  182. + (void)processNode:(id<FNode>)node builder:(FCompoundHashBuilder *)builder {
  183. if ([node isLeafNode]) {
  184. [builder processLeaf:node];
  185. } else {
  186. NSAssert(![node isEmpty], @"Can't calculate hash on empty node!");
  187. FChildrenNode *childrenNode = (FChildrenNode *)node;
  188. [childrenNode enumerateChildrenAndPriorityUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
  189. [builder startChild:key];
  190. [self processNode:node builder:builder];
  191. [builder endChild];
  192. }];
  193. }
  194. }
  195. @end