a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(cbor): improve cbor encode/decode

Mary 0cfed5ad b6222920

+65 -28
+5
.changeset/new-bottles-march.md
··· 1 + --- 2 + '@atcute/cbor': patch 3 + --- 4 + 5 + improve cbor encode/decode
+6 -8
packages/utilities/cbor/lib/decode.ts
··· 113 113 return new CidLinkWrapper(cid.bytes); 114 114 }; 115 115 116 - const compareKeys = (a: string, b: string): number => { 117 - return a.length - b.length || (a < b ? -1 : a > b ? 1 : 0); 118 - }; 119 - 120 116 const decodeStringKey = (state: State): string => { 121 117 const prelude = readUint8(state); 122 118 ··· 126 122 } 127 123 128 124 const info = prelude & 0x1f; 129 - const length = readArgument(state, info); 125 + const length = info < 24 ? info : readArgument(state, info); 130 126 return readString(state, length); 131 127 }; 132 128 ··· 173 169 174 170 const type = prelude >> 5; 175 171 const info = prelude & 0x1f; 176 - const arg = type === 7 ? 0 : readArgument(state, info); 172 + const arg = type === 7 ? 0 : info < 24 ? info : readArgument(state, info); 177 173 178 174 switch (type) { 179 175 case 0: { ··· 292 288 if (!stack.t) { 293 289 // Read the key of the next map item 294 290 const prevKey = stack.k; 295 - stack.k = decodeStringKey(state); 291 + const key = decodeStringKey(state); 292 + stack.k = key; 296 293 297 - if (compareKeys(stack.k, prevKey) <= 0) { 294 + const cmp = key.length - prevKey.length || (key > prevKey ? 1 : key < prevKey ? -1 : 0); 295 + if (cmp <= 0) { 298 296 throw new TypeError(`map keys are not in canonical order or contain duplicates`); 299 297 } 300 298 }
+54 -20
packages/utilities/cbor/lib/encode.ts
··· 154 154 const writeString = (state: State, val: string): void => { 155 155 const strLength = val.length; 156 156 157 + if (strLength === 0) { 158 + resizeIfNeeded(state, 1); 159 + writeUint8(state, 0x60); 160 + return; 161 + } 162 + 157 163 // JS strings are UTF-16 (ECMA spec) 158 164 // Therefore, worst case length of UTF-8 is length * 3. (plus 9 bytes of CBOR header) 159 165 // Greatly overshoots in practice, but doesn't matter. (alloc is O(1)+ anyway) ··· 162 168 // Optimistic fast encode 163 169 ascii: { 164 170 const ptr = state.p + getTypeInfoLength(strLength); 165 - for (let i = 0; i < strLength; i++) { 166 - let code = val.charCodeAt(i); 167 - if (code > 0x7f) break ascii; 171 + const first = val.charCodeAt(0); 172 + if (first > 0x7f) { 173 + break ascii; 174 + } 175 + 176 + state.b[ptr] = first; 177 + let i = 1; 178 + 179 + // batch-process four characters per iteration to lower charCodeAt/branch overhead. 180 + for (; i + 3 < strLength; i += 4) { 181 + const a = val.charCodeAt(i); 182 + const b = val.charCodeAt(i + 1); 183 + const c = val.charCodeAt(i + 2); 184 + const d = val.charCodeAt(i + 3); 185 + 186 + if ((a | b | c | d) & 0x80) { 187 + break ascii; 188 + } 189 + 190 + state.b[ptr + i] = a; 191 + state.b[ptr + i + 1] = b; 192 + state.b[ptr + i + 2] = c; 193 + state.b[ptr + i + 3] = d; 194 + } 195 + 196 + for (; i < strLength; i++) { 197 + const code = val.charCodeAt(i); 198 + if (code > 0x7f) { 199 + break ascii; 200 + } 201 + 168 202 state.b[ptr + i] = code; 169 203 } 170 204 ··· 324 358 /** @internal */ 325 359 export const getOrderedObjectKeys = (obj: Record<string, unknown>): string[] => { 326 360 const keys = Object.keys(obj); 327 - for (let i = 1, len = keys.length, j = 0; i < len; j = i++) { 361 + let len = 0; 362 + 363 + for (let i = 0; i < keys.length; i++) { 328 364 const valA = keys[i]; 329 - 330 - // Tuck in undefined value filtering here to avoid extra iterations. 331 365 if (obj[valA] === undefined) { 332 - // A lot of things are tucked in here xd 333 - // - Pull the currently last item in the keys array at the current place 334 - // - Update saved value of array length 335 - // - Decrease i by 1 336 - keys[i--] = keys[--len]; 337 - keys.length = len; 338 - } else { 339 - for (; j >= 0; j--) { 340 - const valB = keys[j]; 366 + continue; 367 + } 341 368 342 - // Note: Don't need to check for equality, keys are always distinct. 343 - const cmp = valA.length - valB.length || +(valA > valB); 344 - if (cmp > 0) break; 369 + let j = len - 1; 370 + for (; j >= 0; j--) { 371 + const valB = keys[j]; 345 372 346 - keys[j + 1] = valB; 373 + // Note: Don't need to check for equality, keys are always distinct. 374 + const cmp = valA.length - valB.length || +(valA > valB); 375 + if (cmp > 0) { 376 + break; 347 377 } 348 378 349 - keys[j + 1] = valA; 379 + keys[j + 1] = valB; 350 380 } 381 + 382 + keys[j + 1] = valA; 383 + len++; 351 384 } 352 385 386 + keys.length = len; 353 387 return keys; 354 388 };