Skip to content

Conversation

@robert-s-ubi
Copy link
Contributor

@robert-s-ubi robert-s-ubi commented Dec 25, 2025

Description

Add a reimplementation of the WebSocket Per-Message Deflate extension written from scratch for full RFC 7692 compliance, with the following over the existing implementation:

  • compliant extension parameters negotiation handling
  • default operation with context takeover works
  • fragmented messages are handled correctly (either all fragments are compressed/decompressed or none)
  • clears RSV1 after decompressing to remove the compression mark
  • produces the result specified in RFC 7692 section 7.2.3.4
  • produces the result specified in RFC 7692 section 7.2.3.6
  • uses the extension common name registered for RFC 7692
  • has an additional optional constructor parameter "maxFragmentSize" which is checked during decompression to prevent unlimited memory use
  • has an additional API "getCompressionRatio()" to get the effective compression ratio (over all payloads compressed and decompressed)

Otherwise, it is fully API compatible with the old implementation.

Add RFC 7692 tests for the new implementation, to validate it produces the expected results for all examples from RFC 7692 section 7.2.3.

Adapt the existing unit tests to the corrected behavior and changed defaults.

Related Issue

This fixes issue #1496

Motivation and Context

The existing implementation was broken in multiple ways and not fully RFC 7692 compliant. The new implementation was written to fix all that.

How Has This Been Tested?

Tested with adapted existing tests for the existing implementation, new RFC 7692 compliance tests (all part of this PR), and also within the Java-OCA-OCPP library, and an integration test which creates an OCPP client and server and connects these on the local machine.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Add a reimplementation of the WebSocket Per-Message Deflate extension
written from scratch for full RFC 7962 compliance, with the following
over the existing implementation:

- compliant extension parameters negotiation handling
- default operation with context takeover works
- fragmented messages are handled correctly (either all fragments are
  compressed/decompressed or none)
- clears RSV1 after decompressing to remove the compression mark
- produces the result specified in RFC 7962 section 7.2.3.4
- produces the result specified in RFC 7962 section 7.2.3.6
- uses the extension common name registered for RFC 7962
- has an additional optional constructor parameter "maxFrameSize" to
  ensure the limit is not exceeded while decompressing already
- has an additional API "getCompressionRatio()" to get the effective
  compression ratio (over all payloads compressed and decompressed)

Otherwise, it is fully API compatible with the old implementation.

For now, the new implementation lives side by side with the old one, and
is named "WebSocketPerMessageDeflateExtension".

Add RFC 7962 tests for the new implementation, to validate it produces
the expected results for all examples from RFC 7962 section 7.2.3.

Add a copy of the unit tests for the old implementation, which verifies
the new implementation works the same, except for fixed issues and
different defaults.
@robert-s-ubi
Copy link
Contributor Author

If desired, I could also make the new implementation replace the old one.

@robert-s-ubi
Copy link
Contributor Author

@marci4 could you take a look at this?

@marci4
Copy link
Collaborator

marci4 commented Jan 7, 2026

@robert-s-ubi sorry for the delay. I finally got around to check it.

For the structure.
You can simple replace https://github.com/ubitricity/Java-WebSocket/blob/add_new_websocketpermessagedeflate_extension/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java. There is no need to create a new .java file.

For the implementation:
I tested it against an old version of the https://github.com/crossbario/autobahn-testsuite.
The following test cases for the client reported not implemented:

  • 13.3 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 9)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(False, 9)]
  • 13.4 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]
  • 13.5 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 9)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 9)]
  • 13.6 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

The following test cases for the server reported not implemented:

  • 13.3 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 9)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(False, 9)]
  • 13.5 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 9)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 9)]

Do you think you can take a look at these?

@robert-s-ubi
Copy link
Contributor Author

@marci4 thanks for taking the time!

As to the test cases, 13.3 and 13.5 are indeed not supported, because the Java Deflater/Inflater classes do not support setting the maxWindowBits parameter (the underlying native zlib does, but that is not exposed through that class) and thus always operate with the default maxWindowBits 15, which is also the default for RFC 7692.

And the getProvidedExtensionsAs*() methods omit this parameter when it is the default value - which it always is, thus the implementation will never include that extension parameter.

So from the outside it looks like this extension parameter is not implemented at all, but internally, it is fully implemented, but limited to the capabilities of Java Deflater/Inflater. This limitation could be overcome by using a different Java library for zlib compression which offers this parameter, but I chose to stick to the standard Java classes for now.

I'll add a commit which renames the new implementation and test so that it replaces the old ne.

Instead of adding WebSocketPerMessageDeflateExtension side by side with
the old PerMessageDeflateExtension, rename the new implementation to the
old name to replace it.
@robert-s-ubi
Copy link
Contributor Author

@marci4 I have added a commit that makes the new implementation replace the old one. The tests failed in Issue997Test which seems to have a stability issue unrelated to my changes. I cannot retrigger the CI run, maybe you can?

@marci4
Copy link
Collaborator

marci4 commented Jan 9, 2026

@robert-s-ubi dont worry about this test ;)

@robert-s-ubi
Copy link
Contributor Author

@robert-s-ubi dont worry about this test ;)

I found that I can retrigger the checks by closing and reopening the PR, and finally got all checks to pass.

@marci4 marci4 requested a review from Copilot January 9, 2026 16:25
@marci4 marci4 added this to the Release 1.6.1 milestone Jan 9, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new RFC 7692-compliant WebSocket Per-Message Deflate extension implementation to address multiple compliance and correctness issues in the existing implementation. The new implementation is named PerMessageDeflateExtension and lives alongside the old implementation.

Key changes:

  • Complete rewrite of the Per-Message Deflate extension with proper RFC 7692 compliance
  • New features including maxFrameSize parameter and getCompressionRatio() API
  • Changed default behavior for context takeover (now enabled by default) and compression threshold (64 bytes instead of 1024)

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 27 comments.

File Description
src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java Complete reimplementation with proper RFC 7692 compliance, improved parameter negotiation, and new APIs
src/test/java/org/java_websocket/extensions/PerMessageDeflateExtensionRFC7962Test.java New RFC 7692 compliance tests validating all examples from RFC 7692 section 7.2.3
src/test/java/org/java_websocket/extensions/PerMessageDeflateExtensionTest.java Updated existing tests to reflect changed defaults and fixed behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@robert-s-ubi robert-s-ubi changed the title Add new RFC 7962-compliant Per-Message Deflate extension implementation Add new RFC 7692-compliant Per-Message Deflate extension implementation Jan 9, 2026
RFC 7692 was often mistyped as RFC 7962. Fix this.
The PerMessageDeflationExtension's optional maxFrameSize constructor
parameter is applied to the size of a fragment while decompressing, and
not to the complete frame size (that size is checked after passing the
decompressed fragment), so rename the parameter to what it actually is.

Add Javadoc to the full-parameter constructor method.
This new API was intended to be called by library users, but could never
have worked, as it would need to be called on the class instance which
is actually processing frames, but that instance is created internally
in the library using copyInstance() and thus inaccessible to the library
users.

Remove the API and the stats kept for it.
The PerMessageDeflateExtension#getCompressionRatio() API needs to be
called on the class instance used by the library. Add Javadoc explaining
how that instance can be retrieved.
@robert-s-ubi
Copy link
Contributor Author

Following the AI review, I:

  • fixed the shockingly many typos of RFC 7692
  • corrected the name of the "maxFrameSize" parameter to "maxFragmentSize" as that is what it actually limits
  • added documentation to the new getCompressionRatio() API after temporarily removing it thinking it couldn't work due to the copyInstance() used by the library, but found a way to access the copied instance after all, and documented that

And now it passed the checks, too.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants