Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | 8x 8x 8x 8x 8x 74874x 3468x 70x 28x 8x 3000x 3000x 3000x 3000x 3037x 3037x 3037x 3000x 3000x 3000x 8x 191573x 19440x 8x 983x 983x 8582x 983x 7599x 7599x 7599x 7599x 32568x 32568x 27657x 27657x 26263x 26263x 7599x 7599x 8x 6277x 6277x 20423x 3362x 8x 2379x 2379x 2379x 2379x 5798x 5798x 8x 6277x 6277x 6277x 6277x 6277x 26142x 26142x 26142x 15x 15x 15x 2x 1x 1x 2x 13x 13x 13x 13x 153x 153x 153x 153x 13x 13x 13x 13x 153x 13x 2379x 13x 13x 13x 153x 153x 153x 153x 153x 37437x 37437x 37437x 37437x 37437x 37437x 37437x 37437x 37437x 37437x 37437x 37437x 274x 274x 37437x 35566x 35566x 1836x 1836x 1836x 21x 21x 21x 14x 14x 14x 37437x 55x | import {sort} from '@jsonjoy.com/util/lib/sort/insertion2';
import {objKeyCmp} from '@jsonjoy.com/util/lib/objKeyCmp';
import {utf8Size} from '@jsonjoy.com/util/lib/strings/utf8';
import {CborEncoder} from './CborEncoder';
import {MAJOR_OVERLAY} from './constants';
const strHeaderLength = (strSize: number): 1 | 2 | 3 | 5 => {
if (strSize <= 23) return 1;
else if (strSize <= 0xff) return 2;
else if (strSize <= 0xffff) return 3;
else return 5;
};
/**
* Orders two strings by code point, which is the order their UTF-8 encodings
* sort in. Differs from `<` only around the surrogate range.
*/
const cmpCodePoints = (a: string, b: string): number => {
const len1 = a.length;
const len2 = b.length;
const len = len1 < len2 ? len1 : len2;
for (let i = 0; i < len; i++) {
let c1 = a.charCodeAt(i);
let c2 = b.charCodeAt(i);
if (c1 === c2) continue;
if (c1 >= 0xd800) c1 += c1 < 0xe000 ? 0x2000 : -0x800;
if (c2 >= 0xd800) c2 += c2 < 0xe000 ? 0x2000 : -0x800;
return c1 - c2;
}
return len1 - len2;
};
const isAscii = (str: string): boolean => {
for (let i = str.length - 1; i >= 0; i--) if (str.charCodeAt(i) > 127) return false;
return true;
};
/**
* Insertion sort by UTF-8 byte size, then by code point. Carries the sizes
* alongside the keys so each key is measured once, not once per comparison.
*/
const sortKeysUtf8 = (keys: string[]): void => {
const length = keys.length;
const sizes: number[] = [];
for (let i = 0; i < length; i++) sizes.push(utf8Size(keys[i]));
for (let i = 1; i < length; i++) {
const key = keys[i];
const size = sizes[i];
let j = i;
for (; j > 0; j--) {
const size2 = sizes[j - 1];
if (size2 < size) break;
const key2 = keys[j - 1];
if (size2 === size && cmpCodePoints(key2, key) <= 0) break;
keys[j] = key2;
sizes[j] = size2;
}
keys[j] = key;
sizes[j] = size;
}
};
/**
* Sorts object keys the way RFC 8949 ยง4.2.1 requires map keys to be ordered:
* bytewise over the encoded keys. For text strings that is UTF-8 byte length
* first (it leads the header byte), then the UTF-8 bytes themselves. For ASCII
* keys that is exactly what `objKeyCmp` already does, and ASCII is the common
* case, so the slower UTF-8 aware sort is used only when some key is not.
*/
const sortKeys = (keys: string[]): void => {
const length = keys.length;
if (length < 2) return;
for (let i = 0; i < length; i++) if (!isAscii(keys[i])) return sortKeysUtf8(keys);
sort(keys, objKeyCmp);
};
/** Compares two byte ranges of the same buffer bytewise, then by length. */
const cmpRange = (buf: Uint8Array, a: number, aEnd: number, b: number, bEnd: number): number => {
const len1 = aEnd - a;
const len2 = bEnd - b;
const len = len1 < len2 ? len1 : len2;
for (let i = 0; i < len; i++) {
const diff = buf[a + i] - buf[b + i];
if (diff !== 0) return diff;
}
return len1 - len2;
};
export class CborEncoderStable extends CborEncoder {
public writeObj(obj: Record<string, unknown>): void {
const keys = Object.keys(obj);
sortKeys(keys);
const length = keys.length;
this.writeObjHdr(length);
for (let i = 0; i < length; i++) {
const key = keys[i];
this.writeStr(key);
this.writeAny(obj[key]);
}
}
/**
* Map keys can be of any type, so they are sorted by their encodings: the
* entries are written out in insertion order first, then reordered in-place.
*/
public writeMap(map: Map<unknown, unknown>): void {
const size = map.size;
this.writeMapHdr(size);
if (size < 2) {
map.forEach((value, key) => {
this.writeAny(key);
this.writeAny(value);
});
return;
}
const writer = this.writer;
// Offsets are relative to `x0`, absolute ones shift when the buffer grows.
const start = writer.x - writer.x0;
const bounds: number[] = [];
map.forEach((value, key) => {
bounds.push(writer.x - writer.x0);
this.writeAny(key);
bounds.push(writer.x - writer.x0);
this.writeAny(value);
});
const end = writer.x - writer.x0;
const x0 = writer.x0;
const uint8 = writer.uint8;
const order: number[] = [];
for (let i = 0; i < size; i++) order.push(i);
sort(order, (i, j) =>
cmpRange(uint8, x0 + bounds[i << 1], x0 + bounds[(i << 1) + 1], x0 + bounds[j << 1], x0 + bounds[(j << 1) + 1]),
);
const entries = uint8.slice(x0 + start, x0 + end);
let x = x0 + start;
for (let i = 0; i < size; i++) {
const entry = order[i];
const from = bounds[entry << 1];
const to = entry === size - 1 ? end : bounds[(entry + 1) << 1];
uint8.set(entries.subarray(from - start, to - start), x);
x += to - from;
}
}
/** @todo This implementation might be even faster than the default one, verify that. */
public writeStr(str: string): void {
const writer = this.writer;
const length = str.length;
const maxSize = length * 4;
writer.ensureCapacity(5 + maxSize);
const headerLengthGuess = strHeaderLength(length);
const x0 = writer.x;
const x1 = x0 + headerLengthGuess;
writer.x = x1;
const bytesWritten = writer.utf8(str);
const uint8 = writer.uint8;
const headerLength = strHeaderLength(bytesWritten);
if (headerLength !== headerLengthGuess) {
const shift = headerLength - headerLengthGuess;
uint8.copyWithin(x1 + shift, x1, x1 + bytesWritten);
}
switch (headerLength) {
case 1:
uint8[x0] = MAJOR_OVERLAY.STR + bytesWritten;
break;
case 2:
uint8[x0] = 0x78;
uint8[x0 + 1] = bytesWritten;
break;
case 3: {
uint8[x0] = 0x79;
writer.view.setUint16(x0 + 1, bytesWritten);
break;
}
case 5: {
uint8[x0] = 0x7a;
writer.view.setUint32(x0 + 1, bytesWritten);
break;
}
}
writer.x = x0 + headerLength + bytesWritten;
}
public writeUndef(): void {
this.writeNull();
}
}
|