FNextPushId.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FirebaseDatabase/Sources/Utilities/FNextPushId.h"
  17. #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  18. #import "FirebaseDatabase/Sources/Utilities/FValidation.h"
  19. static NSString *const PUSH_CHARS =
  20. @"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
  21. static NSString *const MIN_PUSH_CHAR = @" ";
  22. static NSString *const MAX_PUSH_CHAR = @"\uFFFF";
  23. static NSInteger const MAX_KEY_LEN = 786;
  24. static unichar const LOW_SURROGATE_PAIR_START = 0xDC00;
  25. static unichar const LOW_SURROGATE_PAIR_END = 0xDFFF;
  26. static unichar const HIGH_SURROGATE_PAIR_START = 0xD800;
  27. static unichar const HIGH_SURROGATE_PAIR_END = 0xDBFF;
  28. @implementation FNextPushId
  29. + (NSString *)get:(NSTimeInterval)currentTime {
  30. static long long lastPushTime = 0;
  31. static int lastRandChars[12];
  32. long long now = (long long)(currentTime * 1000);
  33. BOOL duplicateTime = now == lastPushTime;
  34. lastPushTime = now;
  35. unichar timeStampChars[8];
  36. for (int i = 7; i >= 0; i--) {
  37. timeStampChars[i] = [PUSH_CHARS characterAtIndex:(now % 64)];
  38. now = (long long)floor(now / 64);
  39. }
  40. NSMutableString *id = [[NSMutableString alloc] init];
  41. [id appendString:[NSString stringWithCharacters:timeStampChars length:8]];
  42. if (!duplicateTime) {
  43. for (int i = 0; i < 12; i++) {
  44. lastRandChars[i] = (int)floor(arc4random() % 64);
  45. }
  46. } else {
  47. int i = 0;
  48. for (i = 11; i >= 0 && lastRandChars[i] == 63; i--) {
  49. lastRandChars[i] = 0;
  50. }
  51. lastRandChars[i]++;
  52. }
  53. for (int i = 0; i < 12; i++) {
  54. [id appendFormat:@"%C", [PUSH_CHARS characterAtIndex:lastRandChars[i]]];
  55. }
  56. return [NSString stringWithString:id];
  57. }
  58. + (NSString *)from:(NSString *)fn successor:(NSString *_Nonnull)key {
  59. [FValidation validateFrom:fn validKey:key];
  60. NSInteger keyAsInt;
  61. if ([FUtilities tryParseString:key asInt:&keyAsInt]) {
  62. if (keyAsInt == [FUtilities int32max]) {
  63. return MIN_PUSH_CHAR;
  64. }
  65. return [NSString stringWithFormat:@"%ld", (long)keyAsInt + 1];
  66. }
  67. NSMutableString *next = [NSMutableString stringWithString:key];
  68. if ([next length] < MAX_KEY_LEN) {
  69. [next insertString:MIN_PUSH_CHAR atIndex:[key length]];
  70. return next;
  71. }
  72. long i = [next length] - 1;
  73. while (i >= 0) {
  74. if ([next characterAtIndex:i] != [MAX_PUSH_CHAR characterAtIndex:0]) {
  75. break;
  76. }
  77. --i;
  78. }
  79. // `nextAfter` was called on the largest possible key, so return the
  80. // maxName, which sorts larger than all keys.
  81. if (i == -1) {
  82. return [FUtilities maxName];
  83. }
  84. unichar character = [next characterAtIndex:i];
  85. unichar plusOne = character + 1;
  86. BOOL removePreviousCharacter = NO;
  87. BOOL replaceWithLowestSurrogatePair = NO;
  88. switch (plusOne) {
  89. case 0x23: // '#'
  90. case 0x24: // '$'
  91. plusOne = 0x25;
  92. break;
  93. case 0x2E: // '.'
  94. case 0x2F: // '/'
  95. plusOne = 0x30;
  96. break;
  97. case 0x5B: // '['
  98. plusOne = 0x5C;
  99. break;
  100. case 0x5D: // ']'
  101. plusOne = 0x5E;
  102. break;
  103. case 0x7F: // control character: del
  104. plusOne = 0x80;
  105. break;
  106. case HIGH_SURROGATE_PAIR_START: // 0xD800
  107. // We added one to 0xD7FF and entered surrogate pair zone
  108. // Replace with the lowest surrogate pair here
  109. replaceWithLowestSurrogatePair = YES;
  110. case LOW_SURROGATE_PAIR_END + 1: // 0xE000
  111. // If the previous character is the highest surrogate value
  112. // then we increment to the value 0xE000 by replacing the surrogate
  113. // pair by the single value 0xE000 (the value of plusOne)
  114. // Otherwise we increment the high surrogate value and set the low
  115. // surrogate value to the lowest.
  116. if (i == 0) {
  117. // Error, encountered low surrogate without matching high surrogate
  118. } else {
  119. unichar high = [next characterAtIndex:i - 1];
  120. if (high == HIGH_SURROGATE_PAIR_END) { /* highest value for the high
  121. part of the pair */
  122. // Replace pair with 0xE000 (the value of plusOne)
  123. removePreviousCharacter = YES;
  124. } else {
  125. high += 1;
  126. NSString *highStr = [NSString stringWithFormat:@"%C", high];
  127. [next replaceCharactersInRange:NSMakeRange(i - 1, i)
  128. withString:highStr];
  129. plusOne = LOW_SURROGATE_PAIR_START; /* lowest value for the low
  130. part of the pair */
  131. }
  132. }
  133. break;
  134. }
  135. NSString *sourcePlusOne =
  136. replaceWithLowestSurrogatePair
  137. ? [NSString stringWithFormat:@"%C%C", HIGH_SURROGATE_PAIR_START,
  138. LOW_SURROGATE_PAIR_START]
  139. : [NSString stringWithFormat:@"%C", plusOne];
  140. NSInteger replaceLocation = i;
  141. NSInteger replaceLength = 1;
  142. if (removePreviousCharacter) {
  143. --replaceLocation;
  144. ++replaceLength;
  145. }
  146. [next replaceCharactersInRange:NSMakeRange(replaceLocation, replaceLength)
  147. withString:sourcePlusOne];
  148. NSInteger length = i + 1;
  149. if (removePreviousCharacter) {
  150. --length;
  151. } else if (replaceWithLowestSurrogatePair) {
  152. ++length;
  153. }
  154. return [next substringWithRange:NSMakeRange(0, length)];
  155. }
  156. + (NSString *)from:(NSString *)fn predecessor:(NSString *_Nonnull)key {
  157. [FValidation validateFrom:fn validKey:key];
  158. NSInteger keyAsInt;
  159. if ([FUtilities tryParseString:key asInt:&keyAsInt]) {
  160. if (keyAsInt == [FUtilities int32min]) {
  161. return [FUtilities minName];
  162. }
  163. return [NSString stringWithFormat:@"%ld", (long)keyAsInt - 1];
  164. }
  165. NSMutableString *next = [NSMutableString stringWithString:key];
  166. if ([next characterAtIndex:(next.length - 1)] ==
  167. [MIN_PUSH_CHAR characterAtIndex:0]) {
  168. if ([next length] == 1) {
  169. return
  170. [NSString stringWithFormat:@"%ld", (long)[FUtilities int32max]];
  171. }
  172. // If the last character is the smallest possible character, then the
  173. // next smallest string is the prefix of `key` without it.
  174. [next replaceCharactersInRange:NSMakeRange([next length] - 1, 1)
  175. withString:@""];
  176. return next;
  177. }
  178. // Replace the last character with its immediate predecessor, and fill the
  179. // suffix of the key with MAX_PUSH_CHAR. This is the lexicographically
  180. // largest possible key smaller than `key`.
  181. NSUInteger i = next.length - 1;
  182. unichar character = [next characterAtIndex:i];
  183. unichar minusOne = character - 1;
  184. BOOL removePreviousCharacter = NO;
  185. BOOL replaceWithHighestSurrogatePair = NO;
  186. switch (minusOne) {
  187. // NOTE: We already handled the case of min char (0x20)
  188. case 0x23: // '#'
  189. case 0x24: // '$'
  190. minusOne = 0x22;
  191. break;
  192. case 0x2E: // '.'
  193. case 0x2F: // '/'
  194. minusOne = 0x2D;
  195. break;
  196. case 0x5B: // '['
  197. minusOne = 0x5A;
  198. break;
  199. case 0x5D: // ']'
  200. minusOne = 0x5C;
  201. break;
  202. case 0x7F: // control character: del
  203. minusOne = 0x7E;
  204. break;
  205. case LOW_SURROGATE_PAIR_END: // 0xDFFF
  206. // Previously we had 0xE000 which is a single utf16 character,
  207. // this needs to be replaced with the highest surrogate pair:
  208. replaceWithHighestSurrogatePair = YES;
  209. break;
  210. case HIGH_SURROGATE_PAIR_END: // 0xDBFF
  211. // If the previous character is the lowest high surrogate value
  212. // then we decrement to the non-surrogate value 0xD7FF by replacing the
  213. // surrogate pair by the single value 0xD7FF (HIGH_SURROGATE_PAIR_START
  214. // - 1) Otherwise we decrement the high surrogate value and set the low
  215. // surrogate value to the highest.
  216. if (i == 0) {
  217. // Error, found low surrogate without matching high surrogate
  218. } else {
  219. unichar high = [next characterAtIndex:i - 1];
  220. if (high == HIGH_SURROGATE_PAIR_START) { /* lowest value for the
  221. high part of the pair */
  222. // Replace pair with single 0xD7FF value
  223. removePreviousCharacter = YES;
  224. minusOne = HIGH_SURROGATE_PAIR_START - 1;
  225. } else {
  226. high -= 1;
  227. NSString *highStr = [NSString stringWithFormat:@"%C", high];
  228. [next replaceCharactersInRange:NSMakeRange(i - 1, i)
  229. withString:highStr];
  230. minusOne = LOW_SURROGATE_PAIR_END; /* highest value for the low
  231. part of the pair */
  232. }
  233. }
  234. break;
  235. }
  236. NSString *sourceMinusOne =
  237. replaceWithHighestSurrogatePair
  238. ? [NSString stringWithFormat:@"%C%C", HIGH_SURROGATE_PAIR_END,
  239. LOW_SURROGATE_PAIR_END]
  240. : [NSString stringWithFormat:@"%C", minusOne];
  241. NSInteger replaceLocation = i;
  242. NSInteger replaceLength = 1;
  243. if (removePreviousCharacter) {
  244. --replaceLocation;
  245. ++replaceLength;
  246. }
  247. [next replaceCharactersInRange:NSMakeRange(replaceLocation, replaceLength)
  248. withString:sourceMinusOne];
  249. NSInteger length = i + 1;
  250. if (removePreviousCharacter) {
  251. --length;
  252. } else if (replaceWithHighestSurrogatePair) {
  253. ++length;
  254. }
  255. return [next stringByPaddingToLength:MAX_KEY_LEN
  256. withString:MAX_PUSH_CHAR
  257. startingAtIndex:0];
  258. };
  259. @end