| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- /*
- * 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 "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
- #import "FirebaseDatabase/Sources/Constants/FConstants.h"
- #import "FirebaseDatabase/Sources/FMaxNode.h"
- #import "FirebaseDatabase/Sources/FNamedNode.h"
- #import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
- #import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
- #import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
- #import "FirebaseDatabase/Sources/Snapshot/FLeafNode.h"
- #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
- #import "FirebaseDatabase/Sources/Utilities/FValidation.h"
- #import "FirebaseDatabase/Sources/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.h"
- @implementation FSnapshotUtilities
- + (id<FNode>)nodeFrom:(id)val {
- return [FSnapshotUtilities nodeFrom:val priority:nil];
- }
- + (id<FNode>)nodeFrom:(id)val priority:(id)priority {
- return [FSnapshotUtilities nodeFrom:val
- priority:priority
- withValidationFrom:@"nodeFrom:priority:"];
- }
- + (id<FNode>)nodeFrom:(id)val withValidationFrom:(NSString *)fn {
- return [FSnapshotUtilities nodeFrom:val priority:nil withValidationFrom:fn];
- }
- + (id<FNode>)nodeFrom:(id)val
- priority:(id)priority
- withValidationFrom:(NSString *)fn {
- return [FSnapshotUtilities nodeFrom:val
- priority:priority
- withValidationFrom:fn
- atDepth:0
- path:[[NSMutableArray alloc] init]];
- }
- + (id<FNode>)nodeFrom:(id)val
- priority:(id)aPriority
- withValidationFrom:(NSString *)fn
- atDepth:(int)depth
- path:(NSMutableArray *)path {
- @autoreleasepool {
- return [FSnapshotUtilities internalNodeFrom:val
- priority:aPriority
- withValidationFrom:fn
- atDepth:depth
- path:path];
- }
- }
- + (id<FNode>)internalNodeFrom:(id)val
- priority:(id)aPriority
- withValidationFrom:(NSString *)fn
- atDepth:(int)depth
- path:(NSMutableArray *)path {
- if (depth > kFirebaseMaxObjectDepth) {
- NSRange range;
- range.location = 0;
- range.length = 100;
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Max object depth exceeded: %@...",
- fn, pathString]
- userInfo:nil];
- }
- if (val == nil || val == [NSNull null]) {
- // Null is a valid type to store
- return [FEmptyNode emptyNode];
- }
- [FValidation validateFrom:fn isValidPriorityValue:aPriority withPath:path];
- id<FNode> priority = [FSnapshotUtilities nodeFrom:aPriority];
- id value = val;
- BOOL isLeafNode = NO;
- if ([value isKindOfClass:[NSDictionary class]]) {
- NSDictionary *dict = val;
- if (dict[kPayloadPriority] != nil) {
- id rawPriority = [dict objectForKey:kPayloadPriority];
- [FValidation validateFrom:fn
- isValidPriorityValue:rawPriority
- withPath:path];
- priority = [FSnapshotUtilities nodeFrom:rawPriority];
- }
- if (dict[kPayloadValue] != nil) {
- value = [dict objectForKey:kPayloadValue];
- if ([FValidation validateFrom:fn
- isValidLeafValue:value
- withPath:path]) {
- isLeafNode = YES;
- } else {
- @throw [[NSException alloc]
- initWithName:@"InvalidLeafValueType"
- reason:[NSString stringWithFormat:
- @"(%@) Invalid data type used "
- @"with .value. Can only use "
- "NSString and NSNumber or be "
- "null. Found %@ instead.",
- fn, [[value class] description]]
- userInfo:nil];
- }
- }
- }
- if ([FValidation validateFrom:fn isValidLeafValue:value withPath:path]) {
- isLeafNode = YES;
- }
- if (isLeafNode) {
- return [[FLeafNode alloc] initWithValue:value withPriority:priority];
- }
- // Unlike with JS, we have to handle the dictionary and array cases
- // separately.
- if ([value isKindOfClass:[NSDictionary class]]) {
- NSDictionary *dval = (NSDictionary *)value;
- NSMutableDictionary *children =
- [NSMutableDictionary dictionaryWithCapacity:dval.count];
- // Avoid creating a million newPaths by appending to old one
- for (id keyId in dval) {
- [FValidation validateFrom:fn
- validDictionaryKey:keyId
- withPath:path];
- NSString *key = (NSString *)keyId;
- if (![key hasPrefix:kPayloadMetadataPrefix]) {
- [path addObject:key];
- id<FNode> childNode = [FSnapshotUtilities nodeFrom:dval[key]
- priority:nil
- withValidationFrom:fn
- atDepth:depth + 1
- path:path];
- [path removeLastObject];
- if (![childNode isEmpty]) {
- children[key] = childNode;
- }
- }
- }
- if ([children count] == 0) {
- return [FEmptyNode emptyNode];
- } else {
- FImmutableSortedDictionary *childrenDict =
- [FImmutableSortedDictionary
- fromDictionary:children
- withComparator:[FUtilities keyComparator]];
- return [[FChildrenNode alloc] initWithPriority:priority
- children:childrenDict];
- }
- } else if ([value isKindOfClass:[NSArray class]]) {
- NSArray *aval = (NSArray *)value;
- NSMutableDictionary *children =
- [NSMutableDictionary dictionaryWithCapacity:aval.count];
- for (int i = 0; i < [aval count]; i++) {
- NSString *key = [NSString stringWithFormat:@"%i", i];
- [path addObject:key];
- id<FNode> childNode =
- [FSnapshotUtilities nodeFrom:[aval objectAtIndex:i]
- priority:nil
- withValidationFrom:fn
- atDepth:depth + 1
- path:path];
- [path removeLastObject];
- if (![childNode isEmpty]) {
- children[key] = childNode;
- }
- }
- if ([children count] == 0) {
- return [FEmptyNode emptyNode];
- } else {
- FImmutableSortedDictionary *childrenDict =
- [FImmutableSortedDictionary
- fromDictionary:children
- withComparator:[FUtilities keyComparator]];
- return [[FChildrenNode alloc] initWithPriority:priority
- children:childrenDict];
- }
- } else {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:
- @"(%@) Cannot store object of type %@ at %@. "
- "Can only store objects of type NSNumber, "
- "NSString, NSDictionary, and NSArray.",
- fn, [[value class] description], pathString]
- userInfo:nil];
- }
- }
- + (FCompoundWrite *)compoundWriteFromDictionary:(NSDictionary *)values
- withValidationFrom:(NSString *)fn {
- FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite];
- NSMutableArray *updatePaths =
- [NSMutableArray arrayWithCapacity:values.count];
- for (NSString *keyId in values) {
- id value = values[keyId];
- [FValidation validateFrom:fn
- validUpdateDictionaryKey:keyId
- withValue:value];
- FPath *path = [FPath pathWithString:keyId];
- id<FNode> node = [FSnapshotUtilities nodeFrom:value
- withValidationFrom:fn];
- [updatePaths addObject:path];
- compoundWrite = [compoundWrite addWrite:node atPath:path];
- }
- // Check that the update paths are not descendants of each other.
- [updatePaths
- sortUsingComparator:^NSComparisonResult(FPath *left, FPath *right) {
- return [left compare:right];
- }];
- FPath *prevPath = nil;
- for (FPath *path in updatePaths) {
- if (prevPath != nil && [prevPath contains:path]) {
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Invalid path in object. Path "
- @"(%@) is an ancestor of (%@).",
- fn, prevPath, path]
- userInfo:nil];
- }
- prevPath = path;
- }
- return compoundWrite;
- }
- + (void)validatePriorityNode:(id<FNode>)priorityNode {
- assert(priorityNode != nil);
- if (priorityNode.isLeafNode) {
- id val = priorityNode.val;
- if ([val isKindOfClass:[NSDictionary class]]) {
- NSDictionary *valDict __unused = (NSDictionary *)val;
- NSAssert(valDict[kServerValueSubKey] != nil,
- @"Priority can't be object unless it's a deferred value");
- } else {
- NSString *jsType __unused = [FUtilities getJavascriptType:val];
- NSAssert(jsType == kJavaScriptString || jsType == kJavaScriptNumber,
- @"Priority of unexpected type.");
- }
- } else {
- NSAssert(priorityNode == [FMaxNode maxNode] || priorityNode.isEmpty,
- @"Priority of unexpected type.");
- }
- // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
- NSAssert(priorityNode == [FMaxNode maxNode] ||
- priorityNode.getPriority.isEmpty,
- @"Priority nodes can't have a priority of their own.");
- }
- + (void)appendHashRepresentationForLeafNode:(FLeafNode *)leafNode
- toString:(NSMutableString *)string
- hashVersion:(FDataHashVersion)hashVersion {
- NSAssert(hashVersion == FDataHashVersionV1 ||
- hashVersion == FDataHashVersionV2,
- @"Unknown hash version: %lu", (unsigned long)hashVersion);
- if (!leafNode.getPriority.isEmpty) {
- [string appendString:@"priority:"];
- [FSnapshotUtilities
- appendHashRepresentationForLeafNode:leafNode.getPriority
- toString:string
- hashVersion:hashVersion];
- [string appendString:@":"];
- }
- NSString *jsType = [FUtilities getJavascriptType:leafNode.val];
- [string appendString:jsType];
- [string appendString:@":"];
- if (jsType == kJavaScriptBoolean) {
- NSString *boolString =
- [leafNode.val boolValue] ? kJavaScriptTrue : kJavaScriptFalse;
- [string appendString:boolString];
- } else if (jsType == kJavaScriptNumber) {
- NSString *numberString =
- [FUtilities ieee754StringForNumber:leafNode.val];
- [string appendString:numberString];
- } else if (jsType == kJavaScriptString) {
- if (hashVersion == FDataHashVersionV1) {
- [string appendString:leafNode.val];
- } else {
- NSAssert(hashVersion == FDataHashVersionV2,
- @"Invalid hash version found");
- [FSnapshotUtilities appendHashV2RepresentationForString:leafNode.val
- toString:string];
- }
- } else {
- [NSException raise:NSInvalidArgumentException
- format:@"Unknown value for hashing: %@", leafNode];
- }
- }
- + (void)appendHashV2RepresentationForString:(NSString *)string
- toString:(NSMutableString *)mutableString {
- string = [string stringByReplacingOccurrencesOfString:@"\\"
- withString:@"\\\\"];
- string = [string stringByReplacingOccurrencesOfString:@"\""
- withString:@"\\\""];
- [mutableString appendString:@"\""];
- [mutableString appendString:string];
- [mutableString appendString:@"\""];
- }
- + (NSUInteger)estimateLeafNodeSize:(FLeafNode *)leafNode {
- NSString *jsType = [FUtilities getJavascriptType:leafNode.val];
- // These values are somewhat arbitrary, but we don't need an exact value so
- // prefer performance over exact value
- NSUInteger valueSize;
- if (jsType == kJavaScriptNumber) {
- valueSize = 8; // estimate each float with 8 bytes
- } else if (jsType == kJavaScriptBoolean) {
- valueSize = 4; // true or false need roughly 4 bytes
- } else if (jsType == kJavaScriptString) {
- valueSize = 2 + [leafNode.val length]; // add 2 for quotes
- } else {
- [NSException raise:NSInvalidArgumentException
- format:@"Unknown leaf type: %@", leafNode];
- return 0;
- }
- if (leafNode.getPriority.isEmpty) {
- return valueSize;
- } else {
- // Account for extra overhead due to the extra JSON object and the
- // ".value" and ".priority" keys, colons, comma
- NSUInteger leafPriorityOverhead = 2 + 8 + 11 + 2 + 1;
- return leafPriorityOverhead + valueSize +
- [FSnapshotUtilities estimateLeafNodeSize:leafNode.getPriority];
- }
- }
- + (NSUInteger)estimateSerializedNodeSize:(id<FNode>)node {
- if ([node isEmpty]) {
- return 4; // null keyword
- } else if ([node isLeafNode]) {
- return [FSnapshotUtilities estimateLeafNodeSize:node];
- } else {
- NSAssert([node isKindOfClass:[FChildrenNode class]],
- @"Unexpected node type: %@", [node class]);
- __block NSUInteger sum = 1; // opening brackets
- [((FChildrenNode *)node) enumerateChildrenAndPriorityUsingBlock:^(
- NSString *key, id<FNode> child,
- BOOL *stop) {
- sum += key.length;
- sum +=
- 4; // quotes around key and colon and (comma or closing bracket)
- sum += [FSnapshotUtilities estimateSerializedNodeSize:child];
- }];
- return sum;
- }
- }
- @end
|