Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement brotli classes #2619

Merged
merged 1 commit into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading