source: trip-planner-front/node_modules/hdr-histogram-js/src/JsHistogram.encoding.ts@ 571e0df

Last change on this file since 571e0df was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 8.2 KB
Line 
1/*
2 * This is a TypeScript port of the original Java version, which was written by
3 * Gil Tene as described in
4 * https://github.com/HdrHistogram/HdrHistogram
5 * and released to the public domain, as explained at
6 * http://creativecommons.org/publicdomain/zero/1.0/
7 */
8// @ts-ignore
9import * as base64 from "base64-js";
10// @ts-ignore
11import * as pako from "pako";
12import { JsHistogram } from "./JsHistogram";
13import ByteBuffer from "./ByteBuffer";
14import { BitBucketSize } from "./Histogram";
15import { constructorFromBucketSize } from "./JsHistogramFactory";
16import ZigZagEncoding from "./ZigZagEncoding";
17
18const { max } = Math;
19
20const V2EncodingCookieBase = 0x1c849303;
21const V2CompressedEncodingCookieBase = 0x1c849304;
22const V2maxWordSizeInBytes = 9; // LEB128-64b9B + ZigZag require up to 9 bytes per word
23const encodingCookie = V2EncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding
24const compressedEncodingCookie = V2CompressedEncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding
25
26function fillBufferFromCountsArray(self: JsHistogram, buffer: ByteBuffer) {
27 const countsLimit = self.countsArrayIndex(self.maxValue) + 1;
28 let srcIndex = 0;
29
30 while (srcIndex < countsLimit) {
31 // V2 encoding format uses a ZigZag LEB128-64b9B encoded long. Positive values are counts,
32 // while negative values indicate a repeat zero counts.
33 const count = self.getCountAtIndex(srcIndex++);
34 if (count < 0) {
35 throw new Error(
36 "Cannot encode histogram containing negative counts (" +
37 count +
38 ") at index " +
39 srcIndex +
40 ", corresponding the value range [" +
41 self.lowestEquivalentValue(self.valueFromIndex(srcIndex)) +
42 "," +
43 self.nextNonEquivalentValue(self.valueFromIndex(srcIndex)) +
44 ")"
45 );
46 }
47 // Count trailing 0s (which follow this count):
48 let zerosCount = 0;
49 if (count == 0) {
50 zerosCount = 1;
51 while (srcIndex < countsLimit && self.getCountAtIndex(srcIndex) == 0) {
52 zerosCount++;
53 srcIndex++;
54 }
55 }
56 if (zerosCount > 1) {
57 ZigZagEncoding.encode(buffer, -zerosCount);
58 } else {
59 ZigZagEncoding.encode(buffer, count);
60 }
61 }
62}
63
64/**
65 * Encode this histogram into a ByteBuffer
66 * @param self this histogram
67 * @param buffer The buffer to encode into
68 * @return The number of bytes written to the buffer
69 */
70function encodeIntoByteBuffer(self: JsHistogram, buffer: ByteBuffer) {
71 const initialPosition = buffer.position;
72 buffer.putInt32(encodingCookie);
73 buffer.putInt32(0); // Placeholder for payload length in bytes.
74 buffer.putInt32(1);
75 buffer.putInt32(self.numberOfSignificantValueDigits);
76 buffer.putInt64(self.lowestDiscernibleValue);
77 buffer.putInt64(self.highestTrackableValue);
78 buffer.putInt64(1);
79
80 const payloadStartPosition = buffer.position;
81 fillBufferFromCountsArray(self, buffer);
82
83 const backupIndex = buffer.position;
84 buffer.position = initialPosition + 4;
85 buffer.putInt32(backupIndex - payloadStartPosition); // Record the payload length
86
87 buffer.position = backupIndex;
88
89 return backupIndex - initialPosition;
90}
91
92function fillCountsArrayFromSourceBuffer(
93 self: JsHistogram,
94 sourceBuffer: ByteBuffer,
95 lengthInBytes: number,
96 wordSizeInBytes: number
97) {
98 if (
99 wordSizeInBytes != 2 &&
100 wordSizeInBytes != 4 &&
101 wordSizeInBytes != 8 &&
102 wordSizeInBytes != V2maxWordSizeInBytes
103 ) {
104 throw new Error(
105 "word size must be 2, 4, 8, or V2maxWordSizeInBytes (" +
106 V2maxWordSizeInBytes +
107 ") bytes"
108 );
109 }
110 let dstIndex = 0;
111 const endPosition = sourceBuffer.position + lengthInBytes;
112 while (sourceBuffer.position < endPosition) {
113 let zerosCount = 0;
114 let count = ZigZagEncoding.decode(sourceBuffer);
115 if (count < 0) {
116 zerosCount = -count;
117 dstIndex += zerosCount; // No need to set zeros in array. Just skip them.
118 } else {
119 self.setCountAtIndex(dstIndex++, count);
120 }
121 }
122 return dstIndex; // this is the destination length
123}
124
125function getCookieBase(cookie: number): number {
126 return cookie & ~0xf0;
127}
128
129function getWordSizeInBytesFromCookie(cookie: number): number {
130 if (
131 getCookieBase(cookie) == V2EncodingCookieBase ||
132 getCookieBase(cookie) == V2CompressedEncodingCookieBase
133 ) {
134 return V2maxWordSizeInBytes;
135 }
136 const sizeByte = (cookie & 0xf0) >> 4;
137 return sizeByte & 0xe;
138}
139
140function findDeflateFunction() {
141 try {
142 return eval('require("zlib").deflateSync');
143 } catch (error) {
144 return !!pako ? pako.deflate : () => { throw new Error('pako library is mandatory for encoding/deconding on the browser side') };
145 }
146}
147function findInflateFunction() {
148 try {
149 return eval('require("zlib").inflateSync');
150 } catch (error) {
151 return !!pako ? pako.inflate : () => { throw new Error('pako library is mandatory for encoding/deconding on the browser side') };
152 }
153}
154
155export const deflate = findDeflateFunction();
156export const inflate = findInflateFunction();
157
158export function decompress(data: Uint8Array): Uint8Array {
159 const buffer = new ByteBuffer(data);
160 const initialTargetPosition = buffer.position;
161
162 const cookie = buffer.getInt32();
163
164 if ((cookie & ~0xf0) !== V2CompressedEncodingCookieBase) {
165 throw new Error("Encoding not supported, only V2 is supported");
166 }
167
168 const lengthOfCompressedContents = buffer.getInt32();
169
170 const uncompressedBuffer: Uint8Array = inflate(
171 buffer.data.slice(
172 initialTargetPosition + 8,
173 initialTargetPosition + 8 + lengthOfCompressedContents
174 )
175 );
176 return uncompressedBuffer;
177}
178
179export function doDecode(
180 data: Uint8Array,
181 bitBucketSize: BitBucketSize = 32,
182 minBarForHighestTrackableValue: number = 0
183) {
184 const buffer = new ByteBuffer(data);
185 const cookie = buffer.getInt32();
186
187 let payloadLengthInBytes: number;
188 let numberOfSignificantValueDigits: number;
189 let lowestTrackableUnitValue: number;
190 let highestTrackableValue: number;
191
192 if (getCookieBase(cookie) === V2EncodingCookieBase) {
193 if (getWordSizeInBytesFromCookie(cookie) != V2maxWordSizeInBytes) {
194 throw new Error(
195 "The buffer does not contain a Histogram (no valid cookie found)"
196 );
197 }
198 payloadLengthInBytes = buffer.getInt32();
199 buffer.getInt32(); // normalizingIndexOffset not used
200 numberOfSignificantValueDigits = buffer.getInt32();
201 lowestTrackableUnitValue = buffer.getInt64();
202 highestTrackableValue = buffer.getInt64();
203 buffer.getInt64(); // integerToDoubleValueConversionRatio not used
204 } else {
205 throw new Error(
206 "The buffer does not contain a Histogram (no valid V2 encoding cookie found)"
207 );
208 }
209
210 highestTrackableValue = max(
211 highestTrackableValue,
212 minBarForHighestTrackableValue
213 );
214
215 const histogramConstr = constructorFromBucketSize(bitBucketSize);
216
217 const histogram = new histogramConstr(
218 lowestTrackableUnitValue,
219 highestTrackableValue,
220 numberOfSignificantValueDigits
221 );
222
223 const filledLength = fillCountsArrayFromSourceBuffer(
224 histogram,
225 buffer,
226 payloadLengthInBytes,
227 V2maxWordSizeInBytes
228 );
229
230 histogram.establishInternalTackingValues(filledLength);
231
232 return histogram;
233}
234
235function doEncodeIntoCompressedBase64(compressionLevel?: number): string {
236 const compressionOptions = compressionLevel
237 ? { level: compressionLevel }
238 : {};
239 const self: JsHistogram = this as any;
240
241 const targetBuffer = ByteBuffer.allocate();
242 targetBuffer.putInt32(compressedEncodingCookie);
243
244 const intermediateUncompressedByteBuffer = ByteBuffer.allocate();
245 const uncompressedLength = encodeIntoByteBuffer(
246 self,
247 intermediateUncompressedByteBuffer
248 );
249 const data = intermediateUncompressedByteBuffer.data.slice(
250 0,
251 uncompressedLength
252 );
253 const compressedData: Uint8Array = deflate(data, compressionOptions);
254 targetBuffer.putInt32(compressedData.byteLength);
255 targetBuffer.putArray(compressedData);
256
257 return base64.fromByteArray(targetBuffer.data);
258}
259
260declare module "./JsHistogram" {
261 namespace JsHistogram {
262 export let decode: typeof doDecode;
263 }
264}
265
266JsHistogram.decode = doDecode;
267
268declare module "./JsHistogram" {
269 interface JsHistogram {
270 encodeIntoCompressedBase64: typeof doEncodeIntoCompressedBase64;
271 }
272}
273
274JsHistogram.prototype.encodeIntoCompressedBase64 = doEncodeIntoCompressedBase64;
Note: See TracBrowser for help on using the repository browser.