mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
12 hours ago fc4e7423e8007b9b6f5368fa101493cfd5711760
refs
author Valery Kharseko <vharseko@3a-systems.ru>
Saturday, October 17, 2026 17:07 +0200
committer GitHub <noreply@github.com>
Saturday, October 17, 2026 17:07 +0200
commitfc4e7423e8007b9b6f5368fa101493cfd5711760
tree 6d584a8ed12756cee5d7253528252faa3487bc6e tree | zip | gz
parent e7da03271cf5bfb204e5d8b30f7d975d05ce3ccb view | diff
[#1216] Keep the decoding error of the client filter when no LDAP connection is attached (#1217)

Fixes #1216

## Problem

`LDAPClientFilter.handleReadException()` and `exceptionOccurred()` close
the LDAP connection attached to the Grizzly connection without checking
that one is attached. After `handleClose()` has removed it, a message
the client cannot decode throws a `NullPointerException` in place of the
`DecodeException`. Grizzly logs the `NullPointerException` at `WARNING`
(`GRIZZLY0013`), and the decoding error with its message is lost.

The failed job of #1213 ([run
38947864951](https://github.com/OpenIdentityPlatform/OpenDJ/actions/runs/38947864951/job/117131465938))
logged exactly that: the client read its own Unbind on a connection it
had already closed.

## Change

Both methods return when no LDAP connection is attached, as
`handleClose()` and the result handlers of `ClientResponseHandler`
already do.

- `handleRead()` then throws the `DecodeException`, as it does with a
connection attached. `DefaultFilterChain.execute()` logs it at `FINE`
and closes the connection.
- In `exceptionOccurred()` the Grizzly connection is still open (the
method returns earlier otherwise), so only a connection not registered
yet has no LDAP connection. No failure was seen there. The check keeps
the method from failing the same way: an exception thrown from it inside
`DefaultFilterChain.execute()` would escape `throwChain()` before the
`closeWithReason()` that follows.

One correction to the issue: reads cannot reach `handleRead()` before
`registerConnection()`. Grizzly 4.0.2 enables reading only after the
connect completion handler has run:
`TCPNIOConnectorHandler.EnableReadHandler.onComplete()` calls
`completed()` before `enableInitialOpRead()`. The window seen in
practice is the one after `handleClose()`.

## Tests

`LDAPClientFilterTestCase` (new):

| Test | What it pins |
|---|---|
| `handleReadWithoutLDAPConnectionThrowsTheDecodeError` | No LDAP
connection attached: `handleRead()` throws `Unexpected LDAP request:
id=1, …`. Red on master with the NPE. |
| `exceptionOccurredWithoutLDAPConnectionReturns` | No LDAP connection
attached: `exceptionOccurred()` returns. Red on master with the NPE. |
| `handleReadClosesTheLDAPConnectionOnDecodeError` | With one attached,
it is still closed with `CLIENT_SIDE_DECODING_ERROR`. |
| `exceptionOccurredClosesTheLDAPConnection` | With one attached, it is
still closed with `CLIENT_SIDE_LOCAL_ERROR`. |

`GrizzlyLDAPConnection` is final, so the last two use a real one and
check the error through a `ConnectionEventListener` mock.

Mutants of `LDAPClientFilter`, each run against the class:

| Mutant | Caught by |
|---|---|
| no null check in `handleReadException()` |
`handleReadWithoutLDAPConnectionThrowsTheDecodeError` |
| no null check in `exceptionOccurred()` |
`exceptionOccurredWithoutLDAPConnectionReturns` |
| `handleReadException()` never closes |
`handleReadClosesTheLDAPConnectionOnDecodeError` |
| `exceptionOccurred()` never closes |
`exceptionOccurredClosesTheLDAPConnection` |

## Verification

- `org.forgerock.opendj.grizzly.**` in the reactor (`-pl opendj-grizzly
-am`), JDK 27, macOS: 1044 tests, 0 failures, 0 errors, 0 skipped.
1 files modified
1 files added
136 ■■■■■ changed files
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java 9 ●●●●● diff | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/LDAPClientFilterTestCase.java 127 ●●●●● diff | view | raw | blame | history