| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "FCompoundHash.h"
- #import "FLeafNode.h"
- #import "FStringUtilities.h"
- #import "FSnapshotUtilities.h"
- #import "FChildrenNode.h"
- @interface FCompoundHashBuilder ()
- @property (nonatomic, strong) FCompoundHashSplitStrategy splitStrategy;
- @property (nonatomic, strong) NSMutableArray *currentPaths;
- @property (nonatomic, strong) NSMutableArray *currentHashes;
- @end
- @implementation FCompoundHashBuilder {
- // NOTE: We use the existence of this to know if we've started building a range (i.e. encountered a leaf node).
- NSMutableString *optHashValueBuilder;
- // The current path as a stack. This is used in combination with currentPathDepth to simultaneously store the
- // last leaf node path. The depth is changed when descending and ascending, at the same time the current key
- // is set for the current depth. Because the keys are left unchanged for ascending the path will also contain
- // the path of the last visited leaf node (using lastLeafDepth elements)
- NSMutableArray *currentPath;
- NSInteger lastLeafDepth;
- NSInteger currentPathDepth;
- BOOL needsComma;
- }
- - (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy {
- self = [super init];
- if (self != nil) {
- self->_splitStrategy = strategy;
- self->optHashValueBuilder = nil;
- self->currentPath = [NSMutableArray array];
- self->lastLeafDepth = -1;
- self->currentPathDepth = 0;
- self->needsComma = YES;
- self->_currentPaths = [NSMutableArray array];
- self->_currentHashes = [NSMutableArray array];
- }
- return self;
- }
- - (BOOL)isBuildingRange {
- return self->optHashValueBuilder != nil;
- }
- - (NSUInteger)currentHashLength {
- return self->optHashValueBuilder.length;
- }
- - (FPath *)currentPath {
- return [self currentPathWithDepth:self->currentPathDepth];
- }
- - (FPath *)currentPathWithDepth:(NSInteger)depth {
- NSArray *pieces = [self->currentPath subarrayWithRange:NSMakeRange(0, depth)];
- return [[FPath alloc] initWithPieces:pieces andPieceNum:0];
- }
- - (void)enumerateCurrentPathToDepth:(NSInteger)depth withBlock:(void (^) (NSString *key))block {
- for (NSInteger i = 0; i < depth; i++) {
- block(self->currentPath[i]);
- }
- }
- - (void)appendKey:(NSString *)key toString:(NSMutableString *)string {
- [FSnapshotUtilities appendHashV2RepresentationForString:key toString:string];
- }
- - (void)ensureRange {
- if (![self isBuildingRange]) {
- optHashValueBuilder = [NSMutableString string];
- [optHashValueBuilder appendString:@"("];
- [self enumerateCurrentPathToDepth:self->currentPathDepth withBlock:^(NSString *key) {
- [self appendKey:key toString:self->optHashValueBuilder];
- [self->optHashValueBuilder appendString:@":("];
- }];
- self->needsComma = NO;
- }
- }
- - (void)processLeaf:(FLeafNode *)leafNode {
- [self ensureRange];
- self->lastLeafDepth = self->currentPathDepth;
- [FSnapshotUtilities appendHashRepresentationForLeafNode:leafNode
- toString:self->optHashValueBuilder
- hashVersion:FDataHashVersionV2];
- self->needsComma = YES;
- if (self.splitStrategy(self)) {
- [self endRange];
- }
- }
- - (void)startChild:(NSString *)key {
- [self ensureRange];
- if (self->needsComma) {
- [self->optHashValueBuilder appendString:@","];
- }
- [self appendKey:key toString:self->optHashValueBuilder];
- [self->optHashValueBuilder appendString:@":("];
- if (self->currentPathDepth == currentPath.count) {
- [self->currentPath addObject:key];
- } else {
- self->currentPath[self->currentPathDepth] = key;
- }
- self->currentPathDepth++;
- self->needsComma = NO;
- }
- - (void)endChild {
- self->currentPathDepth--;
- if ([self isBuildingRange]) {
- [self->optHashValueBuilder appendString:@")"];
- }
- self->needsComma = YES;
- }
- - (void)finishHashing {
- NSAssert(self->currentPathDepth == 0, @"Can't finish hashing in the middle of processing a child");
- if ([self isBuildingRange] ) {
- [self endRange];
- }
- // Always close with the empty hash for the remaining range to allow simple appending
- [self.currentHashes addObject:@""];
- }
- - (void)endRange {
- NSAssert([self isBuildingRange], @"Can't end range without starting a range!");
- // Add closing parenthesis for current depth
- for (NSUInteger i = 0; i < currentPathDepth; i++) {
- [self->optHashValueBuilder appendString:@")"];
- }
- [self->optHashValueBuilder appendString:@")"];
- FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth];
- NSString *hash = [FStringUtilities base64EncodedSha1:self->optHashValueBuilder];
- [self.currentHashes addObject:hash];
- [self.currentPaths addObject:lastLeafPath];
- self->optHashValueBuilder = nil;
- }
- @end
- @interface FCompoundHash ()
- @property (nonatomic, strong, readwrite) NSArray *posts;
- @property (nonatomic, strong, readwrite) NSArray *hashes;
- @end
- @implementation FCompoundHash
- - (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes {
- self = [super init];
- if (self != nil) {
- if (posts.count != hashes.count - 1) {
- [NSException raise:NSInvalidArgumentException format:@"Number of posts need to be n-1 for n hashes in FCompoundHash"];
- }
- self.posts = posts;
- self.hashes = hashes;
- }
- return self;
- }
- + (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id<FNode>)node {
- NSUInteger estimatedSize = [FSnapshotUtilities estimateSerializedNodeSize:node];
- // Splits for
- // 1k -> 512 (2 parts)
- // 5k -> 715 (7 parts)
- // 100k -> 3.2k (32 parts)
- // 500k -> 7k (71 parts)
- // 5M -> 23k (228 parts)
- NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100));
- return ^BOOL(FCompoundHashBuilder *builder) {
- // Never split on priorities
- return [builder currentHashLength] > splitThreshold && ![[[builder currentPath] getBack] isEqualToString:@".priority"];
- };
- }
- + (FCompoundHash *)fromNode:(id<FNode>)node {
- return [FCompoundHash fromNode:node splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
- }
- + (FCompoundHash *)fromNode:(id<FNode>)node splitStrategy:(FCompoundHashSplitStrategy)strategy {
- if ([node isEmpty]) {
- return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[@""]];
- } else {
- FCompoundHashBuilder *builder = [[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy];
- [FCompoundHash processNode:node builder:builder];
- [builder finishHashing];
- return [[FCompoundHash alloc] initWithPosts:builder.currentPaths hashes:builder.currentHashes];
- }
- }
- + (void)processNode:(id<FNode>)node builder:(FCompoundHashBuilder *)builder {
- if ([node isLeafNode]) {
- [builder processLeaf:node];
- } else {
- NSAssert(![node isEmpty], @"Can't calculate hash on empty node!");
- FChildrenNode *childrenNode = (FChildrenNode *)node;
- [childrenNode enumerateChildrenAndPriorityUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
- [builder startChild:key];
- [self processNode:node builder:builder];
- [builder endChild];
- }];
- }
- }
- @end
|