aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Database/Utilities
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Database/Utilities
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Firebase/Database/Utilities')
-rw-r--r--Firebase/Database/Utilities/FAtomicNumber.h23
-rw-r--r--Firebase/Database/Utilities/FAtomicNumber.m54
-rw-r--r--Firebase/Database/Utilities/FEventEmitter.h33
-rw-r--r--Firebase/Database/Utilities/FEventEmitter.m145
-rw-r--r--Firebase/Database/Utilities/FNextPushId.h23
-rw-r--r--Firebase/Database/Utilities/FNextPushId.m63
-rw-r--r--Firebase/Database/Utilities/FParsedUrl.h25
-rw-r--r--Firebase/Database/Utilities/FParsedUrl.m24
-rw-r--r--Firebase/Database/Utilities/FStringUtilities.h26
-rw-r--r--Firebase/Database/Utilities/FStringUtilities.m61
-rw-r--r--Firebase/Database/Utilities/FTypedefs.h45
-rw-r--r--Firebase/Database/Utilities/FUtilities.h76
-rw-r--r--Firebase/Database/Utilities/FUtilities.m389
-rw-r--r--Firebase/Database/Utilities/FValidation.h45
-rw-r--r--Firebase/Database/Utilities/FValidation.m312
-rw-r--r--Firebase/Database/Utilities/NSString+FURLUtils.h24
-rw-r--r--Firebase/Database/Utilities/NSString+FURLUtils.m38
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h25
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m24
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h24
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m22
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleFirebase.h26
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleFirebase.m25
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleNodePath.h28
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleNodePath.m33
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleObjectNode.h27
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleObjectNode.m32
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleObjects.h24
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleObjects.m24
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h27
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m26
-rw-r--r--Firebase/Database/Utilities/Tuples/FTuplePathValue.h25
-rw-r--r--Firebase/Database/Utilities/Tuples/FTuplePathValue.m38
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h30
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m37
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h27
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m33
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleStringNode.h27
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleStringNode.m34
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleTSN.h25
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleTSN.m24
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleTransaction.h74
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleTransaction.m38
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleUserCallback.h31
-rw-r--r--Firebase/Database/Utilities/Tuples/FTupleUserCallback.m35
45 files changed, 2251 insertions, 0 deletions
diff --git a/Firebase/Database/Utilities/FAtomicNumber.h b/Firebase/Database/Utilities/FAtomicNumber.h
new file mode 100644
index 0000000..589dc25
--- /dev/null
+++ b/Firebase/Database/Utilities/FAtomicNumber.h
@@ -0,0 +1,23 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface FAtomicNumber : NSObject
+
+- (NSNumber *) getAndIncrement;
+
+@end
diff --git a/Firebase/Database/Utilities/FAtomicNumber.m b/Firebase/Database/Utilities/FAtomicNumber.m
new file mode 100644
index 0000000..be0e537
--- /dev/null
+++ b/Firebase/Database/Utilities/FAtomicNumber.m
@@ -0,0 +1,54 @@
+/*
+ * 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 "FAtomicNumber.h"
+
+@interface FAtomicNumber() {
+ unsigned long number;
+}
+
+@property (nonatomic, strong) NSLock* lock;
+
+@end
+
+@implementation FAtomicNumber
+
+@synthesize lock;
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ number = 1;
+ self.lock = [[NSLock alloc] init];
+ }
+ return self;
+}
+
+- (NSNumber *) getAndIncrement {
+ NSNumber* result;
+
+ // See: http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW14 to improve, etc.
+
+ [self.lock lock];
+ result = [NSNumber numberWithUnsignedLong:number];
+ number = number + 1;
+ [self.lock unlock];
+
+ return result;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/FEventEmitter.h b/Firebase/Database/Utilities/FEventEmitter.h
new file mode 100644
index 0000000..069e10f
--- /dev/null
+++ b/Firebase/Database/Utilities/FEventEmitter.h
@@ -0,0 +1,33 @@
+/*
+ * 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 "FIRDatabaseQuery.h"
+#import "FIRDatabaseConfig.h"
+#import "FTypedefs_Private.h"
+
+@interface FEventEmitter : NSObject
+
+- (id) initWithAllowedEvents:(NSArray *)theAllowedEvents queue:(dispatch_queue_t)queue;
+
+- (id) getInitialEventForType:(NSString *)eventType;
+- (void) triggerEventType:(NSString *)eventType data:(id)data;
+
+- (FIRDatabaseHandle)observeEventType:(NSString *)eventType withBlock:(fbt_void_id)block;
+- (void) removeObserverForEventType:(NSString *)eventType withHandle:(FIRDatabaseHandle)handle;
+
+- (void) validateEventType:(NSString *)eventType;
+
+@end
diff --git a/Firebase/Database/Utilities/FEventEmitter.m b/Firebase/Database/Utilities/FEventEmitter.m
new file mode 100644
index 0000000..f7c569b
--- /dev/null
+++ b/Firebase/Database/Utilities/FEventEmitter.m
@@ -0,0 +1,145 @@
+/*
+ * 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 "FEventEmitter.h"
+#import "FUtilities.h"
+#import "FRepoManager.h"
+#import "FIRDatabaseQuery_Private.h"
+
+@interface FEventListener : NSObject
+
+@property (nonatomic, copy) fbt_void_id userCallback;
+@property (nonatomic) FIRDatabaseHandle handle;
+
+@end
+
+@implementation FEventListener
+
+@synthesize userCallback;
+@synthesize handle;
+
+@end
+
+
+@interface FEventEmitter ()
+
+@property (nonatomic, strong) NSArray *allowedEvents;
+@property (nonatomic, strong) NSMutableDictionary *listeners;
+@property (nonatomic, strong) dispatch_queue_t queue;
+
+@end
+
+
+@implementation FEventEmitter
+
+@synthesize allowedEvents;
+@synthesize listeners;
+
+- (id) initWithAllowedEvents:(NSArray *)theAllowedEvents queue:(dispatch_queue_t)queue {
+ if (theAllowedEvents == nil || [theAllowedEvents count] == 0) {
+ @throw [NSException exceptionWithName:@"AllowedEventsValidation" reason:@"FEventEmitters must be initialized with at least one valid event." userInfo:nil];
+ }
+
+ self = [super init];
+
+ if (self) {
+ self.allowedEvents = [theAllowedEvents copy];
+ self.listeners = [[NSMutableDictionary alloc] init];
+ self.queue = queue;
+ }
+
+ return self;
+}
+
+- (id) getInitialEventForType:(NSString *)eventType {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"You must override getInitialEvent: when subclassing FEventEmitter" userInfo:nil];
+}
+
+- (void) triggerEventType:(NSString *)eventType data:(id)data {
+ [self validateEventType:eventType];
+ NSMutableDictionary *eventTypeListeners = [self.listeners objectForKey:eventType];
+ for (FEventListener *listener in eventTypeListeners) {
+ [self triggerListener:listener withData:data];
+ }
+}
+
+- (void) triggerListener:(FEventListener *)listener withData:(id)data {
+ // TODO, should probably get this from FRepo or something although it ends up being the same. (Except maybe for testing)
+ if (listener.userCallback) {
+ dispatch_async(self.queue, ^{
+ listener.userCallback(data);
+ });
+ }
+}
+
+- (FIRDatabaseHandle)observeEventType:(NSString *)eventType withBlock:(fbt_void_id)block {
+ [self validateEventType:eventType];
+
+ // Create listener
+ FEventListener *listener = [[FEventListener alloc] init];
+ listener.handle = [[FUtilities LUIDGenerator] integerValue];
+ listener.userCallback = block; // copies block automatically
+
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ [self addEventListener:listener forEventType:eventType];
+ });
+
+ return listener.handle;
+}
+
+- (void) addEventListener:(FEventListener *)listener forEventType:(NSString *)eventType {
+ // Get or initializer listeners map [FIRDatabaseHandle -> callback block] for eventType
+ NSMutableArray *eventTypeListeners = [self.listeners objectForKey:eventType];
+ if (eventTypeListeners == nil) {
+ eventTypeListeners = [[NSMutableArray alloc] init];
+ [self.listeners setObject:eventTypeListeners forKey:eventType];
+ }
+
+ // Add listener and fire the current event for this listener
+ [eventTypeListeners addObject:listener];
+ id initialData = [self getInitialEventForType:eventType];
+ [self triggerListener:listener withData:initialData];
+}
+
+- (void) removeObserverForEventType:(NSString *)eventType withHandle:(FIRDatabaseHandle)handle {
+ [self validateEventType:eventType];
+
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ [self removeEventListenerWithHandle:handle forEventType:eventType];
+ });
+}
+
+- (void)removeEventListenerWithHandle:(FIRDatabaseHandle)handle forEventType:(NSString *)eventType {
+ NSMutableArray *eventTypeListeners = [self.listeners objectForKey:eventType];
+ for (FEventListener *listener in [eventTypeListeners copy]) {
+ if (handle == NSNotFound || handle == listener.handle) {
+ [eventTypeListeners removeObject:listener];
+ }
+ }
+}
+
+
+- (void) validateEventType:(NSString *)eventType {
+ if ([self.allowedEvents indexOfObject:eventType] == NSNotFound) {
+ @throw [NSException exceptionWithName:@"InvalidEventType"
+ reason:[NSString stringWithFormat:@"%@ is not a valid event type. %@ is the list of valid events.",
+ eventType, self.allowedEvents]
+ userInfo:nil];
+ }
+}
+
+@end
diff --git a/Firebase/Database/Utilities/FNextPushId.h b/Firebase/Database/Utilities/FNextPushId.h
new file mode 100644
index 0000000..2da54f0
--- /dev/null
+++ b/Firebase/Database/Utilities/FNextPushId.h
@@ -0,0 +1,23 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface FNextPushId : NSObject
+
++ (NSString *) get:(NSTimeInterval)now;
+
+@end
diff --git a/Firebase/Database/Utilities/FNextPushId.m b/Firebase/Database/Utilities/FNextPushId.m
new file mode 100644
index 0000000..af54e3d
--- /dev/null
+++ b/Firebase/Database/Utilities/FNextPushId.m
@@ -0,0 +1,63 @@
+/*
+ * 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 "FNextPushId.h"
+#import "FUtilities.h"
+
+static NSString *const PUSH_CHARS = @"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
+
+@implementation FNextPushId
+
++ (NSString *) get:(NSTimeInterval)currentTime {
+ static long long lastPushTime = 0;
+ static int lastRandChars[12];
+
+ long long now = (long long)(currentTime * 1000);
+
+ BOOL duplicateTime = now == lastPushTime;
+ lastPushTime = now;
+
+ unichar timeStampChars[8];
+ for(int i = 7; i >= 0; i--) {
+ timeStampChars[i] = [PUSH_CHARS characterAtIndex:(now % 64)];
+ now = (long long)floor(now / 64);
+ }
+
+ NSMutableString* id = [[NSMutableString alloc] init];
+ [id appendString:[NSString stringWithCharacters:timeStampChars length:8]];
+
+
+ if(!duplicateTime) {
+ for(int i = 0; i < 12; i++) {
+ lastRandChars[i] = (int)floor(arc4random() % 64);
+ }
+ }
+ else {
+ int i = 0;
+ for(i = 11; i >= 0 && lastRandChars[i] == 63; i--) {
+ lastRandChars[i] = 0;
+ }
+ lastRandChars[i]++;
+ }
+
+ for(int i = 0; i < 12; i++) {
+ [id appendFormat:@"%C", [PUSH_CHARS characterAtIndex:lastRandChars[i]]];
+ }
+
+ return [NSString stringWithString:id];
+}
+
+@end
diff --git a/Firebase/Database/Utilities/FParsedUrl.h b/Firebase/Database/Utilities/FParsedUrl.h
new file mode 100644
index 0000000..7145f86
--- /dev/null
+++ b/Firebase/Database/Utilities/FParsedUrl.h
@@ -0,0 +1,25 @@
+/*
+ * 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 "FRepoInfo.h"
+#import "FPath.h"
+
+@interface FParsedUrl : NSObject
+
+@property (nonatomic, strong) FRepoInfo* repoInfo;
+@property (nonatomic, strong) FPath* path;
+
+@end
diff --git a/Firebase/Database/Utilities/FParsedUrl.m b/Firebase/Database/Utilities/FParsedUrl.m
new file mode 100644
index 0000000..eb83330
--- /dev/null
+++ b/Firebase/Database/Utilities/FParsedUrl.m
@@ -0,0 +1,24 @@
+/*
+ * 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 "FParsedUrl.h"
+
+@implementation FParsedUrl
+
+@synthesize repoInfo;
+@synthesize path;
+
+@end
diff --git a/Firebase/Database/Utilities/FStringUtilities.h b/Firebase/Database/Utilities/FStringUtilities.h
new file mode 100644
index 0000000..34ac9af
--- /dev/null
+++ b/Firebase/Database/Utilities/FStringUtilities.h
@@ -0,0 +1,26 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface FStringUtilities : NSObject
+
++ (NSString *) base64EncodedSha1:(NSString *)str;
++ (NSString *) urlDecoded:(NSString *)url;
++ (NSString *) urlEncoded:(NSString *)url;
++ (NSString *) sanitizedForUserAgent:(NSString *)str;
+
+@end
diff --git a/Firebase/Database/Utilities/FStringUtilities.m b/Firebase/Database/Utilities/FStringUtilities.m
new file mode 100644
index 0000000..dff58e0
--- /dev/null
+++ b/Firebase/Database/Utilities/FStringUtilities.m
@@ -0,0 +1,61 @@
+/*
+ * 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 <CommonCrypto/CommonDigest.h>
+#import "FStringUtilities.h"
+#import "NSData+SRB64Additions.h"
+
+@implementation FStringUtilities
+
+// http://stackoverflow.com/questions/3468268/objective-c-sha1
+// http://stackoverflow.com/questions/7310457/ios-objective-c-sha-1-and-base64-problem
++ (NSString *) base64EncodedSha1:(NSString *)str {
+ const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding];
+ // NSString reports length in characters, but we want it in bytes, which strlen will give us.
+ unsigned long dataLen = strlen(cstr);
+ NSData *data = [NSData dataWithBytes:cstr length:dataLen];
+ uint8_t digest[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(data.bytes, (unsigned int)data.length, digest);
+ NSData* output = [[NSData alloc] initWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
+ return [FSRUtilities base64EncodedStringFromData:output];
+}
+
++ (NSString *) urlDecoded:(NSString *)url {
+ NSString* replaced = [url stringByReplacingOccurrencesOfString:@"+" withString:@" "];
+ NSString* decoded = [replaced stringByRemovingPercentEncoding];
+ // This is kind of a hack, but is generally how the js client works. We could run into trouble if
+ // some piece is a correctly escaped %-sequence, and another isn't. But, that's bad input anyways...
+ if (decoded) {
+ return decoded;
+ } else {
+ return replaced;
+ }
+}
+
++ (NSString *) urlEncoded:(NSString *)url {
+ // Didn't seem like there was an Apple NSCharacterSet that had our version of the encoding
+ // So I made my own, following RFC 2396 https://www.ietf.org/rfc/rfc2396.txt
+ // allowedCharacters = alphanum | "-" | "_" | "~"
+ NSCharacterSet *allowedCharacters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_~"];
+ return [url stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
+}
+
++ (NSString *) sanitizedForUserAgent:(NSString *)str {
+ return [str stringByReplacingOccurrencesOfString:@"/|_" withString:@"|" options:NSRegularExpressionSearch range:NSMakeRange(0, [str length])];
+}
+
+
+@end
diff --git a/Firebase/Database/Utilities/FTypedefs.h b/Firebase/Database/Utilities/FTypedefs.h
new file mode 100644
index 0000000..4a24ca5
--- /dev/null
+++ b/Firebase/Database/Utilities/FTypedefs.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#ifndef Firebase_FTypedefs_h
+#define Firebase_FTypedefs_h
+
+/**
+ * Stub...
+ */
+@class FIRDataSnapshot;
+@class FIRDatabaseReference;
+@class FAuthData;
+@protocol FNode;
+
+// fbt = Firebase Block Typedef
+
+typedef void (^fbt_void_void)(void);
+typedef void (^fbt_void_datasnapshot_nsstring) (FIRDataSnapshot *snapshot, NSString *prevName);
+typedef void (^fbt_void_datasnapshot) (FIRDataSnapshot *snapshot);
+typedef void (^fbt_void_user)(FAuthData *user);
+typedef void (^fbt_void_nsstring_id)(NSString* status, id data);
+typedef void (^fbt_void_nserror_id)(NSError* error, id data);
+typedef void (^fbt_void_nserror)(NSError *error);
+typedef void (^fbt_void_nserror_ref)(NSError* error, FIRDatabaseReference * ref);
+typedef void (^fbt_void_nserror_user)(NSError* error, FAuthData * user);
+typedef void (^fbt_void_nserror_json)(NSError* error, NSDictionary* json);
+typedef void (^fbt_void_nsdictionary)(NSDictionary *data);
+typedef id (^fbt_id_node_nsstring)(id<FNode> node, NSString* childName);
+
+#endif
diff --git a/Firebase/Database/Utilities/FUtilities.h b/Firebase/Database/Utilities/FUtilities.h
new file mode 100644
index 0000000..f5e312f
--- /dev/null
+++ b/Firebase/Database/Utilities/FUtilities.h
@@ -0,0 +1,76 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FIRLogger.h"
+#import "FParsedUrl.h"
+
+@interface FUtilities : NSObject
+
++ (NSArray *) splitString:(NSString *)str intoMaxSize:(const unsigned int)size;
++ (NSNumber *) LUIDGenerator;
++ (FParsedUrl *) parseUrl:(NSString *)url;
++ (NSString *) getJavascriptType:(id)obj;
++ (NSError *) errorForStatus:(NSString *)status andReason:(NSString *)reason;
++ (NSNumber *) intForString:(NSString *)string;
++ (NSString *) ieee754StringForNumber:(NSNumber *)val;
++ (void) setLoggingEnabled:(BOOL)enabled;
++ (BOOL) getLoggingEnabled;
+
++ (NSString*) minName;
++ (NSString*) maxName;
++ (NSComparisonResult) compareKey:(NSString *)a toKey:(NSString *)b;
++ (NSComparator) stringComparator;
++ (NSComparator) keyComparator;
+
++ (double)randomDouble;
+
+@end
+
+typedef enum {
+ FLogLevelDebug = 1,
+ FLogLevelInfo = 2,
+ FLogLevelWarn = 3,
+ FLogLevelError = 4,
+ FLogLevelNone = 5
+} FLogLevel;
+
+// Log tags
+FOUNDATION_EXPORT NSString *const kFPersistenceLogTag;
+
+#define FFLog(code, format, ...) FFDebug((code), (format), ##__VA_ARGS__)
+
+#define FFDebug(code, format, ...) do { \
+ if (FFIsLoggingEnabled(FLogLevelDebug)) { \
+ FIRLogDebug(kFIRLoggerDatabase, (code), (format), ##__VA_ARGS__); \
+ } \
+} while(0)
+
+#define FFInfo(code, format, ...) do { \
+ if (FFIsLoggingEnabled(FLogLevelInfo)) { \
+ FIRLogError(kFIRLoggerDatabase, (code), (format), ##__VA_ARGS__); \
+ } \
+} while(0)
+
+#define FFWarn(code, format, ...) do { \
+ if (FFIsLoggingEnabled(FLogLevelWarn)) { \
+ FIRLogWarning(kFIRLoggerDatabase, (code), (format), ##__VA_ARGS__); \
+ } \
+} while(0)
+
+BOOL FFIsLoggingEnabled(FLogLevel logLevel);
+void firebaseUncaughtExceptionHandler(NSException *exception);
+void firebaseJobsTroll(void);
diff --git a/Firebase/Database/Utilities/FUtilities.m b/Firebase/Database/Utilities/FUtilities.m
new file mode 100644
index 0000000..7c25e3b
--- /dev/null
+++ b/Firebase/Database/Utilities/FUtilities.m
@@ -0,0 +1,389 @@
+/*
+ * 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 "FUtilities.h"
+#import "FStringUtilities.h"
+#import "FConstants.h"
+#import "FAtomicNumber.h"
+
+#define ARC4RANDOM_MAX 0x100000000
+#define INTEGER_32_MIN (-2147483648)
+#define INTEGER_32_MAX 2147483647
+
+#pragma mark -
+#pragma mark C functions
+
+static FLogLevel logLevel = FLogLevelInfo; // Default log level is info
+static NSMutableDictionary* options = nil;
+
+BOOL FFIsLoggingEnabled(FLogLevel level) {
+ return level >= logLevel;
+}
+
+void firebaseJobsTroll(void) {
+ FFLog(@"I-RDB095001", @"password super secret; JFK conspiracy; Hello there! Having fun digging through Firebase? We're always hiring! jobs@firebase.com");
+}
+
+#pragma mark -
+#pragma mark Private property and singleton specification
+
+@interface FUtilities() {
+
+}
+
+@property (nonatomic, strong) FAtomicNumber* localUid;
+
++ (FUtilities*)singleton;
+
+@end
+
+@implementation FUtilities
+
+@synthesize localUid;
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ self.localUid = [[FAtomicNumber alloc] init];
+ }
+ return self;
+}
+
+// TODO: We really want to be able to set the log level
++ (void) setLoggingEnabled:(BOOL)enabled {
+ logLevel = enabled ? FLogLevelDebug : FLogLevelInfo;
+}
+
++ (BOOL) getLoggingEnabled {
+ return logLevel == FLogLevelDebug;
+}
+
++ (FUtilities*) singleton
+{
+ static dispatch_once_t pred = 0;
+ __strong static id _sharedObject = nil;
+ dispatch_once(&pred, ^{
+ _sharedObject = [[self alloc] init]; // or some other init method
+ });
+ return _sharedObject;
+}
+
+// Refactor as a category of NSString
++ (NSArray *) splitString:(NSString *) str intoMaxSize:(const unsigned int) size {
+ if(str.length <= size) {
+ return [NSArray arrayWithObject:str];
+ }
+
+ NSMutableArray* dataSegs = [[NSMutableArray alloc] init];
+ for(int c = 0; c < str.length; c += size) {
+ if (c + size > str.length) {
+ int rangeStart = c;
+ unsigned long rangeLength = size - ((c + size) - str.length);
+ [dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
+ }
+ else {
+ int rangeStart = c;
+ int rangeLength = size;
+ [dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
+ }
+ }
+ return dataSegs;
+}
+
++ (NSNumber *) LUIDGenerator {
+ FUtilities* f = [FUtilities singleton];
+ return [f.localUid getAndIncrement];
+}
+
++ (NSString *) decodePath:(NSString *)pathString {
+ NSMutableArray* decodedPieces = [[NSMutableArray alloc] init];
+ NSArray* pieces = [pathString componentsSeparatedByString:@"/"];
+ for (NSString* piece in pieces) {
+ if (piece.length > 0) {
+ [decodedPieces addObject:[FStringUtilities urlDecoded:piece]];
+ }
+ }
+ return [NSString stringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]];
+}
+
++ (FParsedUrl *) parseUrl:(NSString *)url {
+ NSString* original = url;
+ //NSURL* n = [[NSURL alloc] initWithString:url]
+
+ NSString* host;
+ NSString* namespace;
+ bool secure;
+
+ NSString* scheme = nil;
+ FPath* path = nil;
+ NSRange colonIndex = [url rangeOfString:@"//"];
+ if (colonIndex.location != NSNotFound) {
+ scheme = [url substringToIndex:colonIndex.location - 1];
+ url = [url substringFromIndex:colonIndex.location + 2];
+ }
+ NSInteger slashIndex = [url rangeOfString:@"/"].location;
+ if (slashIndex == NSNotFound) {
+ slashIndex = url.length;
+ }
+
+ host = [[url substringToIndex:slashIndex] lowercaseString];
+ if (slashIndex >= url.length) {
+ url = @"";
+ } else {
+ url = [url substringFromIndex:slashIndex + 1];
+ }
+
+ NSArray *parts = [host componentsSeparatedByString:@"."];
+ if([parts count] == 3) {
+ NSInteger colonIndex = [[parts objectAtIndex:2] rangeOfString:@":"].location;
+ if (colonIndex != NSNotFound) {
+ // we have a port, use the provided scheme
+ secure = [scheme isEqualToString:@"https"];
+ } else {
+ secure = YES;
+ }
+
+ namespace = [[parts objectAtIndex:0] lowercaseString];
+ NSString* pathString = [self decodePath:[NSString stringWithFormat:@"/%@", url]];
+ path = [[FPath alloc] initWith:pathString];
+ }
+ else {
+ [NSException raise:@"No Firebase database specified." format:@"No Firebase database found for input: %@", url];
+ }
+
+ FRepoInfo* repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:secure withNamespace:namespace];
+
+ FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", original, [repoInfo description], [repoInfo connectionURL], repoInfo.namespace, [path description]);
+
+ FParsedUrl* parsedUrl = [[FParsedUrl alloc] init];
+ parsedUrl.repoInfo = repoInfo;
+ parsedUrl.path = path;
+
+ return parsedUrl;
+}
+
+/*
+ case str: JString => priString + "string:" + str.s;
+ case bool: JBool => priString + "boolean:" + bool.value;
+ case double: JDouble => priString + "number:" + double.num;
+ case int: JInt => priString + "number:" + int.num;
+ case _ => {
+ error("Leaf node has value '" + data.value + "' of invalid type '" + data.value.getClass.toString + "'");
+ "";
+ }
+ */
+
++ (NSString *) getJavascriptType:(id)obj {
+ if ([obj isKindOfClass:[NSDictionary class]]) {
+ return kJavaScriptObject;
+ } else if([obj isKindOfClass:[NSString class]]) {
+ return kJavaScriptString;
+ }
+ else if ([obj isKindOfClass:[NSNumber class]]) {
+ // We used to just compare to @encode(BOOL) as suggested at
+ // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but on arm64, @encode(BOOL) returns "B"
+ // instead of "c" even though objCType still returns 'c' (signed char). So check both.
+ if(strcmp([obj objCType], @encode(BOOL)) == 0 ||
+ strcmp([obj objCType], @encode(signed char)) == 0) {
+ return kJavaScriptBoolean;
+ }
+ else {
+ return kJavaScriptNumber;
+ }
+ }
+ else {
+ return kJavaScriptNull;
+ }
+}
+
++ (NSError *) errorForStatus:(NSString *)status andReason:(NSString *)reason {
+ static dispatch_once_t pred = 0;
+ __strong static NSDictionary* errorMap = nil;
+ __strong static NSDictionary* errorCodes = nil;
+ dispatch_once(&pred, ^{
+ errorMap = @{
+ @"permission_denied": @"Permission Denied",
+ @"unavailable": @"Service is unavailable",
+ kFErrorWriteCanceled: @"Write cancelled by user"
+ };
+ errorCodes = @{
+ @"permission_denied": @1,
+ @"unavailable": @2,
+ kFErrorWriteCanceled: @3
+ };
+ });
+
+ if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
+ return nil;
+ } else {
+ NSInteger code;
+ NSString* desc = nil;
+ if (reason) {
+ desc = reason;
+ } else if ([errorMap objectForKey:status] != nil) {
+ desc = [errorMap objectForKey:status];
+ } else {
+ desc = status;
+ }
+
+ if ([errorCodes objectForKey:status] != nil) {
+ NSNumber* num = [errorCodes objectForKey:status];
+ code = [num integerValue];
+ } else {
+ // XXX what to do here?
+ code = 9999;
+ }
+
+ return [[NSError alloc] initWithDomain:kFErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: desc}];
+ }
+}
+
++ (NSNumber *) intForString:(NSString *)string {
+ static NSCharacterSet *notDigits = nil;
+ if (!notDigits) {
+ notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
+ }
+ if ([string rangeOfCharacterFromSet:notDigits].length == 0) {
+ NSInteger num;
+ NSScanner* scanner = [NSScanner scannerWithString:string];
+ if ([scanner scanInteger:&num]) {
+ return [NSNumber numberWithInteger:num];
+ }
+ }
+ return nil;
+}
+
++ (NSString *) ieee754StringForNumber:(NSNumber *)val {
+ double d = [val doubleValue];
+ NSData* data = [NSData dataWithBytes:&d length:sizeof(double)];
+ NSMutableString* str = [[NSMutableString alloc] init];
+ const unsigned char* buffer = (const unsigned char*)[data bytes];
+ for (int i = 0; i < data.length; i++) {
+ unsigned char byte = buffer[7 - i];
+ [str appendFormat:@"%02x", byte];
+ }
+ return str;
+}
+
+static inline BOOL tryParseStringToInt(__unsafe_unretained NSString* str, NSInteger* integer) {
+ // First do some cheap checks (NOTE: The below checks are significantly faster than an equivalent regex :-( ).
+ NSUInteger length = str.length;
+ if (length > 11 || length == 0) {
+ return NO;
+ }
+ long long value = 0;
+ BOOL negative = NO;
+ NSUInteger i = 0;
+ if ([str characterAtIndex:0] == '-') {
+ if (length == 1) {
+ return NO;
+ }
+ negative = YES;
+ i = 1;
+ }
+ for(; i < length; i++) {
+ unichar c = [str characterAtIndex:i];
+ // Must be a digit, or '-' if it's the first char.
+ if (c < '0' || c > '9') {
+ return NO;
+ } else {
+ int charValue = c - '0';
+ value = value*10 + charValue;
+ }
+ }
+
+ value = (negative) ? -value : value;
+
+ if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) {
+ return NO;
+ } else {
+ *integer = (NSInteger)value;
+ return YES;
+ }
+}
+
++ (NSString *) maxName {
+ static dispatch_once_t once;
+ static NSString *maxName;
+ dispatch_once(&once, ^{
+ maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"];
+ });
+ return maxName;
+}
+
++ (NSString *) minName {
+ static dispatch_once_t once;
+ static NSString *minName;
+ dispatch_once(&once, ^{
+ minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"];
+ });
+ return minName;
+}
+
++ (NSComparisonResult) compareKey:(NSString *)a toKey:(NSString *)b {
+ if (a == b) {
+ return NSOrderedSame;
+ } else if (a == [FUtilities minName] || b == [FUtilities maxName]) {
+ return NSOrderedAscending;
+ } else if (b == [FUtilities minName] || a == [FUtilities maxName]) {
+ return NSOrderedDescending;
+ } else {
+ NSInteger aAsInt, bAsInt;
+ if (tryParseStringToInt(a, &aAsInt)) {
+ if (tryParseStringToInt(b, &bAsInt)) {
+ if (aAsInt > bAsInt) {
+ return NSOrderedDescending;
+ } else if (aAsInt < bAsInt) {
+ return NSOrderedAscending;
+ } else if (a.length > b.length) {
+ return NSOrderedDescending;
+ } else if (a.length < b.length) {
+ return NSOrderedAscending;
+ } else {
+ return NSOrderedSame;
+ }
+ } else {
+ return (NSComparisonResult) NSOrderedAscending;
+ }
+ } else if (tryParseStringToInt(b, &bAsInt)) {
+ return (NSComparisonResult) NSOrderedDescending;
+ } else {
+ // Perform literal character by character search to prevent a > b && b > a issues.
+ // Note that calling -(NSString *)decomposedStringWithCanonicalMapping also works.
+ return [a compare:b options:NSLiteralSearch];
+ }
+ }
+}
+
++ (NSComparator) keyComparator {
+ return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
+ return [FUtilities compareKey:a toKey:b];
+ };
+}
+
++ (NSComparator) stringComparator {
+ return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
+ return [a compare:b];
+ };
+}
+
++ (double) randomDouble {
+ return ((double) arc4random() / ARC4RANDOM_MAX);
+}
+
+@end
+
diff --git a/Firebase/Database/Utilities/FValidation.h b/Firebase/Database/Utilities/FValidation.h
new file mode 100644
index 0000000..faa8f76
--- /dev/null
+++ b/Firebase/Database/Utilities/FValidation.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FPath.h"
+#import "FIRDataEventType.h"
+#import "FParsedUrl.h"
+#import "FTypedefs.h"
+
+@interface FValidation : NSObject
+
++ (void) validateFrom:(NSString *)fn writablePath:(FPath *)path;
++ (void) validateFrom:(NSString *)fn knownEventType:(FIRDataEventType)event;
++ (void) validateFrom:(NSString *)fn validPathString:(NSString *)pathString;
++ (void) validateFrom:(NSString *)fn validRootPathString:(NSString *)pathString;
++ (void) validateFrom:(NSString *)fn validKey:(NSString *)key;
++ (void) validateFrom:(NSString *)fn validURL:(FParsedUrl *)parsedUrl;
+
++ (void) validateToken:(NSString *)token;
+
+// Functions for handling passing errors back
++ (void) handleError:(NSError *)error withUserCallback:(fbt_void_nserror_id)userCallback;
++ (void) handleError:(NSError *)error withSuccessCallback:(fbt_void_nserror)userCallback;
+
+// Functions used for validating while creating snapshots in FSnapshotUtilities
++ (BOOL) validateFrom:(NSString*)fn isValidLeafValue:(id)value withPath:(NSArray*)path;
++ (void) validateFrom:(NSString*)fn validDictionaryKey:(id)keyId withPath:(NSArray*)path;
++ (void) validateFrom:(NSString*)fn validUpdateDictionaryKey:(id)keyId withValue:(id)value;
++ (void) validateFrom:(NSString*)fn isValidPriorityValue:(id)value withPath:(NSArray*)path;
++ (BOOL) validatePriorityValue:value;
+
+@end
diff --git a/Firebase/Database/Utilities/FValidation.m b/Firebase/Database/Utilities/FValidation.m
new file mode 100644
index 0000000..c4c6b2b
--- /dev/null
+++ b/Firebase/Database/Utilities/FValidation.m
@@ -0,0 +1,312 @@
+/*
+ * 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 "FValidation.h"
+#import "FConstants.h"
+#import "FParsedUrl.h"
+#import "FTypedefs.h"
+
+
+// Have to escape: * ? + [ ( ) { } ^ $ | \ . /
+// See: https://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html
+
+NSString *const kInvalidPathCharacters = @"[].#$";
+NSString *const kInvalidKeyCharacters = @"[].#$/";
+
+@implementation FValidation
+
++ (void) validateFrom:(NSString *)fn writablePath:(FPath *)path {
+ if([[path getFront] isEqualToString:kDotInfoPrefix]) {
+ @throw [[NSException alloc] initWithName:@"WritablePathValidation" reason:[NSString stringWithFormat:@"(%@) failed to path %@: Can't modify data under %@", fn, [path description], kDotInfoPrefix] userInfo:nil];
+ }
+}
+
++ (void) validateFrom:(NSString*)fn knownEventType:(FIRDataEventType)event {
+ switch (event) {
+ case FIRDataEventTypeValue:
+ case FIRDataEventTypeChildAdded:
+ case FIRDataEventTypeChildChanged:
+ case FIRDataEventTypeChildMoved:
+ case FIRDataEventTypeChildRemoved:
+ return;
+ break;
+ default:
+ @throw [[NSException alloc] initWithName:@"KnownEventTypeValidation" reason:[NSString stringWithFormat:@"(%@) Unknown event type: %d", fn, (int) event] userInfo:nil];
+ break;
+ }
+}
+
++ (BOOL) isValidPathString:(NSString *)pathString {
+ static dispatch_once_t token;
+ static NSCharacterSet *badPathChars = nil;
+ dispatch_once(&token, ^{
+ badPathChars = [NSCharacterSet characterSetWithCharactersInString:kInvalidPathCharacters];
+ });
+ return pathString != nil && [pathString length] != 0 &&
+ [pathString rangeOfCharacterFromSet:badPathChars].location == NSNotFound;
+}
+
++ (void) validateFrom:(NSString *)fn validPathString:(NSString *)pathString {
+ if(! [self isValidPathString:pathString]) {
+ @throw [[NSException alloc] initWithName:@"InvalidPathValidation" reason:[NSString stringWithFormat:@"(%@) Must be a non-empty string and not contain '.' '#' '$' '[' or ']'", fn] userInfo:nil];
+ }
+}
+
++ (void) validateFrom:(NSString *)fn validRootPathString:(NSString *)pathString {
+ static dispatch_once_t token;
+ static NSRegularExpression *dotInfoRegex = nil;
+ dispatch_once(&token, ^{
+ dotInfoRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\/*\\.info(\\/|$)" options:0 error:nil];
+ });
+
+ NSString *tempPath = pathString;
+ // HACK: Obj-C regex are kinda' slow. Do a plain string search first before bothering with the regex.
+ if ([pathString rangeOfString:@".info"].location != NSNotFound) {
+ tempPath = [dotInfoRegex stringByReplacingMatchesInString:pathString options:0 range:NSMakeRange(0, pathString.length) withTemplate:@"/"];
+ }
+ [self validateFrom:fn validPathString:tempPath];
+}
+
++ (BOOL) isValidKey:(NSString *)key {
+ static dispatch_once_t token;
+ static NSCharacterSet *badKeyChars = nil;
+ dispatch_once(&token, ^{
+ badKeyChars = [NSCharacterSet characterSetWithCharactersInString:kInvalidKeyCharacters];
+ });
+ return key != nil && key.length > 0 && [key rangeOfCharacterFromSet:badKeyChars].location == NSNotFound;
+}
+
++ (void) validateFrom:(NSString *)fn validKey:(NSString *)key {
+ if (![self isValidKey:key]) {
+ @throw [[NSException alloc] initWithName:@"InvalidKeyValidation" reason:[NSString stringWithFormat:@"(%@) Must be a non-empty string and not contain '/' '.' '#' '$' '[' or ']'", fn] userInfo:nil];
+ }
+}
+
++ (void) validateFrom:(NSString *)fn validURL:(FParsedUrl *)parsedUrl {
+ NSString* pathString = [parsedUrl.path description];
+ [self validateFrom:fn validRootPathString:pathString];
+}
+
+#pragma mark -
+#pragma mark Authentication validation
+
++ (BOOL) stringNonempty:(NSString *)str {
+ return str != nil && ![str isKindOfClass:[NSNull class]] && str.length > 0;
+}
+
++ (void) validateToken:(NSString *)token {
+ if (![FValidation stringNonempty:token]) {
+ [NSException raise:NSInvalidArgumentException format:@"Can't have empty string or nil for custom token"];
+ }
+}
+
+#pragma mark -
+#pragma mark Handling authentication errors
+
+/**
+* This function immediately calls the callback.
+* It assumes that it is not on FirebaseWorker thread.
+* It assumes it's on a user-controlled thread.
+*/
++ (void) handleError:(NSError *)error withUserCallback:(fbt_void_nserror_id)userCallback {
+ if (userCallback) {
+ userCallback(error, nil);
+ }
+}
+
+/**
+* This function immediately calls the callback.
+* It assumes that it is not on FirebaseWorker thread.
+* It assumes it's on a user-controlled thread.
+*/
++ (void) handleError:(NSError *)error withSuccessCallback:(fbt_void_nserror)userCallback {
+ if (userCallback) {
+ userCallback(error);
+ }
+}
+
+#pragma mark -
+#pragma mark Snapshot validation
+
++ (BOOL) validateFrom:(NSString*)fn isValidLeafValue:(id)value withPath:(NSArray*)path {
+ if ([value isKindOfClass:[NSString class]]) {
+ // Try to avoid conversion to bytes if possible
+ NSString* theString = value;
+ if ([theString maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kFirebaseMaxLeafSize &&
+ [theString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kFirebaseMaxLeafSize) {
+ 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:@"(%@) String exceeds max size of %u utf8 bytes: %@", fn, (int)kFirebaseMaxLeafSize, pathString] userInfo:nil];
+ }
+ return YES;
+ }
+
+ else if ([value isKindOfClass:[NSNumber class]]) {
+ // Cannot store NaN, but otherwise can store NSNumbers.
+ if ([[NSDecimalNumber notANumber] isEqualToNumber:value]) {
+ 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 NaN at path: %@.", fn, pathString] userInfo:nil];
+ }
+ return YES;
+ }
+
+ else if ([value isKindOfClass:[NSDictionary class]]) {
+ NSDictionary* dval = value;
+ if (dval[kServerValueSubKey] != nil) {
+ if ([dval count] > 1) {
+ 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 other keys with server value keys.%@.", fn, pathString] userInfo:nil];
+ }
+ return YES;
+ }
+ return NO;
+ }
+
+ else if (value == [NSNull null] || value == nil) {
+ // Null is valid type to store at leaf
+ return YES;
+ }
+
+ return NO;
+}
+
++ (NSString*) parseAndValidateKey:(id)keyId fromFunction:(NSString*)fn path:(NSArray*)path {
+ if (![keyId isKindOfClass:[NSString class]]) {
+ 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:@"(%@) Non-string keys are not allowed in object at path: %@", fn, pathString] userInfo:nil];
+ }
+ return (NSString*)keyId;
+}
+
++ (void) validateFrom:(NSString*)fn validDictionaryKey:(id)keyId withPath:(NSArray*)path {
+ NSString *key = [self parseAndValidateKey:keyId fromFunction:fn path:path];
+ if (![key isEqualToString:kPayloadPriority] && ![key isEqualToString:kPayloadValue] && ![key isEqualToString:kServerValueSubKey] && ![FValidation isValidKey:key]) {
+ 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:@"(%@) Invalid key in object at path: %@. Keys must be non-empty and cannot contain '/' '.' '#' '$' '[' or ']'", fn, pathString] userInfo:nil];
+ }
+}
+
++ (void) validateFrom:(NSString*)fn validUpdateDictionaryKey:(id)keyId withValue:(id)value {
+ FPath *path = [FPath pathWithString:[self parseAndValidateKey:keyId fromFunction:fn path:@[]]];
+ __block NSInteger keyNum = 0;
+ [path enumerateComponentsUsingBlock:^void (NSString *key, BOOL *stop) {
+ if ([key isEqualToString:kPayloadPriority] && keyNum == [path length] - 1) {
+ [self validateFrom:fn isValidPriorityValue:value withPath:@[]];
+ } else {
+ keyNum++;
+
+ if (![FValidation isValidKey:key]) {
+ @throw [[NSException alloc] initWithName:@"InvalidFirebaseData" reason:[NSString stringWithFormat:@"(%@) Invalid key in object. Keys must be non-empty and cannot contain '.' '#' '$' '[' or ']'", fn] userInfo:nil];
+ }
+ }
+ }];
+}
+
++ (void) validateFrom:(NSString*)fn isValidPriorityValue:(id)value withPath:(NSArray*)path {
+ [self validateFrom:fn isValidPriorityValue:value withPath:path throwError:YES];
+}
+
+/**
+* Returns YES if priority is valid.
+*/
++ (BOOL)validatePriorityValue:value {
+ return [self validateFrom:nil isValidPriorityValue:value withPath:nil throwError:NO];
+}
+
+/**
+* Helper for validating priorities. If passed YES for throwError, it'll throw descriptive errors on validation
+* problems. Else, it'll just return YES/NO.
+*/
++ (BOOL) validateFrom:(NSString*)fn isValidPriorityValue:(id)value withPath:(NSArray*)path throwError:(BOOL)throwError {
+ if ([value isKindOfClass:[NSNumber class]]) {
+ if ([[NSDecimalNumber notANumber] isEqualToNumber:value]) {
+ if (throwError) {
+ 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 NaN as priority at path: %@.", fn, pathString] userInfo:nil];
+ } else {
+ return NO;
+ }
+ } else if (value == (id) kCFBooleanFalse || value == (id) kCFBooleanTrue) {
+ if (throwError) {
+ 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 true/false as priority at path: %@.", fn, pathString] userInfo:nil];
+ } else {
+ return NO;
+ }
+ }
+ }
+ else if ([value isKindOfClass:[NSDictionary class]]) {
+ NSDictionary *dval = value;
+ if (dval[kServerValueSubKey] != nil) {
+ if ([dval count] > 1) {
+ if (throwError) {
+ 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 other keys with server value keys as priority at path: %@.", fn, pathString] userInfo:nil];
+ } else {
+ return NO;
+ }
+ }
+ } else {
+ if (throwError) {
+ 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 an NSDictionary as priority at path: %@.", fn, pathString] userInfo:nil];
+ } else {
+ return NO;
+ }
+ }
+ }
+ else if ([value isKindOfClass:[NSArray class]]) {
+ if (throwError) {
+ 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 an NSArray as priority at path: %@.", fn, pathString] userInfo:nil];
+ } else {
+ return NO;
+ }
+ }
+
+ // It's valid!
+ return YES;
+}
+@end
diff --git a/Firebase/Database/Utilities/NSString+FURLUtils.h b/Firebase/Database/Utilities/NSString+FURLUtils.h
new file mode 100644
index 0000000..7bd39bc
--- /dev/null
+++ b/Firebase/Database/Utilities/NSString+FURLUtils.h
@@ -0,0 +1,24 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface NSString (FURLUtils)
+
+- (NSString *) urlEncoded;
+- (NSString *) urlDecoded;
+
+@end
diff --git a/Firebase/Database/Utilities/NSString+FURLUtils.m b/Firebase/Database/Utilities/NSString+FURLUtils.m
new file mode 100644
index 0000000..2e018c8
--- /dev/null
+++ b/Firebase/Database/Utilities/NSString+FURLUtils.m
@@ -0,0 +1,38 @@
+/*
+ * 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 "NSString+FURLUtils.h"
+
+@implementation NSString (FURLUtils)
+
+- (NSString *) urlDecoded {
+ NSString* replaced = [self stringByReplacingOccurrencesOfString:@"+" withString:@" "];
+ NSString* decoded = [replaced stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ // This is kind of a hack, but is generally how the js client works. We could run into trouble if
+ // some piece is a correctly escaped %-sequence, and another isn't. But, that's bad input anyways...
+ if (decoded) {
+ return decoded;
+ } else {
+ return replaced;
+ }
+}
+
+- (NSString *) urlEncoded {
+ CFStringRef urlString = CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)self, NULL, (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ", kCFStringEncodingUTF8);
+ return (__bridge NSString *) urlString;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h b/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h
new file mode 100644
index 0000000..bceeed2
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h
@@ -0,0 +1,25 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FTypedefs.h"
+
+@interface FTupleBoolBlock : NSObject
+
+@property (nonatomic, readwrite) BOOL boolean;
+@property (nonatomic, copy) fbt_void_void block;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m b/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m
new file mode 100644
index 0000000..c4cd8bf
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m
@@ -0,0 +1,24 @@
+/*
+ * 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 "FTupleBoolBlock.h"
+
+@implementation FTupleBoolBlock
+
+@synthesize boolean;
+@synthesize block;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h b/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h
new file mode 100644
index 0000000..6ec2375
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h
@@ -0,0 +1,24 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FTypedefs_Private.h"
+
+@interface FTupleCallbackStatus : NSObject
+@property (nonatomic, copy) fbt_void_nsstring_nsstring block;
+@property (nonatomic) NSString* status;
+@property (nonatomic) NSString* errorReason;
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m b/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m
new file mode 100644
index 0000000..05914bf
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m
@@ -0,0 +1,22 @@
+/*
+ * 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 "FTupleCallbackStatus.h"
+
+@implementation FTupleCallbackStatus
+@synthesize block;
+@synthesize status;
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleFirebase.h b/Firebase/Database/Utilities/Tuples/FTupleFirebase.h
new file mode 100644
index 0000000..ff84bbb
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleFirebase.h
@@ -0,0 +1,26 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FIRDatabaseReference.h"
+
+@interface FTupleFirebase : NSObject
+
+@property (nonatomic, strong) FIRDatabaseReference * one;
+@property (nonatomic, strong) FIRDatabaseReference * two;
+@property (nonatomic, strong) FIRDatabaseReference * three;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleFirebase.m b/Firebase/Database/Utilities/Tuples/FTupleFirebase.m
new file mode 100644
index 0000000..3956f8b
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleFirebase.m
@@ -0,0 +1,25 @@
+/*
+ * 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 "FTupleFirebase.h"
+
+@implementation FTupleFirebase
+
+@synthesize one;
+@synthesize two;
+@synthesize three;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleNodePath.h b/Firebase/Database/Utilities/Tuples/FTupleNodePath.h
new file mode 100644
index 0000000..fbf62c7
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleNodePath.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FPath.h"
+#import "FNode.h"
+
+@interface FTupleNodePath : NSObject
+
+@property (nonatomic, strong) FPath* path;
+@property (nonatomic, strong) id<FNode> node;
+
+- (id) initWithNode:(id<FNode>)aNode andPath:(FPath *)aPath;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleNodePath.m b/Firebase/Database/Utilities/Tuples/FTupleNodePath.m
new file mode 100644
index 0000000..eefc0c2
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleNodePath.m
@@ -0,0 +1,33 @@
+/*
+ * 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 "FTupleNodePath.h"
+
+@implementation FTupleNodePath
+
+@synthesize path;
+@synthesize node;
+
+- (id) initWithNode:(id<FNode>)aNode andPath:(FPath *)aPath {
+ self = [super init];
+ if (self) {
+ self.path = aPath;
+ self.node = aNode;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleObjectNode.h b/Firebase/Database/Utilities/Tuples/FTupleObjectNode.h
new file mode 100644
index 0000000..6fcb746
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleObjectNode.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FNode.h"
+
+@interface FTupleObjectNode : NSObject
+
+- (id)initWithObject:(id)aObj andNode:(id<FNode>)aNode;
+
+@property (nonatomic, strong) id<FNode> node;
+@property (nonatomic, strong) id obj;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleObjectNode.m b/Firebase/Database/Utilities/Tuples/FTupleObjectNode.m
new file mode 100644
index 0000000..4c533b0
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleObjectNode.m
@@ -0,0 +1,32 @@
+/*
+ * 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 "FTupleObjectNode.h"
+
+@implementation FTupleObjectNode
+
+@synthesize obj;
+@synthesize node;
+
+- (id)initWithObject:(id)aObj andNode:(id<FNode>)aNode {
+ self = [super init];
+ if (self) {
+ self.obj = aObj;
+ self.node = aNode;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleObjects.h b/Firebase/Database/Utilities/Tuples/FTupleObjects.h
new file mode 100644
index 0000000..4ff1fcf
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleObjects.h
@@ -0,0 +1,24 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface FTupleObjects : NSObject
+
+@property (nonatomic, strong) id objA;
+@property (nonatomic, strong) id objB;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleObjects.m b/Firebase/Database/Utilities/Tuples/FTupleObjects.m
new file mode 100644
index 0000000..a9e4c88
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleObjects.m
@@ -0,0 +1,24 @@
+/*
+ * 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 "FTupleObjects.h"
+
+@implementation FTupleObjects
+
+@synthesize objA;
+@synthesize objB;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h b/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h
new file mode 100644
index 0000000..91ad5e4
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FTypedefs_Private.h"
+
+@interface FTupleOnDisconnect : NSObject
+
+@property (strong, nonatomic) NSString* pathString;
+@property (strong, nonatomic) NSString* action;
+@property (strong, nonatomic) id data;
+@property (strong, nonatomic) fbt_void_nsstring_nsstring onComplete;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m b/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m
new file mode 100644
index 0000000..bd45822
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m
@@ -0,0 +1,26 @@
+/*
+ * 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 "FTupleOnDisconnect.h"
+
+@implementation FTupleOnDisconnect
+
+@synthesize pathString;
+@synthesize action;
+@synthesize data;
+@synthesize onComplete;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTuplePathValue.h b/Firebase/Database/Utilities/Tuples/FTuplePathValue.h
new file mode 100644
index 0000000..f7ed423
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTuplePathValue.h
@@ -0,0 +1,25 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@class FPath;
+
+@interface FTuplePathValue : NSObject
+@property (nonatomic, strong, readonly) FPath *path;
+@property (nonatomic, strong, readonly) id value;
+- (id) initWithPath:(FPath *)aPath value:(id)aValue;
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTuplePathValue.m b/Firebase/Database/Utilities/Tuples/FTuplePathValue.m
new file mode 100644
index 0000000..49240aa
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTuplePathValue.m
@@ -0,0 +1,38 @@
+/*
+ * 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 "FTuplePathValue.h"
+#import "FPath.h"
+
+@interface FTuplePathValue ()
+@property (nonatomic, strong, readwrite) id value;
+@property (nonatomic, strong, readwrite) FPath *path;
+@end
+
+@implementation FTuplePathValue
+@synthesize path;
+@synthesize value;
+
+- (id) initWithPath:(FPath *)aPath value:(id)aValue {
+ self = [super init];
+ if (self) {
+ self.value = aValue;
+ self.path = aPath;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h b/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h
new file mode 100644
index 0000000..f986916
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h
@@ -0,0 +1,30 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface FTupleRemovedQueriesEvents : NSObject
+/**
+* `FIRDatabaseQuery`s removed with [SyncPoint removeEventRegistration:]
+*/
+@property (nonatomic, strong, readonly) NSArray *removedQueries;
+/**
+* cancel events as FEvent
+*/
+@property (nonatomic, strong, readonly) NSArray *cancelEvents;
+
+- (id) initWithRemovedQueries:(NSArray *)removed cancelEvents:(NSArray *)events;
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m b/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m
new file mode 100644
index 0000000..818d16b
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m
@@ -0,0 +1,37 @@
+/*
+ * 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 "FTupleRemovedQueriesEvents.h"
+
+@interface FTupleRemovedQueriesEvents ()
+@property (nonatomic, strong, readwrite) NSArray *removedQueries;
+@property (nonatomic, strong, readwrite) NSArray *cancelEvents;
+@end
+
+@implementation FTupleRemovedQueriesEvents
+@synthesize removedQueries;
+@synthesize cancelEvents;
+
+- (id) initWithRemovedQueries:(NSArray *)removed cancelEvents:(NSArray *)events {
+ self = [super init];
+ if (self) {
+ self.removedQueries = removed;
+ self.cancelEvents = events;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h b/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h
new file mode 100644
index 0000000..5133d6d
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FPath.h"
+
+@interface FTupleSetIdPath : NSObject
+
+- (id) initWithSetId:(NSNumber *)aSetId andPath:(FPath *)aPath;
+
+@property (strong, nonatomic) NSNumber* setId;
+@property (strong, nonatomic) FPath* path;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m b/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m
new file mode 100644
index 0000000..5d3312b
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m
@@ -0,0 +1,33 @@
+/*
+ * 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 "FTupleSetIdPath.h"
+
+@implementation FTupleSetIdPath
+
+@synthesize path;
+@synthesize setId;
+
+- (id) initWithSetId:(NSNumber *)aSetId andPath:(FPath *)aPath {
+ self = [super init];
+ if (self) {
+ self.setId = aSetId;
+ self.path = aPath;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleStringNode.h b/Firebase/Database/Utilities/Tuples/FTupleStringNode.h
new file mode 100644
index 0000000..e3fec80
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleStringNode.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FNode.h"
+
+@interface FTupleStringNode : NSObject
+
+- (id)initWithString:(NSString *)aString andNode:(id<FNode>)aNode;
+
+@property (nonatomic, strong) id<FNode> node;
+@property (nonatomic, strong) NSString* string;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleStringNode.m b/Firebase/Database/Utilities/Tuples/FTupleStringNode.m
new file mode 100644
index 0000000..f058a8e
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleStringNode.m
@@ -0,0 +1,34 @@
+/*
+ * 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 "FTupleStringNode.h"
+
+@implementation FTupleStringNode
+
+@synthesize string;
+@synthesize node;
+
+- (id)initWithString:(NSString *)aString andNode:(id<FNode>)aNode {
+ self = [super init];
+ if (self) {
+ self.string = aString;
+ self.node = aNode;
+ }
+ return self;
+}
+
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleTSN.h b/Firebase/Database/Utilities/Tuples/FTupleTSN.h
new file mode 100644
index 0000000..bc62b2d
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleTSN.h
@@ -0,0 +1,25 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FTupleStringNode.h"
+
+@interface FTupleTSN : NSObject
+
+@property (nonatomic, strong) FTupleStringNode* from;
+@property (nonatomic, strong) FTupleStringNode* to;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleTSN.m b/Firebase/Database/Utilities/Tuples/FTupleTSN.m
new file mode 100644
index 0000000..348c319
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleTSN.m
@@ -0,0 +1,24 @@
+/*
+ * 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 "FTupleTSN.h"
+
+@implementation FTupleTSN
+
+@synthesize from;
+@synthesize to;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleTransaction.h b/Firebase/Database/Utilities/Tuples/FTupleTransaction.h
new file mode 100644
index 0000000..c9dcf4b
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleTransaction.h
@@ -0,0 +1,74 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FPath.h"
+#import "FTypedefs_Private.h"
+#import "FTypedefs.h"
+
+@interface FTupleTransaction : NSObject
+
+@property (nonatomic, strong) FPath* path;
+@property (nonatomic, copy) fbt_transactionresult_mutabledata update;
+@property (nonatomic, copy) fbt_void_nserror_bool_datasnapshot onComplete;
+@property (nonatomic) FTransactionStatus status;
+
+/**
+* Used when combining transaction at different locations to figure out which one goes first.
+*/
+@property (nonatomic, strong) NSNumber* order;
+/**
+* Whether to raise local events for this transaction
+*/
+@property (nonatomic) BOOL applyLocally;
+
+/**
+* Count how many times we've retried the transaction
+*/
+@property (nonatomic) int retryCount;
+
+/**
+* Function to call to clean up our listener
+*/
+@property (nonatomic, copy) fbt_void_void unwatcher;
+
+/**
+* Stores why a transaction was aborted
+*/
+@property (nonatomic, strong, readonly) NSString* abortStatus;
+@property (nonatomic, strong, readonly) NSString* abortReason;
+
+- (void)setAbortStatus:(NSString *)abortStatus reason:(NSString *)reason;
+- (NSError *)abortError;
+
+@property (nonatomic, strong) NSNumber *currentWriteId;
+
+/**
+* Stores the input snapshot, before the update
+*/
+@property (nonatomic, strong) id<FNode> currentInputSnapshot;
+
+/**
+* Stores the unresolved (for server values) output snapshot, after the update
+*/
+@property (nonatomic, strong) id<FNode> currentOutputSnapshotRaw;
+
+/**
+ * Stores the resolved (for server values) output snapshot, after the update
+ */
+@property (nonatomic, strong) id<FNode> currentOutputSnapshotResolved;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleTransaction.m b/Firebase/Database/Utilities/Tuples/FTupleTransaction.m
new file mode 100644
index 0000000..bcff54e
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleTransaction.m
@@ -0,0 +1,38 @@
+/*
+ * 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 "FTupleTransaction.h"
+#import "FUtilities.h"
+
+@interface FTupleTransaction ()
+
+@property (nonatomic, strong) NSString *abortStatus;
+@property (nonatomic, strong) NSString *abortReason;
+
+@end
+
+@implementation FTupleTransaction
+
+- (void)setAbortStatus:(NSString *)abortStatus reason:(NSString *)reason {
+ self.abortStatus = abortStatus;
+ self.abortReason = reason;
+}
+
+- (NSError *)abortError {
+ return (self.abortStatus != nil) ? [FUtilities errorForStatus:self.abortStatus andReason:self.abortReason] : nil;
+}
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleUserCallback.h b/Firebase/Database/Utilities/Tuples/FTupleUserCallback.h
new file mode 100644
index 0000000..d598217
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleUserCallback.h
@@ -0,0 +1,31 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FTypedefs.h"
+#import "FQueryParams.h"
+
+@interface FTupleUserCallback : NSObject
+
+- (id) initWithHandle:(NSUInteger)handle;
+
+@property (nonatomic, copy) fbt_void_datasnapshot_nsstring datasnapshotPrevnameCallback;
+@property (nonatomic, copy) fbt_void_datasnapshot datasnapshotCallback;
+@property (nonatomic, copy) fbt_void_nserror cancelCallback;
+@property (nonatomic, copy) FQueryParams* queryParams;
+@property (nonatomic) NSUInteger handle;
+
+@end
diff --git a/Firebase/Database/Utilities/Tuples/FTupleUserCallback.m b/Firebase/Database/Utilities/Tuples/FTupleUserCallback.m
new file mode 100644
index 0000000..dc33bbd
--- /dev/null
+++ b/Firebase/Database/Utilities/Tuples/FTupleUserCallback.m
@@ -0,0 +1,35 @@
+/*
+ * 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 "FTupleUserCallback.h"
+
+@implementation FTupleUserCallback
+
+@synthesize datasnapshotCallback;
+@synthesize datasnapshotPrevnameCallback;
+@synthesize cancelCallback;
+@synthesize queryParams;
+@synthesize handle;
+
+- (id) initWithHandle:(NSUInteger)theHandle {
+ self = [super init];
+ if (self) {
+ self.handle = theHandle;
+ }
+ return self;
+}
+
+@end