This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 23b61b9098f754df0151f33835292bbc3114e5a6 Author: opencode <[email protected]> AuthorDate: Fri Oct 9 11:52:24 2026 +0200 Make RpcMessage serialization tolerate null identifiers The public RpcMessage constructor accepts null rpcId and uuid arrays, but writeExternal dereferenced them, so serializing such a message failed with a NullPointerException. The same package already treats null byte arrays as a supported input in ByteMessage, which writes a zero length and skips the bytes. Follow that pattern in the writeExternal methods of RpcMessage and NoRpcChannelReply. Streams with non-null arrays are unchanged byte for byte, and zero lengths are read back by readExternal as zero-length arrays. Document the normalization in the constructor. Add TestRpcMessage covering normal, null and mixed null round-trips. --- .../apache/catalina/tribes/group/RpcMessage.java | 27 ++++--- .../catalina/tribes/group/TestRpcMessage.java | 88 ++++++++++++++++++++++ 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/java/org/apache/catalina/tribes/group/RpcMessage.java b/java/org/apache/catalina/tribes/group/RpcMessage.java index 574e661f66..7a4dfe4498 100644 --- a/java/org/apache/catalina/tribes/group/RpcMessage.java +++ b/java/org/apache/catalina/tribes/group/RpcMessage.java @@ -57,7 +57,8 @@ public class RpcMessage implements Externalizable { } /** - * Create a new RpcMessage. + * Create a new RpcMessage. A null identifier is serialized as a zero-length + * array and deserialized back into one. * * @param rpcId The RPC channel identifier * @param uuid The unique identifier for this message @@ -84,10 +85,14 @@ public class RpcMessage implements Externalizable { @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeBoolean(reply); - out.writeInt(uuid.length); - out.write(uuid, 0, uuid.length); - out.writeInt(rpcId.length); - out.write(rpcId, 0, rpcId.length); + out.writeInt(uuid != null ? uuid.length : 0); + if (uuid != null) { + out.write(uuid, 0, uuid.length); + } + out.writeInt(rpcId != null ? rpcId.length : 0); + if (rpcId != null) { + out.write(rpcId, 0, rpcId.length); + } out.writeObject(message); } @@ -132,10 +137,14 @@ public class RpcMessage implements Externalizable { @Override public void writeExternal(ObjectOutput out) throws IOException { - out.writeInt(uuid.length); - out.write(uuid, 0, uuid.length); - out.writeInt(rpcId.length); - out.write(rpcId, 0, rpcId.length); + out.writeInt(uuid != null ? uuid.length : 0); + if (uuid != null) { + out.write(uuid, 0, uuid.length); + } + out.writeInt(rpcId != null ? rpcId.length : 0); + if (rpcId != null) { + out.write(rpcId, 0, rpcId.length); + } } } diff --git a/test/org/apache/catalina/tribes/group/TestRpcMessage.java b/test/org/apache/catalina/tribes/group/TestRpcMessage.java new file mode 100644 index 0000000000..37e78391c0 --- /dev/null +++ b/test/org/apache/catalina/tribes/group/TestRpcMessage.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.catalina.tribes.group; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.charset.StandardCharsets; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.tribes.group.RpcMessage.NoRpcChannelReply; + +public class TestRpcMessage { + + private static Object roundTrip(Object original) throws Exception { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(bytes)) { + oos.writeObject(original); + } + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { + return ois.readObject(); + } + } + + @Test + public void testRoundTrip() throws Exception { + RpcMessage original = new RpcMessage(new byte[] { 1 }, new byte[] { 2 }, "PAYLOAD"); + RpcMessage copy = (RpcMessage) roundTrip(original); + Assert.assertArrayEquals(new byte[] { 1 }, copy.rpcId); + Assert.assertArrayEquals(new byte[] { 2 }, copy.uuid); + Assert.assertEquals("PAYLOAD", copy.message); + Assert.assertFalse(copy.reply); + } + + @Test + public void testNullArraysDoNotBreakSerialization() throws Exception { + RpcMessage original = new RpcMessage(null, null, "PAYLOAD"); + RpcMessage copy = (RpcMessage) roundTrip(original); + Assert.assertEquals(0, copy.rpcId.length); + Assert.assertEquals(0, copy.uuid.length); + Assert.assertEquals("PAYLOAD", copy.message); + } + + @Test + public void testMixedNullArraysDoNotBreakSerialization() throws Exception { + RpcMessage original = new RpcMessage(new byte[] { 1 }, null, "PAYLOAD"); + RpcMessage copy = (RpcMessage) roundTrip(original); + Assert.assertArrayEquals(new byte[] { 1 }, copy.rpcId); + Assert.assertEquals(0, copy.uuid.length); + Assert.assertEquals("PAYLOAD", copy.message); + } + + @Test + public void testNoRpcChannelReplyNullArraysDoNotBreakSerialization() throws Exception { + NoRpcChannelReply original = new NoRpcChannelReply(null, null); + NoRpcChannelReply copy = (NoRpcChannelReply) roundTrip(original); + Assert.assertEquals(0, copy.rpcId.length); + Assert.assertEquals(0, copy.uuid.length); + Assert.assertTrue(copy.reply); + } + + @Test + public void testNoRpcChannelReplyRoundTrip() throws Exception { + byte[] id = "id".getBytes(StandardCharsets.UTF_8); + NoRpcChannelReply original = new NoRpcChannelReply(id, id); + NoRpcChannelReply copy = (NoRpcChannelReply) roundTrip(original); + Assert.assertArrayEquals(id, copy.rpcId); + Assert.assertArrayEquals(id, copy.uuid); + Assert.assertTrue(copy.reply); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
