16
16
17
17
#import " FirebaseDatabase/Sources/Utilities/FNextPushId.h"
18
18
#import " FirebaseDatabase/Sources/Utilities/FUtilities.h"
19
+ #import " FirebaseDatabase/Sources/Utilities/FValidation.h"
19
20
20
21
static NSString *const PUSH_CHARS =
21
22
@" -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" ;
22
23
23
- static NSString *const MIN_PUSH_CHAR = @" - " ;
24
+ static NSString *const MIN_PUSH_CHAR = @" " ;
24
25
25
- static NSString *const MAX_PUSH_CHAR = @" z " ;
26
+ static NSString *const MAX_PUSH_CHAR = @" \uFFFF " ;
26
27
27
28
static NSInteger const MAX_KEY_LEN = 786 ;
28
29
30
+ static unichar const LOW_SURROGATE_PAIR_START = 0xDC00 ;
31
+ static unichar const LOW_SURROGATE_PAIR_END = 0xDFFF ;
32
+ static unichar const HIGH_SURROGATE_PAIR_START = 0xD800 ;
33
+ static unichar const HIGH_SURROGATE_PAIR_END = 0xDBFF ;
34
+
29
35
@implementation FNextPushId
30
36
31
37
+ (NSString *)get : (NSTimeInterval )currentTime {
@@ -65,7 +71,8 @@ + (NSString *)get:(NSTimeInterval)currentTime {
65
71
return [NSString stringWithString: id ];
66
72
}
67
73
68
- + (NSString *)successor : (NSString *_Nonnull)key {
74
+ + (NSString *)from : (NSString *)fn successor : (NSString *_Nonnull)key {
75
+ [FValidation validateFrom: fn validKey: key];
69
76
NSInteger keyAsInt;
70
77
if ([FUtilities tryParseString: key asInt: &keyAsInt]) {
71
78
if (keyAsInt == [FUtilities int32max ]) {
@@ -93,19 +100,91 @@ + (NSString *)successor:(NSString *_Nonnull)key {
93
100
return [FUtilities maxName ];
94
101
}
95
102
96
- NSString *source =
97
- [NSString stringWithFormat: @" %C " , [next characterAtIndex: i]];
98
- NSInteger sourceIndex = [PUSH_CHARS rangeOfString: source].location ;
99
- NSString *sourcePlusOne = [NSString
100
- stringWithFormat: @" %C " , [PUSH_CHARS characterAtIndex: sourceIndex + 1 ]];
103
+ unichar character = [next characterAtIndex: i];
104
+ unichar plusOne = character + 1 ;
105
+ BOOL removePreviousCharacter = NO ;
106
+ BOOL replaceWithLowestSurrogatePair = NO ;
107
+ switch (plusOne) {
108
+ case 0x23 : // '#'
109
+ case 0x24 : // '$'
110
+ plusOne = 0x25 ;
111
+ break ;
112
+
113
+ case 0x2E : // '.'
114
+ case 0x2F : // '/'
115
+ plusOne = 0x30 ;
116
+ break ;
117
+
118
+ case 0x5B : // '['
119
+ plusOne = 0x5C ;
120
+ break ;
121
+
122
+ case 0x5D : // ']'
123
+ plusOne = 0x5E ;
124
+ break ;
101
125
102
- [next replaceCharactersInRange: NSMakeRange (i, i + 1 )
126
+ case 0x7F : // control character: del
127
+ plusOne = 0x80 ;
128
+ break ;
129
+
130
+ case HIGH_SURROGATE_PAIR_START: // 0xD800
131
+ // We added one to 0xD7FF and entered surrogate pair zone
132
+ // Replace with the lowest surrogate pair here
133
+ replaceWithLowestSurrogatePair = YES ;
134
+
135
+ case LOW_SURROGATE_PAIR_END + 1 : // 0xE000
136
+ // If the previous character is the highest surrogate value
137
+ // then we increment to the value 0xE000 by replacing the surrogate
138
+ // pair by the single value 0xE000 (the value of plusOne)
139
+ // Otherwise we increment the high surrogate value and set the low
140
+ // surrogate value to the lowest.
141
+ if (i == 0 ) {
142
+ // Error, encountered low surrogate without matching high surrogate
143
+ } else {
144
+ unichar high = [next characterAtIndex: i - 1 ];
145
+ if (high == HIGH_SURROGATE_PAIR_END) { /* highest value for the high
146
+ part of the pair */
147
+ // Replace pair with 0xE000 (the value of plusOne)
148
+ removePreviousCharacter = YES ;
149
+ } else {
150
+ high += 1 ;
151
+ NSString *highStr = [NSString stringWithFormat: @" %C " , high];
152
+
153
+ [next replaceCharactersInRange: NSMakeRange (i - 1 , i)
154
+ withString: highStr];
155
+ plusOne = LOW_SURROGATE_PAIR_START; /* lowest value for the low
156
+ part of the pair */
157
+ }
158
+ }
159
+ break ;
160
+ }
161
+
162
+ NSString *sourcePlusOne =
163
+ replaceWithLowestSurrogatePair
164
+ ? [NSString stringWithFormat: @" %C%C " , HIGH_SURROGATE_PAIR_START,
165
+ LOW_SURROGATE_PAIR_START]
166
+ : [NSString stringWithFormat: @" %C " , plusOne];
167
+
168
+ NSInteger replaceLocation = i;
169
+ NSInteger replaceLength = 1 ;
170
+ if (removePreviousCharacter) {
171
+ --replaceLocation;
172
+ ++replaceLength;
173
+ }
174
+
175
+ [next replaceCharactersInRange: NSMakeRange (replaceLocation, replaceLength)
103
176
withString: sourcePlusOne];
104
- return [next substringWithRange: NSMakeRange (0 , i + 1 )];
177
+ NSInteger length = i + 1 ;
178
+ if (removePreviousCharacter) {
179
+ --length;
180
+ } else if (replaceWithLowestSurrogatePair) {
181
+ ++length;
182
+ }
183
+ return [next substringWithRange: NSMakeRange (0 , length)];
105
184
}
106
185
107
- // `key` is assumed to be non-empty.
108
- + ( NSString *) predecessor : ( NSString *_Nonnull) key {
186
+ + ( NSString *) from : ( NSString *) fn predecessor : ( NSString *_Nonnull) key {
187
+ [FValidation validateFrom: fn validKey: key];
109
188
NSInteger keyAsInt;
110
189
if ([FUtilities tryParseString: key asInt: &keyAsInt]) {
111
190
if (keyAsInt == [FUtilities int32min ]) {
@@ -129,13 +208,91 @@ + (NSString *)predecessor:(NSString *_Nonnull)key {
129
208
// Replace the last character with its immedate predecessor, and fill the
130
209
// suffix of the key with MAX_PUSH_CHAR. This is the lexicographically
131
210
// largest possible key smaller than `key`.
132
- unichar curr = [next characterAtIndex: next.length - 1 ];
133
- NSRange dstRange = NSMakeRange ([next length ] - 1 , 1 );
134
- NSRange srcRange =
135
- [PUSH_CHARS rangeOfString: [NSString stringWithFormat: @" %C " , curr]];
136
- srcRange.location -= 1 ;
137
- [next replaceCharactersInRange: dstRange
138
- withString: [PUSH_CHARS substringWithRange: srcRange]];
211
+ NSUInteger i = next.length - 1 ;
212
+ unichar character = [next characterAtIndex: i];
213
+ unichar minusOne = character - 1 ;
214
+ BOOL removePreviousCharacter = NO ;
215
+ BOOL replaceWithHighestSurrogatePair = NO ;
216
+ switch (minusOne) {
217
+ // NOTE: We already handled the case of min char (0x20)
218
+ case 0x23 : // '#'
219
+ case 0x24 : // '$'
220
+ minusOne = 0x22 ;
221
+ break ;
222
+
223
+ case 0x2E : // '.'
224
+ case 0x2F : // '/'
225
+ minusOne = 0x2D ;
226
+ break ;
227
+
228
+ case 0x5B : // '['
229
+ minusOne = 0x5A ;
230
+ break ;
231
+
232
+ case 0x5D : // ']'
233
+ minusOne = 0x5C ;
234
+ break ;
235
+
236
+ case 0x7F : // control character: del
237
+ minusOne = 0x7E ;
238
+ break ;
239
+
240
+ case LOW_SURROGATE_PAIR_END: // 0xDFFF
241
+ // Previously we had 0xE000 which is a single utf16 character,
242
+ // this needs to be replaced with the highest surrogate pair:
243
+ replaceWithHighestSurrogatePair = YES ;
244
+ break ;
245
+
246
+ case HIGH_SURROGATE_PAIR_END: // 0xDBFF
247
+ // If the previous character is the lowest high surrogate value
248
+ // then we decrement to the non-surrogate value 0xD7FF by replacing the
249
+ // surrogate pair by the single value 0xD7FF (HIGH_SURROGATE_PAIR_START
250
+ // - 1) Otherwise we decrement the high surrogate value and set the low
251
+ // surrogate value to the highest.
252
+ if (i == 0 ) {
253
+ // Error, found low surrogate without matching high surrogate
254
+ } else {
255
+ unichar high = [next characterAtIndex: i - 1 ];
256
+ if (high == HIGH_SURROGATE_PAIR_START) { /* lowest value for the
257
+ high part of the pair */
258
+ // Replace pair with single 0xD7FF value
259
+ removePreviousCharacter = YES ;
260
+ minusOne = HIGH_SURROGATE_PAIR_START - 1 ;
261
+ } else {
262
+ high -= 1 ;
263
+ NSString *highStr = [NSString stringWithFormat: @" %C " , high];
264
+
265
+ [next replaceCharactersInRange: NSMakeRange (i - 1 , i)
266
+ withString: highStr];
267
+ minusOne = LOW_SURROGATE_PAIR_END; /* highest value for the low
268
+ part of the pair */
269
+ }
270
+ }
271
+ break ;
272
+ }
273
+
274
+ NSString *sourceMinusOne =
275
+ replaceWithHighestSurrogatePair
276
+ ? [NSString stringWithFormat: @" %C%C " , HIGH_SURROGATE_PAIR_END,
277
+ LOW_SURROGATE_PAIR_END]
278
+ : [NSString stringWithFormat: @" %C " , minusOne];
279
+
280
+ NSInteger replaceLocation = i;
281
+ NSInteger replaceLength = 1 ;
282
+ if (removePreviousCharacter) {
283
+ --replaceLocation;
284
+ ++replaceLength;
285
+ }
286
+
287
+ [next replaceCharactersInRange: NSMakeRange (replaceLocation, replaceLength)
288
+ withString: sourceMinusOne];
289
+
290
+ NSInteger length = i + 1 ;
291
+ if (removePreviousCharacter) {
292
+ --length;
293
+ } else if (replaceWithHighestSurrogatePair) {
294
+ ++length;
295
+ }
139
296
return [next stringByPaddingToLength: MAX_KEY_LEN
140
297
withString: MAX_PUSH_CHAR
141
298
startingAtIndex: 0 ];
0 commit comments