Skip to content

fix #156 Instead of pipeline.fireChannelRead(...), forward PacketsMessage from the context of the HTTP/WebSocket codec#158

Open
NeatGuyCoding wants to merge 5 commits intomainfrom
fix-ssl-buffer-cnt
Open

fix #156 Instead of pipeline.fireChannelRead(...), forward PacketsMessage from the context of the HTTP/WebSocket codec#158
NeatGuyCoding wants to merge 5 commits intomainfrom
fix-ssl-buffer-cnt

Conversation

@NeatGuyCoding
Copy link
Copy Markdown
Collaborator

@NeatGuyCoding NeatGuyCoding commented Mar 31, 2026

Description

Instead of pipeline.fireChannelRead(...), forward PacketsMessage from the context of the HTTP/WebSocket codec

Type of Change

  • 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 not work as expected)
  • Documentation update
  • Code refactoring
  • Test improvements
  • Build/tooling changes

Related Issue

Closes #156

Changes Made

  • Prefer ctx.pipeline().context(HttpRequestDecoder.class)
  • After WebSocket handshake (Netty renames/replaces decoders), prefer ctx.pipeline().context(WebSocket13FrameDecoder.class)
  • Added an extra fallback for robustness: ctx.pipeline().context(HttpServerCodec.class)
    Only if none exist, fall back to pipeline.fireChannelRead(...)

Testing

  • All existing tests pass
  • New tests added for new functionality
  • Tests pass locally with mvn test
  • Integration tests pass (if applicable)

Checklist

  • Code follows project coding standards
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (if needed)
  • Commit messages follow conventional format
  • No merge conflicts
  • All CI checks pass

Additional Notes

Any additional information, screenshots, or context that reviewers should know.

Summary by CodeRabbit

  • New Features

    • WebSocket control frame handling (automatic ping/pong response)
  • Improvements

    • Migrate TLS to Netty-native stack with dynamic provider selection, stricter protocol validation, safer trust/keystore handling and buffered keystore support
    • Better routing of polling/WebSocket payloads through protocol codecs
    • Server detects and records ephemeral (port 0) bind results after async start
  • Tests

    • End-to-end SSL/TLS Java client tests and server TLS restart tests
  • Chores

    • Added Netty tcnative dependency and centralized version property; updated example Netty version

…age from the context of the HTTP/WebSocket codec.

Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 31, 2026 01:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c572e346-93ee-495c-8087-4ee04fa889b9

📥 Commits

Reviewing files that changed from the base of the PR and between c45d67e and 8f31846.

📒 Files selected for processing (5)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketSslConfig.java
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/SocketSslServerRestartTest.java
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java
 __________________________________________________
< Are you a real coder or just a pretty committer? >
 --------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

Migrates TLS from JDK SSLContext to Netty SslContext with provider selection and optional tcnative; routes PacketsMessage via discovered Netty codec contexts in polling/websocket transports; adds end-to-end Java WSS/WebSocket tests and bumps example Netty version and dependency management for netty-tcnative-boringssl-static.

Changes

Cohort / File(s) Summary
Dependency Management
pom.xml, netty-socketio-core/pom.xml
Added netty.tcnative.version property, added managed dependency io.netty:netty-tcnative-boringssl-static and optional module dependency in core POM.
Example Netty Version
netty-socketio-examples/netty-socketio-examples-spring-boot-base/pom.xml
Bumped netty.version from 4.1.119.Final4.2.9.Final.
SSL/TLS Migration
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java
Replaced JDK SSLContext usage with Netty SslContext/SslContextBuilder; select SslProvider.OPENSSL when available else JDK; apply protocols conditionally; wire TrustManagerFactory via builder.trustManager(...); use try-with-resources for keystore/truststore streams.
Transport Message Routing
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java, netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java
Forward PacketsMessage starting from a discovered codec handler context (HttpRequestDecoder, WebSocket13FrameDecoder, or HttpServerCodec) instead of always from pipeline head; WebSocketTransport: explicit ping/pong handling, drop unknown frames, use configuration for websocket compression, centralized packet dispatch helper.
Integration Tests
netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java
Added JUnit 5 tests verifying Java client connectivity over WSS and plain WebSocket with handshake and ack assertions.

Sequence Diagrams

sequenceDiagram
    participant App as Application
    participant CI as SocketIOChannelInitializer
    participant Builder as SslContextBuilder
    participant OpenSSL as OpenSSL Provider
    participant JDK as JDK Provider
    participant SslC as SslContext

    App->>CI: createSSLContext(kmf, socketSslConfig)
    CI->>Builder: SslContextBuilder.forServer(kmf)
    alt OpenSSL available
        Builder->>OpenSSL: sslProvider(OPENSSL)
        OpenSSL-->>Builder: configured
    else OpenSSL not available
        Builder->>JDK: sslProvider(JDK)
        JDK-->>Builder: configured
    end
    Builder->>Builder: apply protocols & trustManager(if any)
    Builder-->>CI: build() -> SslContext
    CI-->>App: sslContext set on pipeline (newEngine(...))
Loading
sequenceDiagram
    participant Transport as Polling/WebSocket Transport
    participant Pipeline as Netty Pipeline
    participant Codec as Codec Handler
    participant PacketH as Packet Handler

    Transport->>Pipeline: create PacketsMessage
    Transport->>Pipeline: lookup codec handler context (HttpRequestDecoder / WebSocket13FrameDecoder / HttpServerCodec)
    alt codec context found
        Transport->>Codec: codecCtx.fireChannelRead(packetsMessage)
        Codec->>PacketH: deliver packetsMessage
    else not found
        Transport->>Pipeline: pipeline.fireChannelRead(packetsMessage)
        Pipeline->>PacketH: deliver packetsMessage
    end
    PacketH-->>Transport: processed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #154: Modifies WebSocketTransport/PollingTransport dispatch and handler forwarding; closely related to transport routing changes.
  • PR #91: Bumps Netty version (updates netty.version property) — related to the example Netty version bump.
  • PR #87: Also updates Netty version properties in POMs — related to dependency/version management changes.

Suggested labels

bug, enhancement

Poem

🐇 I hopped through keys and TLS bright,

Netty chose BoringSSL by night.
Packets find codecs, pings meet pong,
Tests clap paws — the flow is strong.
A happy rabbit hums along.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive The PR includes significant scope extensions beyond the core issue fix: SSL/TLS context migration to Netty, Netty version upgrades, and new test coverage that may exceed the original issue requirements. Clarify whether tcnative dependency, Netty version upgrade, SSL context migration, and WebSocket control frame handling are necessary for fixing issue #156 or should be separated into follow-up PRs.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly references the primary change: forwarding PacketsMessage from codec context instead of pipeline.fireChannelRead(), and directly links to issue #156.
Description check ✅ Passed The description covers required sections: Type of Change, Related Issue, and Changes Made with specific implementation details about codec context handling.
Linked Issues check ✅ Passed The changes address issue #156 by routing PacketsMessage through proper codec contexts (HttpRequestDecoder, WebSocket13FrameDecoder, HttpServerCodec) instead of pipeline.fireChannelRead, which should fix message reception issues with multiple transports.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ssl-buffer-cnt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable poems in the walkthrough.

Disable the reviews.poem setting to disable the poems in the walkthrough.

@NeatGuyCoding
Copy link
Copy Markdown
Collaborator Author

@sanjomo kindly review please

Copy link
Copy Markdown
Contributor

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 addresses #156 by changing how PacketsMessage is forwarded within the Netty pipeline to avoid re-entering from the pipeline head (and re-traversing SSL / HTTP / WebSocket codec handlers), which can break mixed polling+websocket transport flows.

Changes:

  • Forward PacketsMessage via the codec handler’s ChannelHandlerContext (with fallbacks) instead of pipeline.fireChannelRead(...) from the pipeline head.
  • Update SSL handling to build a Netty SslContext (optionally using OpenSSL via tcnative) and add an end-to-end Java socket.io-client SSL test + test keystore.
  • Add additional WebSocket frame handling (ping/pong) and align handshake extension enablement with configuration.isWebsocketCompression().

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pom.xml Adds managed Netty tcnative version + dependency management entry for netty-tcnative-boringssl-static.
netty-socketio-core/pom.xml Adds optional dependency on netty-tcnative-boringssl-static for core.
netty-socketio-examples/netty-socketio-examples-spring-boot-base/pom.xml Aligns example module Netty version with the parent.
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java Switches from JDK SSLContext to Netty SslContext + OpenSSL selection and truststore integration.
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java Fires PacketsMessage from codec context, adds ping/pong handling, and tweaks WebSocket handshake extension flag.
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java Fires PacketsMessage from codec context in POST handling (mirroring the websocket path).
netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java Adds an end-to-end Java socket.io-client test over WS and WSS (with ack).
netty-socketio-core/src/test/resources/ssl/test-socketio.p12 Adds a PKCS12 keystore used by the new SSL integration test.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java (1)

176-189: Extract the codec-context lookup into one helper.

The HttpRequestDecoderWebSocket13FrameDecoderHttpServerCodec resolution now exists here and in PollingTransport.onPost(...); centralizing it would keep future pipeline fixes in sync.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java`
around lines 176 - 189, Extract the repeated pipeline codec-context lookup into
a single helper method (e.g., findCodecContext) and replace the inline
resolution in firePacketsMessageToPacketHandler and PollingTransport.onPost with
a call to that helper; the helper should accept a ChannelHandlerContext, attempt
ctx.pipeline().context(HttpRequestDecoder.class) then
WebSocket13FrameDecoder.class then HttpServerCodec.class, and return the found
ChannelHandlerContext (or null) so callers can fireChannelRead on the returned
context or fall back to ctx.pipeline().fireChannelRead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java`:
- Around line 227-231: The test's allocatePort() creates a race by
opening/closing a ServerSocket on port 0; instead have the server bind to port 0
directly and read back the assigned port after startup. Replace uses of
allocatePort() with creating the SocketIOServer/Configuration using port 0, call
server.start() (or equivalent) and then read the actual bound port via the
server's accessor (e.g., SocketIOServer.getPort() /
getConfiguration().getPort()) to use in client connections; if server API
doesn't expose the bound port, keep the ServerSocket open until the
SocketIOServer binds to avoid the race.
- Around line 98-137: Current tests force opts.transports = {"websocket"} so
they never exercise the polling→websocket upgrade or the PollingTransport.onPost
forwarding path (regression `#156`); add a mixed-transport test (or modify an
existing test in SocketIoJavaClientSslTest) that sets opts.transports = new
String[] { "polling", "websocket" } (or leaves it default), then have the server
send a packet immediately after connect (server-initiated send) to trigger the
polling path and upgrade sequence, and assert the client does not disconnect
(reuse existing latches like serverReceivedHello/clientReceivedAck or add a
latch) to verify the polling->websocket upgrade and PollingTransport.onPost
behavior are exercised.
- Around line 75-95: The test currently creates a blind X509TrustManager and
disables hostname verification in SocketIoJavaClientSslTest, which bypasses real
TLS validation; replace that by loading the test truststore (the fixed keystore
resource already used elsewhere), create a TrustManagerFactory from that
KeyStore to obtain the proper X509TrustManager, initialize SSLContext with those
trust managers (use SSLContext.init(null, trustManagers, new SecureRandom())),
pass the real X509TrustManager to
OkHttpClient.Builder.sslSocketFactory(sslContext.getSocketFactory(),
realTrustManager), and remove the hostnameVerifier override so the client
actually verifies the server certificate and hostname.

---

Nitpick comments:
In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java`:
- Around line 176-189: Extract the repeated pipeline codec-context lookup into a
single helper method (e.g., findCodecContext) and replace the inline resolution
in firePacketsMessageToPacketHandler and PollingTransport.onPost with a call to
that helper; the helper should accept a ChannelHandlerContext, attempt
ctx.pipeline().context(HttpRequestDecoder.class) then
WebSocket13FrameDecoder.class then HttpServerCodec.class, and return the found
ChannelHandlerContext (or null) so callers can fireChannelRead on the returned
context or fall back to ctx.pipeline().fireChannelRead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e35b761-a7c7-49aa-9836-24a719cee537

📥 Commits

Reviewing files that changed from the base of the PR and between e3acbd9 and 3226e53.

📒 Files selected for processing (8)
  • netty-socketio-core/pom.xml
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java
  • netty-socketio-core/src/test/resources/ssl/test-socketio.p12
  • netty-socketio-examples/netty-socketio-examples-spring-boot-base/pom.xml
  • pom.xml

Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 31, 2026 02:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java (1)

219-225: Consider warning about deprecated TLS versions (TLSv1, TLSv1.1).

The validation accepts "TLSv1" and "TLSv1.1" which are deprecated per RFC 8996. Given that SocketSslConfig defaults to "TLSv1", users relying on defaults may encounter connection failures with modern clients.

♻️ Proposed enhancement to warn about deprecated protocols
         if (sslProtocol != null) {
             // SocketSslConfig historically accepted SSLContext algorithm names like "TLS".
             // SslContextBuilder.protocols(...) expects concrete enabled protocol versions.
             if (isTlsProtocolVersion(sslProtocol)) {
+                if ("TLSv1".equals(sslProtocol) || "TLSv1.1".equals(sslProtocol)) {
+                    log.warn("SocketSslConfig.sslProtocol='{}' is deprecated (RFC 8996). " +
+                            "Consider upgrading to 'TLSv1.2' or 'TLSv1.3'.", sslProtocol);
+                }
                 builder.protocols(sslProtocol);
             } else {

Consider also updating the default in SocketSslConfig from "TLSv1" to "TLSv1.2" in a separate change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java`
around lines 219 - 225, The current protocol validation in isTlsProtocolVersion
accepts deprecated values like "TLSv1" and "TLSv1.1" but does not warn users;
update the branch in SocketIOChannelInitializer (where you call
builder.protocols(sslProtocol)) to detect these deprecated strings and emit a
warning (e.g., via log.warn) that the chosen protocol is deprecated per RFC 8996
and may cause modern client failures, while still allowing the value; reference
the isTlsProtocolVersion helper and SocketSslConfig.sslProtocol to locate the
check, and note in the log that changing the default to "TLSv1.2" is recommended
(implement the default change in a separate PR).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java`:
- Around line 237-244: The isTlsProtocolVersion method's regex currently accepts
"TLSv1.0"; update the pattern used in isTlsProtocolVersion to only allow "TLSv1"
or "TLSv1.1", "TLSv1.2", "TLSv1.3" (i.e., disallow a ".0" suffix). Replace the
existing regex in isTlsProtocolVersion with one that permits either no minor
version or a minor of 1, 2, or 3 (for example using a grouped alternative for
1|2|3).

---

Nitpick comments:
In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java`:
- Around line 219-225: The current protocol validation in isTlsProtocolVersion
accepts deprecated values like "TLSv1" and "TLSv1.1" but does not warn users;
update the branch in SocketIOChannelInitializer (where you call
builder.protocols(sslProtocol)) to detect these deprecated strings and emit a
warning (e.g., via log.warn) that the chosen protocol is deprecated per RFC 8996
and may cause modern client failures, while still allowing the value; reference
the isTlsProtocolVersion helper and SocketSslConfig.sslProtocol to locate the
check, and note in the log that changing the default to "TLSv1.2" is recommended
(implement the default change in a separate PR).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: caa3996c-b214-4246-b168-6ca0fecbd770

📥 Commits

Reviewing files that changed from the base of the PR and between 3226e53 and c45d67e.

📒 Files selected for processing (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java

Copy link
Copy Markdown
Contributor

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

Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

pom.xml:86

  • PR title/description focus on forwarding PacketsMessage, but this PR also introduces a Netty-native TLS provider selection (OpenSSL/JDK) and adds a new netty-tcnative-boringssl-static dependency. Consider updating the PR description (and/or splitting into a separate PR) so reviewers and release notes clearly capture the TLS/runtime dependency changes.
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <testcontainers.version>2.0.3</testcontainers.version>
    <netty.version>4.2.9.Final</netty.version>
    <netty.tcnative.version>2.0.74.Final</netty.tcnative.version>
    <jmockit.version>1.50</jmockit.version>
    <byte-buddy.version>1.18.4</byte-buddy.version>
    <junit.version>6.0.2</junit.version>
    <junit-platform-launcher.version>6.0.2</junit-platform-launcher.version>
    <slf4j.version>2.0.17</slf4j.version>
    <jackson.version>2.20.1</jackson.version>
    <redisson.version>4.1.0</redisson.version>
    <hazelcast.version>5.2.5</hazelcast.version>
    <awaitility.version>4.3.0</awaitility.version>
    <assertj.version>3.27.6</assertj.version>
    <mockito.version>5.21.0</mockito.version>
    <logback.version>1.5.25</logback.version>
    <socketio.version>2.1.2</socketio.version>
    <javafaker.version>1.0.2</javafaker.version>
    <kafka.version>4.1.1</kafka.version>
    <commons-lang3.version>3.20.0</commons-lang3.version>
    <nats.version>2.25.1</nats.version>

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

Comment on lines +206 to +240
private SslContext createSSLContext(SocketSslConfig socketSslConfig) throws Exception {
KeyStore ks = KeyStore.getInstance(socketSslConfig.getKeyStoreFormat());
try (InputStream keyStoreStream = socketSslConfig.getKeyStore()) {
ks.load(keyStoreStream, socketSslConfig.getKeyStorePassword().toCharArray());
}

KeyManagerFactory kmf = KeyManagerFactory.getInstance(socketSslConfig.getKeyManagerFactoryAlgorithm());
kmf.init(ks, socketSslConfig.getKeyStorePassword().toCharArray());

SslProvider sslProvider = OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;

SslContextBuilder builder = SslContextBuilder.forServer(kmf).sslProvider(sslProvider);
String sslProtocol = socketSslConfig.getSSLProtocol();
if (sslProtocol != null) {
// SocketSslConfig historically accepted SSLContext algorithm names like "TLS".
// SslContextBuilder.protocols(...) expects concrete enabled protocol versions.
if (isTlsProtocolVersion(sslProtocol)) {
builder.protocols(sslProtocol);
} else {
log.warn("Ignoring SocketSslConfig.sslProtocol='{}' because it is not a concrete TLS protocol " +
"version (expected values like 'TLSv1.2' or 'TLSv1.3'). Using provider defaults instead.",
sslProtocol);
}
}
if (socketSslConfig.getTrustStore() != null) {
KeyStore ts = KeyStore.getInstance(socketSslConfig.getTrustStoreFormat());
ts.load(socketSslConfig.getTrustStore(), socketSslConfig.getTrustStorePassword().toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ts = KeyStore.getInstance(socketSslConfig.getTrustStoreFormat());
try (InputStream trustStoreStream = socketSslConfig.getTrustStore()) {
ts.load(trustStoreStream, socketSslConfig.getTrustStorePassword().toCharArray());
}
tmf.init(ts);
managers = tmf.getTrustManagers();
builder.trustManager(tmf);
}
return builder.build();
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

createSSLContext now wraps socketSslConfig.getKeyStore() / getTrustStore() in try-with-resources, which closes the provided InputStreams. Configuration(Configuration) performs a shallow copy of SocketSslConfig (same instance), and SocketIOServer.stop() explicitly documents that the server may be started again. Closing the streams on first start makes subsequent restarts fail even if the caller provided a resettable stream. Consider either (a) documenting/contracting that SocketSslConfig must provide a fresh InputStream per start (e.g., via Supplier/Path), or (b) buffering the keystore/truststore bytes so restarts don’t depend on reusing the original stream.

Copilot uses AI. Check for mistakes.
Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 1, 2026 03:00
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustAll }, new SecureRandom());

Check failure

Code scanning / CodeQL

`TrustManager` that accepts all certificates High test

This uses
TrustManager
, which is defined in
SocketIoJavaClientSslTest$
and trusts any certificate.

Copilot Autofix

AI 7 minutes ago

General approach: Replace the custom “trust all” X509TrustManager with the platform-default trust managers derived from TrustManagerFactory. This way, normal certificate validation occurs and we no longer have an implementation that accepts all certificates. Additionally, remove the custom hostnameVerifier that unconditionally returns true, so hostname verification uses the default secure behavior.

Concrete fix in this file:

  1. Remove the anonymous X509TrustManager trustAll definition.
  2. Instead of sslContext.init(null, new TrustManager[] { trustAll }, new SecureRandom());, initialize the SSLContext with default trust managers:
    • Obtain a TrustManagerFactory via TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).
    • Initialize it with null key store (tmf.init((java.security.KeyStore) null);) to use the default system trust store.
    • Use tmf.getTrustManagers() as the second argument to sslContext.init(...).
  3. Change the OkHttpClient construction:
    • Use the first X509TrustManager from the default trust managers when calling .sslSocketFactory.
    • Remove .hostnameVerifier((hostname, session) -> true) so default hostname verification applies.
  4. Add imports as needed: javax.net.ssl.TrustManagerFactory and java.security.KeyStore (explicit type reference; we already have other java.security imports).

These changes are localized to the shouldPollUpgradeToWebSocketWithServerPushAndClientHelloAckOverWss test method in SocketIoJavaClientSslTest.java and preserve the overall test logic and structure while eliminating the “trust all certificates” behavior.

Suggested changeset 1
netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java b/netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java
--- a/netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java
+++ b/netty-socketio-core/src/test/java/com/socketio4j/socketio/transport/SocketIoJavaClientSslTest.java
@@ -27,6 +27,7 @@
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
 import org.json.JSONObject;
@@ -159,26 +160,25 @@
         int port = awaitBoundPort(server);
         assertTrue(port > 0, "server did not bind an ephemeral port");
 
-        X509TrustManager trustAll = new X509TrustManager() {
-            @Override
-            public void checkClientTrusted(X509Certificate[] chain, String authType) {
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        tmf.init((java.security.KeyStore) null);
+        TrustManager[] trustManagers = tmf.getTrustManagers();
+        X509TrustManager trustManager = null;
+        for (TrustManager tm : trustManagers) {
+            if (tm instanceof X509TrustManager) {
+                trustManager = (X509TrustManager) tm;
+                break;
             }
+        }
+        if (trustManager == null) {
+            throw new IllegalStateException("No X509TrustManager found");
+        }
 
-            @Override
-            public void checkServerTrusted(X509Certificate[] chain, String authType) {
-            }
-
-            @Override
-            public X509Certificate[] getAcceptedIssuers() {
-                return new X509Certificate[0];
-            }
-        };
         SSLContext sslContext = SSLContext.getInstance("TLS");
-        sslContext.init(null, new TrustManager[] { trustAll }, new SecureRandom());
+        sslContext.init(null, trustManagers, new SecureRandom());
 
         OkHttpClient okHttp = new OkHttpClient.Builder()
-                .sslSocketFactory(sslContext.getSocketFactory(), trustAll)
-                .hostnameVerifier((hostname, session) -> true)
+                .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
                 .readTimeout(1, TimeUnit.MINUTES)
                 .build();
 
EOF
@@ -27,6 +27,7 @@

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.json.JSONObject;
@@ -159,26 +160,25 @@
int port = awaitBoundPort(server);
assertTrue(port > 0, "server did not bind an ephemeral port");

X509TrustManager trustAll = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((java.security.KeyStore) null);
TrustManager[] trustManagers = tmf.getTrustManagers();
X509TrustManager trustManager = null;
for (TrustManager tm : trustManagers) {
if (tm instanceof X509TrustManager) {
trustManager = (X509TrustManager) tm;
break;
}
}
if (trustManager == null) {
throw new IllegalStateException("No X509TrustManager found");
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustAll }, new SecureRandom());
sslContext.init(null, trustManagers, new SecureRandom());

OkHttpClient okHttp = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), trustAll)
.hostnameVerifier((hostname, session) -> true)
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.readTimeout(1, TimeUnit.MINUTES)
.build();

Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown
Contributor

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

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.


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

Comment on lines +81 to +94
byte[] resolveKeyStoreBytes() throws IOException {
synchronized (sslMaterialLock) {
if (cachedKeyStoreBytes != null) {
return cachedKeyStoreBytes;
}
if (keyStore == null) {
return null;
}
try (InputStream in = keyStore) {
cachedKeyStoreBytes = in.readAllBytes();
}
keyStore = null;
return cachedKeyStoreBytes;
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

resolveKeyStoreBytes() always returns cachedKeyStoreBytes when present, so calling setKeyStore(...) after the first TLS context build will never take effect. This also risks leaking the newly provided stream because it may never be read/closed. If runtime reconfiguration is expected, consider clearing the cache when setKeyStore(...) is called or adding an explicit API to reset the cached SSL material.

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +154
byte[] resolveTrustStoreBytes() throws IOException {
synchronized (sslMaterialLock) {
if (cachedTrustStoreBytes != null) {
return cachedTrustStoreBytes;
}
if (trustStore == null) {
return null;
}
try (InputStream in = trustStore) {
cachedTrustStoreBytes = in.readAllBytes();
}
trustStore = null;
return cachedTrustStoreBytes;
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

resolveTrustStoreBytes() always prefers cachedTrustStoreBytes when present, so a later setTrustStore(...) call won’t be observed and the new stream may never be consumed/closed. If the intent is to support updating trust material between restarts, consider clearing the cache on setTrustStore(...) or providing a dedicated reset method.

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +257
// Common enabled-protocol tokens used by JSSE/Netty.
// Accept "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"; reject "TLSv1.0" and other dotted minors.
if (value == null) {
return false;
}
return value.matches("^TLSv1(\\.(1|2|3))?$");
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

isTlsProtocolVersion currently accepts "TLSv1", but in JSSE/Netty this token corresponds to TLS 1.0. The comment says TLSv1.0 should be rejected, so either the regex/comment is inconsistent or TLS 1.0 is being re-enabled unintentionally (which can be a security and interoperability issue because many clients/JDKs disable it). Consider tightening the accepted set (e.g., only TLSv1.2+/TLSv1.3) or updating the comment/logging to match the intended behavior.

Suggested change
// Common enabled-protocol tokens used by JSSE/Netty.
// Accept "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"; reject "TLSv1.0" and other dotted minors.
if (value == null) {
return false;
}
return value.matches("^TLSv1(\\.(1|2|3))?$");
// Accept only concrete, modern TLS protocol versions supported by JSSE/Netty.
// Specifically allow "TLSv1.2" and "TLSv1.3" and reject TLS 1.0/1.1 and other variants.
if (value == null) {
return false;
}
return value.matches("^TLSv1\\.(2|3)$");

Copilot uses AI. Check for mistakes.
Comment on lines +601 to 612
if (configCopy.getPort() == 0) {
try {
InetSocketAddress local = (InetSocketAddress) cf.channel().localAddress();
int actualPort = local.getPort();
configCopy.setPort(actualPort);
configuration.setPort(actualPort);
} catch (Exception ignore) {
// keep configured port if localAddress is not InetSocketAddress
}
}
serverStatus.set(ServerStatus.STARTED);
log.info("SocketIO server started on port {}", configCopy.getPort());
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

When the configured port is 0 (ephemeral), this code mutates both configCopy and the original configuration to the bound port. That changes the semantics of “port 0” for subsequent restarts (a second start() will attempt to bind the previous port instead of a new ephemeral port) and can introduce flakiness or port-collision surprises. Consider keeping the configured port as-is (0) and exposing the actual bound port via serverChannel.localAddress()/a dedicated getter instead of persisting it back into the configuration object.

Suggested change
if (configCopy.getPort() == 0) {
try {
InetSocketAddress local = (InetSocketAddress) cf.channel().localAddress();
int actualPort = local.getPort();
configCopy.setPort(actualPort);
configuration.setPort(actualPort);
} catch (Exception ignore) {
// keep configured port if localAddress is not InetSocketAddress
}
}
serverStatus.set(ServerStatus.STARTED);
log.info("SocketIO server started on port {}", configCopy.getPort());
int loggedPort = configCopy.getPort();
try {
if (cf.channel().localAddress() instanceof InetSocketAddress) {
InetSocketAddress local = (InetSocketAddress) cf.channel().localAddress();
loggedPort = local.getPort();
}
} catch (Exception ignore) {
// keep configured port if localAddress is not InetSocketAddress
}
serverStatus.set(ServerStatus.STARTED);
log.info("SocketIO server started on port {}", loggedPort);

Copilot uses AI. Check for mistakes.
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.

[BUG] JS client does not receive messages if both polling ad websocket transports are configured

3 participants