Skip to content

Conversation

@alistairjevans
Copy link

Based pretty heavily on the brotli support.

Closes #4013

@alistairjevans alistairjevans requested review from a team as code owners February 3, 2026 11:09
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@alistairjevans
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

input_.pos = 0;
output_.dst = output.begin();
output_.size = output.size();
output_.pos = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could defer to the setInputBuffer and setOutputBuffer methods below to reduce duplication

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment on lines 795 to 797
ZstdEncoderContext::ZstdEncoderContext(ZlibMode _mode): ZstdContext(_mode) {
cctx_ = ZSTD_createCCtx();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??

Suggested change
ZstdEncoderContext::ZstdEncoderContext(ZlibMode _mode): ZstdContext(_mode) {
cctx_ = ZSTD_createCCtx();
}
ZstdEncoderContext::ZstdEncoderContext(ZlibMode _mode)
: ZstdContext(_mode),
cctx_(ZSTD_createCCtx()) {}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, should we check for cctx_ == nullptr in here or no?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't check for nullptr here, initialize will raise an error if it failed to set. Will update to use the compact style.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

ZstdEncoderContext::~ZstdEncoderContext() {
if (cctx_ != nullptr) {
ZSTD_freeCCtx(cctx_);
cctx_ = nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth wrapping this in a custom smart pointer to ensure the cleanup is correct. Non-blocking tho.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added (brotli implementation does already do this)

kj::Maybe<CompressionError> ZstdEncoderContext::initialize(uint64_t pledgedSrcSize) {
if (cctx_ == nullptr) {
cctx_ = ZSTD_createCCtx();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this be the case since we're calling ZSTD_createCCtx() in the constructor? If there's a possibility this is nullptr here a doc comment explaining the scenario would be helpful.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was a faulty duplication of some of the behaviour in the brotli implementation; resetStream for brotli calls initialize to reset the context, but zstd has it's own ZSTD_CCtx_reset, so creating the context in initialize is unnecessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up

Comment on lines 842 to 846
if (ZSTD_isError(result)) {
error_ = ZSTD_getErrorCode(result);
return CompressionError(kj::str(ZSTD_getErrorName(result)),
kj::str("ERR_ZSTD_COMPRESSION_FAILED"), -1);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this ZSTD_isError(...) block is repeated across multiple functions, it might be worth having a separate utility function, e.g.

KJ_IF_SOME(err, checkIsError(result)) {
  return err;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a zstdCheckError function, although to be honest I'm not sure I like it more than just making the repeated checks 🙈

}

kj::Maybe<CompressionError> ZstdEncoderContext::setParams(int key, int value) {
size_t result = ZSTD_CCtx_setParameter(cctx_, static_cast<ZSTD_cParameter>(key), value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A KJ_DASSERT verifying that key is within the range of ZSTD_cParameter would be helpful

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@jasnell jasnell requested a review from anonrig February 3, 2026 15:25
@jasnell
Copy link
Collaborator

jasnell commented Feb 3, 2026

@anonrig ... given your familiarity with the zlib impl in workerd, it would be good to have your review on this as well.

Comment on lines 828 to 830
constructor(options: ZstdOptions | undefined | null, mode: number) {
ok(mode === CONST_ZSTD_DECODE || mode === CONST_ZSTD_ENCODE);
zstdInitParamsArray.fill(-1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diverges from the Node.js implementation and makes it a lot less readable. Ideally the constructor should receive a parameter for initParamsArray just like Node.js

class Zstd extends ZlibBase {
  constructor(opts, mode, initParamsArray, maxParam) {

If we diverge from Node.js, finding bugs & keep the code in sync would be a lot harder then it really is.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated 👍

size = "large",
src = "zstd-nodejs-test.wd-test",
args = ["--experimental"],
data = ["zstd-nodejs-test.js"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data = ["zstd-nodejs-test.js"],
data = ["zlib-zstd-nodejs-test.js"],

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed


wd_test(
size = "large",
src = "zstd-nodejs-test.wd-test",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
src = "zstd-nodejs-test.wd-test",
src = "zlib-zstd-nodejs-test.wd-test",

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed

export const zstdConstantsTest = {
test() {
// Flush directives
assert.strictEqual(typeof zlib.constants.ZSTD_e_continue, 'number');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've already tested this in zlib-nodejs-test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed duplicate

@alistairjevans
Copy link
Author

Thank you for the feedback @anonrig and @jasnell! 🙏 I believe I've addressed all your comments now.

@alistairjevans
Copy link
Author

Merged latest; @anonrig / @jasnell , any chance of a re-review? 🙏

@anonrig
Copy link
Member

anonrig commented Feb 11, 2026

LGTM. @alistairjevans formatter seems to be failing: https://github.com/cloudflare/workerd/actions/runs/21922721360/job/63307147452?pr=6007

@jasnell can you take a look?

@alistairjevans
Copy link
Author

alistairjevans commented Feb 11, 2026

Thanks @anonrig, formatting fix pushed, linter passes locally now.

@jasnell
Copy link
Collaborator

jasnell commented Feb 11, 2026

Overall I think this looks good. What I'm considering... (something I wish I had thought to do on the previous node:zlib additions)... is adding this initially behind an experimental compat flag and having the security team do some quick fuzz testing on it. Anything new that is dealing with bytes being passed around makes me just a bit nervous and a little extra testing doesn't hurt. That's not to say I see anything in here concretely questionable, just thinking about taking the extra precaution. @danlapid what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

node:zlib implement zstd

3 participants