Skip to content

Commit

Permalink
implement brotli classes
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Aug 30, 2024
1 parent f067058 commit 44a863b
Show file tree
Hide file tree
Showing 10 changed files with 745 additions and 226 deletions.
15 changes: 15 additions & 0 deletions src/node/internal/internal_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,21 @@ export class ERR_BUFFER_TOO_LARGE extends NodeRangeError {
}
}

export class ERR_BROTLI_INVALID_PARAM extends NodeRangeError {
constructor(value: unknown) {
super(
'ERR_BROTLI_INVALID_PARAM',
`${value} is not a valid Brotli parameter`
);
}
}

export class ERR_ZLIB_INITIALIZATION_FAILED extends NodeError {
constructor() {
super('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');
}
}

export function aggregateTwoErrors(innerError: any, outerError: any) {
if (innerError && outerError && innerError !== outerError) {
if (Array.isArray(outerError.errors)) {
Expand Down
76 changes: 26 additions & 50 deletions src/node/internal/internal_zlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,23 @@ import {
default as zlibUtil,
type ZlibOptions,
type CompressCallback,
type BrotliOptions,
} from 'node-internal:zlib';
import { Buffer } from 'node-internal:internal_buffer';
import { validateUint32 } from 'node-internal:validators';
import { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';
import { Zlib } from 'node-internal:internal_zlib_base';
import { Zlib, Brotli } from 'node-internal:internal_zlib_base';

const {
CONST_Z_OK,
CONST_Z_STREAM_END,
CONST_Z_NEED_DICT,
CONST_Z_ERRNO,
CONST_Z_STREAM_ERROR,
CONST_Z_DATA_ERROR,
CONST_Z_MEM_ERROR,
CONST_Z_BUF_ERROR,
CONST_Z_VERSION_ERROR,
CONST_DEFLATE,
CONST_DEFLATERAW,
CONST_INFLATE,
CONST_INFLATERAW,
CONST_GUNZIP,
CONST_GZIP,
CONST_UNZIP,
CONST_BROTLI_DECODE,
CONST_BROTLI_ENCODE,
} = zlibUtil;

export function crc32(
Expand Down Expand Up @@ -277,46 +271,6 @@ export function gzip(
zlibUtil.zlib(data, options, zlibUtil.CONST_GZIP, wrapCallback(callback));
}

const constPrefix = 'CONST_';
export const constants: Record<string, number> = {};

Object.defineProperties(
constants,
Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Object.entries(Object.getPrototypeOf(zlibUtil))
.filter(([k]) => k.startsWith(constPrefix))
.map(([k, v]) => [
k.slice(constPrefix.length),
{
value: v,
writable: false,
configurable: false,
enumerable: true,
},
])
)
);

// Translation table for return codes.
const rawCodes: Record<string, number | string> = {
Z_OK: CONST_Z_OK,
Z_STREAM_END: CONST_Z_STREAM_END,
Z_NEED_DICT: CONST_Z_NEED_DICT,
Z_ERRNO: CONST_Z_ERRNO,
Z_STREAM_ERROR: CONST_Z_STREAM_ERROR,
Z_DATA_ERROR: CONST_Z_DATA_ERROR,
Z_MEM_ERROR: CONST_Z_MEM_ERROR,
Z_BUF_ERROR: CONST_Z_BUF_ERROR,
Z_VERSION_ERROR: CONST_Z_VERSION_ERROR,
};

for (const key of Object.keys(rawCodes)) {
rawCodes[rawCodes[key] as number] = key;
}

export const codes = Object.freeze(rawCodes);

export class Gzip extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_GZIP);
Expand Down Expand Up @@ -362,6 +316,18 @@ export class Unzip extends Zlib {
}
}

export class BrotliCompress extends Brotli {
public constructor(options: BrotliOptions) {
super(options, CONST_BROTLI_ENCODE);
}
}

export class BrotliDecompress extends Brotli {
public constructor(options: BrotliOptions) {
super(options, CONST_BROTLI_DECODE);
}
}

export function createGzip(options: ZlibOptions): Gzip {
return new Gzip(options);
}
Expand Down Expand Up @@ -389,3 +355,13 @@ export function createInflateRaw(options: ZlibOptions): InflateRaw {
export function createUnzip(options: ZlibOptions): Unzip {
return new Unzip(options);
}

export function createBrotliCompress(options: BrotliOptions): BrotliCompress {
return new BrotliCompress(options);
}

export function createBrotliDecompress(
options: BrotliOptions
): BrotliDecompress {
return new BrotliDecompress(options);
}
104 changes: 92 additions & 12 deletions src/node/internal/internal_zlib_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
// https://opensource.org/licenses/Apache-2.0
// Copyright Joyent and Node contributors. All rights reserved. MIT license.

import { default as zlibUtil, type ZlibOptions } from 'node-internal:zlib';
import {
default as zlibUtil,
type ZlibOptions,
type BrotliOptions,
} from 'node-internal:zlib';
import { Buffer, kMaxLength } from 'node-internal:internal_buffer';
import {
checkRangesOrGetDefault,
Expand All @@ -13,6 +17,8 @@ import {
ERR_OUT_OF_RANGE,
ERR_BUFFER_TOO_LARGE,
ERR_INVALID_ARG_TYPE,
ERR_BROTLI_INVALID_PARAM,
ERR_ZLIB_INITIALIZATION_FAILED,
NodeError,
} from 'node-internal:internal_errors';
import { Transform, type DuplexOptions } from 'node-internal:streams_transform';
Expand All @@ -21,6 +27,7 @@ import {
isArrayBufferView,
isAnyArrayBuffer,
} from 'node-internal:internal_types';
import { constants } from 'node-internal:internal_zlib_constants';

// Explicitly import `ok()` to avoid typescript error requiring every name in the call target to
// be annotated with an explicit type annotation.
Expand Down Expand Up @@ -53,8 +60,15 @@ const {
CONST_BROTLI_DECODE,
CONST_BROTLI_OPERATION_PROCESS,
CONST_BROTLI_OPERATION_EMIT_METADATA,
CONST_BROTLI_OPERATION_FINISH,
CONST_BROTLI_OPERATION_FLUSH,
} = zlibUtil;

// This type contains all possible handler types.
type ZlibHandleType =
| zlibUtil.ZlibStream
| zlibUtil.BrotliEncoder
| zlibUtil.BrotliDecoder;
export const owner_symbol = Symbol('owner');

const FLUSH_BOUND_IDX_NORMAL: number = 0;
Expand All @@ -67,7 +81,7 @@ const FLUSH_BOUND: [[number, number], [number, number]] = [
const kFlushFlag = Symbol('kFlushFlag');
const kError = Symbol('kError');

function processCallback(this: zlibUtil.ZlibStream): void {
function processCallback(this: ZlibHandleType): void {
// This callback's context (`this`) is the `_handle` (ZCtx) object. It is
// important to null out the values once they are no longer needed since
// `_handle` can stay in memory long after the buffer is needed.
Expand Down Expand Up @@ -186,23 +200,27 @@ for (let i = 0; i < kFlushFlagList.length; i++) {
flushiness[kFlushFlagList[i] as number] = i;
}

type BufferWithFlushFlag = Buffer & { [kFlushFlag]: number };
function maxFlush(a: number, b: number): number {
return (flushiness[a] as number) > (flushiness[b] as number) ? a : b;
}

// Set up a list of 'special' buffers that can be written using .write()
// from the .flush() code as a way of introducing flushing operations into the
// write sequence.
const kFlushBuffers: BufferWithFlushFlag[] = [];
const kFlushBuffers: (Buffer & { [kFlushFlag]: number })[] = [];
{
const dummyArrayBuffer = new ArrayBuffer(0);
for (const flushFlag of kFlushFlagList) {
const buf = Buffer.from(dummyArrayBuffer) as BufferWithFlushFlag;
const buf = Buffer.from(dummyArrayBuffer) as Buffer & {
[kFlushFlag]: number;
};
buf[kFlushFlag] = flushFlag;
kFlushBuffers[flushFlag] = buf;
}
}

function zlibOnError(
this: zlibUtil.ZlibStream,
this: ZlibHandleType,
errno: number,
code: string,
message: string
Expand Down Expand Up @@ -332,15 +350,15 @@ export class ZlibBase extends Transform {
public _finishFlushFlag: number;
public _defaultFullFlushFlag: number;
public _info: unknown;
public _handle: zlibUtil.ZlibStream | null = null;
public _handle: ZlibHandleType | null = null;
public _writeState = new Uint32Array(2);

public [kError]: NodeError | undefined;

public constructor(
opts: ZlibOptions & DuplexOptions,
mode: number,
handle: zlibUtil.ZlibStream,
handle: ZlibHandleType,
{ flush, finishFlush, fullFlush }: ZlibDefaultOptions = zlibDefaultOptions
) {
let chunkSize = CONST_Z_DEFAULT_CHUNK;
Expand Down Expand Up @@ -544,10 +562,6 @@ export class ZlibBase extends Transform {
}
}

function maxFlush(a: number, b: number): number {
return (flushiness[a] as number) > (flushiness[b] as number) ? a : b;
}

export class Zlib extends ZlibBase {
public _level = CONST_Z_DEFAULT_COMPRESSION;
public _strategy = CONST_Z_DEFAULT_STRATEGY;
Expand Down Expand Up @@ -686,3 +700,69 @@ export class Zlib extends ZlibBase {
}
}
}

const kMaxBrotliParam = Math.max(
...Object.entries(constants).map(([key, value]) =>
key.startsWith('BROTLI_PARAM_') ? value : 0
)
);
const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1);
const brotliDefaultOptions: ZlibDefaultOptions = {
flush: CONST_BROTLI_OPERATION_PROCESS,
finishFlush: CONST_BROTLI_OPERATION_FINISH,
fullFlush: CONST_BROTLI_OPERATION_FLUSH,
};

export class Brotli extends ZlibBase {
public constructor(options: BrotliOptions | undefined | null, mode: number) {
ok(mode === CONST_BROTLI_DECODE || mode === CONST_BROTLI_ENCODE);
brotliInitParamsArray.fill(-1);

if (options?.params) {
for (const [origKey, value] of Object.entries(options.params)) {
const key = +origKey;
if (
Number.isNaN(key) ||
key < 0 ||
key > kMaxBrotliParam ||
((brotliInitParamsArray[key] as number) | 0) !== -1
) {
throw new ERR_BROTLI_INVALID_PARAM(origKey);
}

if (typeof value !== 'number' && typeof value !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE(
'options.params[key]',
'number',
value
);
}
// as number is required to avoid force type coercion on runtime.
// boolean has number representation, but typescript doesn't understand it.
brotliInitParamsArray[key] = value as number;
}
}

const handle =
mode === CONST_BROTLI_DECODE
? new zlibUtil.BrotliDecoder(mode)
: new zlibUtil.BrotliEncoder(mode);

const _writeState = new Uint32Array(2);
super(options ?? {}, mode, handle, brotliDefaultOptions);
this._writeState = _writeState;

// TODO(addaleax): Sometimes we generate better error codes in C++ land,
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
// the current bindings setup, though.
if (
!handle.initialize(
brotliInitParamsArray,
_writeState,
processCallback.bind(handle)
)
) {
throw new ERR_ZLIB_INITIALIZATION_FAILED();
}
}
}
53 changes: 53 additions & 0 deletions src/node/internal/internal_zlib_constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { default as zlibUtil } from 'node-internal:zlib';

const {
CONST_Z_OK,
CONST_Z_STREAM_END,
CONST_Z_NEED_DICT,
CONST_Z_ERRNO,
CONST_Z_STREAM_ERROR,
CONST_Z_DATA_ERROR,
CONST_Z_MEM_ERROR,
CONST_Z_BUF_ERROR,
CONST_Z_VERSION_ERROR,
} = zlibUtil;

const constPrefix = 'CONST_';
export const constants: Record<string, number> = {};

Object.defineProperties(
constants,
Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Object.entries(Object.getPrototypeOf(zlibUtil))
.filter(([k]) => k.startsWith(constPrefix))
.map(([k, v]) => [
k.slice(constPrefix.length),
{
value: v,
writable: false,
configurable: false,
enumerable: true,
},
])
)
);

// Translation table for return codes.
const rawCodes: Record<string, number | string> = {
Z_OK: CONST_Z_OK,
Z_STREAM_END: CONST_Z_STREAM_END,
Z_NEED_DICT: CONST_Z_NEED_DICT,
Z_ERRNO: CONST_Z_ERRNO,
Z_STREAM_ERROR: CONST_Z_STREAM_ERROR,
Z_DATA_ERROR: CONST_Z_DATA_ERROR,
Z_MEM_ERROR: CONST_Z_MEM_ERROR,
Z_BUF_ERROR: CONST_Z_BUF_ERROR,
Z_VERSION_ERROR: CONST_Z_VERSION_ERROR,
};

for (const key of Object.keys(rawCodes)) {
rawCodes[rawCodes[key] as number] = key;
}

export const codes = Object.freeze(rawCodes);
Loading

0 comments on commit 44a863b

Please sign in to comment.