FCompoundHash.m 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 "FChildrenNode.h"
  18. #import "FLeafNode.h"
  19. #import "FSnapshotUtilities.h"
  20. #import "FStringUtilities.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
  28. // range (i.e. encountered a leaf node).
  29. NSMutableString *optHashValueBuilder;
  30. // The current path as a stack. This is used in combination with
  31. // currentPathDepth to simultaneously store the last leaf node path. The
  32. // depth is changed when descending and ascending, at the same time the
  33. // current key is set for the current depth. Because the keys are left
  34. // unchanged for ascending the path will also contain the path of the last
  35. // visited leaf node (using lastLeafDepth elements)
  36. NSMutableArray *currentPath;
  37. NSInteger lastLeafDepth;
  38. NSInteger currentPathDepth;
  39. BOOL needsComma;
  40. }
  41. - (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy {
  42. self = [super init];
  43. if (self != nil) {
  44. self->_splitStrategy = strategy;
  45. self->optHashValueBuilder = nil;
  46. self->currentPath = [NSMutableArray array];
  47. self->lastLeafDepth = -1;
  48. self->currentPathDepth = 0;
  49. self->needsComma = YES;
  50. self->_currentPaths = [NSMutableArray array];
  51. self->_currentHashes = [NSMutableArray array];
  52. }
  53. return self;
  54. }
  55. - (BOOL)isBuildingRange {
  56. return self->optHashValueBuilder != nil;
  57. }
  58. - (NSUInteger)currentHashLength {
  59. return self->optHashValueBuilder.length;
  60. }
  61. - (FPath *)currentPath {
  62. return [self currentPathWithDepth:self->currentPathDepth];
  63. }
  64. - (FPath *)currentPathWithDepth:(NSInteger)depth {
  65. NSArray *pieces =
  66. [self->currentPath subarrayWithRange:NSMakeRange(0, depth)];
  67. return [[FPath alloc] initWithPieces:pieces andPieceNum:0];
  68. }
  69. - (void)enumerateCurrentPathToDepth:(NSInteger)depth
  70. withBlock:(void (^)(NSString *key))block {
  71. for (NSInteger i = 0; i < depth; i++) {
  72. block(self->currentPath[i]);
  73. }
  74. }
  75. - (void)appendKey:(NSString *)key toString:(NSMutableString *)string {
  76. [FSnapshotUtilities appendHashV2RepresentationForString:key
  77. toString:string];
  78. }
  79. - (void)ensureRange {
  80. if (![self isBuildingRange]) {
  81. optHashValueBuilder = [NSMutableString string];
  82. [optHashValueBuilder appendString:@"("];
  83. [self
  84. enumerateCurrentPathToDepth:self->currentPathDepth
  85. withBlock:^(NSString *key) {
  86. [self appendKey:key
  87. toString:self->optHashValueBuilder];
  88. [self->optHashValueBuilder appendString:@":("];
  89. }];
  90. self->needsComma = NO;
  91. }
  92. }
  93. - (void)processLeaf:(FLeafNode *)leafNode {
  94. [self ensureRange];
  95. self->lastLeafDepth = self->currentPathDepth;
  96. [FSnapshotUtilities
  97. appendHashRepresentationForLeafNode:leafNode
  98. toString:self->optHashValueBuilder
  99. hashVersion:FDataHashVersionV2];
  100. self->needsComma = YES;
  101. if (self.splitStrategy(self)) {
  102. [self endRange];
  103. }
  104. }
  105. - (void)startChild:(NSString *)key {
  106. [self ensureRange];
  107. if (self->needsComma) {
  108. [self->optHashValueBuilder appendString:@","];
  109. }
  110. [self appendKey:key toString:self->optHashValueBuilder];
  111. [self->optHashValueBuilder appendString:@":("];
  112. if (self->currentPathDepth == currentPath.count) {
  113. [self->currentPath addObject:key];
  114. } else {
  115. self->currentPath[self->currentPathDepth] = key;
  116. }
  117. self->currentPathDepth++;
  118. self->needsComma = NO;
  119. }
  120. - (void)endChild {
  121. self->currentPathDepth--;
  122. if ([self isBuildingRange]) {
  123. [self->optHashValueBuilder appendString:@")"];
  124. }
  125. self->needsComma = YES;
  126. }
  127. - (void)finishHashing {
  128. NSAssert(self->currentPathDepth == 0,
  129. @"Can't finish hashing in the middle of processing a child");
  130. if ([self isBuildingRange]) {
  131. [self endRange];
  132. }
  133. // Always close with the empty hash for the remaining range to allow simple
  134. // appending
  135. [self.currentHashes addObject:@""];
  136. }
  137. - (void)endRange {
  138. NSAssert([self isBuildingRange],
  139. @"Can't end range without starting a range!");
  140. // Add closing parenthesis for current depth
  141. for (NSUInteger i = 0; i < currentPathDepth; i++) {
  142. [self->optHashValueBuilder appendString:@")"];
  143. }
  144. [self->optHashValueBuilder appendString:@")"];
  145. FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth];
  146. NSString *hash =
  147. [FStringUtilities base64EncodedSha1:self->optHashValueBuilder];
  148. [self.currentHashes addObject:hash];
  149. [self.currentPaths addObject:lastLeafPath];
  150. self->optHashValueBuilder = nil;
  151. }
  152. @end
  153. @interface FCompoundHash ()
  154. @property(nonatomic, strong, readwrite) NSArray *posts;
  155. @property(nonatomic, strong, readwrite) NSArray *hashes;
  156. @end
  157. @implementation FCompoundHash
  158. - (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes {
  159. self = [super init];
  160. if (self != nil) {
  161. if (posts.count != hashes.count - 1) {
  162. [NSException raise:NSInvalidArgumentException
  163. format:@"Number of posts need to be n-1 for n hashes "
  164. @"in FCompoundHash"];
  165. }
  166. self.posts = posts;
  167. self.hashes = hashes;
  168. }
  169. return self;
  170. }
  171. + (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id<FNode>)node {
  172. NSUInteger estimatedSize =
  173. [FSnapshotUtilities estimateSerializedNodeSize:node];
  174. // Splits for
  175. // 1k -> 512 (2 parts)
  176. // 5k -> 715 (7 parts)
  177. // 100k -> 3.2k (32 parts)
  178. // 500k -> 7k (71 parts)
  179. // 5M -> 23k (228 parts)
  180. NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100));
  181. return ^BOOL(FCompoundHashBuilder *builder) {
  182. // Never split on priorities
  183. return [builder currentHashLength] > splitThreshold &&
  184. ![[[builder currentPath] getBack] isEqualToString:@".priority"];
  185. };
  186. }
  187. + (FCompoundHash *)fromNode:(id<FNode>)node {
  188. return [FCompoundHash
  189. fromNode:node
  190. splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
  191. }
  192. + (FCompoundHash *)fromNode:(id<FNode>)node
  193. splitStrategy:(FCompoundHashSplitStrategy)strategy {
  194. if ([node isEmpty]) {
  195. return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[ @"" ]];
  196. } else {
  197. FCompoundHashBuilder *builder =
  198. [[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy];
  199. [FCompoundHash processNode:node builder:builder];
  200. [builder finishHashing];
  201. return [[FCompoundHash alloc] initWithPosts:builder.currentPaths
  202. hashes:builder.currentHashes];
  203. }
  204. }
  205. + (void)processNode:(id<FNode>)node builder:(FCompoundHashBuilder *)builder {
  206. if ([node isLeafNode]) {
  207. [builder processLeaf:node];
  208. } else {
  209. NSAssert(![node isEmpty], @"Can't calculate hash on empty node!");
  210. FChildrenNode *childrenNode = (FChildrenNode *)node;
  211. [childrenNode enumerateChildrenAndPriorityUsingBlock:^(
  212. NSString *key, id<FNode> node, BOOL *stop) {
  213. [builder startChild:key];
  214. [self processNode:node builder:builder];
  215. [builder endChild];
  216. }];
  217. }
  218. }
  219. @end