This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new a0244bb4ba91 CAMEL-26903: camel-hl7 - HL7MLLPDecoder no longer fails
on an end byte 1 right before the start byte (#29894)
a0244bb4ba91 is described below
commit a0244bb4ba912cc5929d79df6251b66dad16086c
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Nov 14 15:11:07 2026 +0530
CAMEL-26903: camel-hl7 - HL7MLLPDecoder no longer fails on an end byte 1
right before the start byte (#29894)
HL7MLLPDecoder (the MINA codec of camel-hl7) keeps the previous byte to
find the end bytes. A start byte did not reset it, so an end byte 1
right before the start byte, followed by an end byte 2 right after it,
was taken as the end of a message with a negative length, and the
decoder failed with an IllegalArgumentException (or a
NegativeArraySizeException with produceString=false), closing the
sender's session.
The previous byte is now reset to -1 when a start byte starts a message,
and its initial value and other resets use -1 too, so an end byte 1 of
0x00 is not matched after a reset either. Tests in the new
HL7MLLPDecoderEndBytesBeforeStartTest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/component/hl7/HL7MLLPDecoder.java | 14 ++-
.../hl7/HL7MLLPDecoderEndBytesBeforeStartTest.java | 119 +++++++++++++++++++++
2 files changed, 129 insertions(+), 4 deletions(-)
diff --git
a/components/camel-hl7/src/main/java/org/apache/camel/component/hl7/HL7MLLPDecoder.java
b/components/camel-hl7/src/main/java/org/apache/camel/component/hl7/HL7MLLPDecoder.java
index a3dfc7844b5b..b92c69097a15 100644
---
a/components/camel-hl7/src/main/java/org/apache/camel/component/hl7/HL7MLLPDecoder.java
+++
b/components/camel-hl7/src/main/java/org/apache/camel/component/hl7/HL7MLLPDecoder.java
@@ -36,6 +36,8 @@ class HL7MLLPDecoder extends CumulativeProtocolDecoder {
private static final Logger LOG =
LoggerFactory.getLogger(HL7MLLPDecoder.class);
private static final String DECODER_STATE = HL7MLLPDecoder.class.getName()
+ ".STATE";
private static final String CHARSET_DECODER =
HL7MLLPDecoder.class.getName() + ".charsetdecoder";
+ // the previous byte when there is none that could be an end byte 1 (the
bytes are compared unsigned, 0 to 0xFF)
+ private static final int NO_PREVIOUS_BYTE = -1;
private HL7MLLPConfig config;
@@ -113,6 +115,10 @@ class HL7MLLPDecoder extends CumulativeProtocolDecoder {
}
// Check if we are at the start of an HL7 message
if (current == config.getStartByte()) {
+ if (!state.isStarted()) {
+ // an end byte 1 before the start byte does not end
the message with the end byte 2 after it
+ state.markPrevious(NO_PREVIOUS_BYTE);
+ }
state.markStart(previousPosition);
} else {
// Remember previous byte in state object because the
buffer could
@@ -151,7 +157,7 @@ class HL7MLLPDecoder extends CumulativeProtocolDecoder {
if (current == config.getEndByte2() && state.previous() ==
config.getEndByte1()) {
LOG.debug("End of the discarded message at position {}",
in.position() - 1);
state.stopDiscarding();
- state.markPrevious(0);
+ state.markPrevious(NO_PREVIOUS_BYTE);
state.markDataStart(in.position());
return true;
}
@@ -276,8 +282,8 @@ class HL7MLLPDecoder extends CumulativeProtocolDecoder {
private static class DecoderState {
private int startPos = -1;
private int currentPos;
- // unsigned (0 to 0xFF)
- private int previousByte;
+ // unsigned (0 to 0xFF), or NO_PREVIOUS_BYTE
+ private int previousByte = NO_PREVIOUS_BYTE;
// where the data of the current message starts in the buffer: after
the end bytes of the previous message
private int dataStartPos;
// whether the rest of a message that was too long is being skipped
@@ -286,7 +292,7 @@ class HL7MLLPDecoder extends CumulativeProtocolDecoder {
void reset() {
startPos = -1;
currentPos = 0;
- previousByte = 0;
+ previousByte = NO_PREVIOUS_BYTE;
}
void markStart(int position) {
diff --git
a/components/camel-hl7/src/test/java/org/apache/camel/component/hl7/HL7MLLPDecoderEndBytesBeforeStartTest.java
b/components/camel-hl7/src/test/java/org/apache/camel/component/hl7/HL7MLLPDecoderEndBytesBeforeStartTest.java
new file mode 100644
index 000000000000..78f329623cd2
--- /dev/null
+++
b/components/camel-hl7/src/test/java/org/apache/camel/component/hl7/HL7MLLPDecoderEndBytesBeforeStartTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.camel.component.hl7;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.filter.codec.ProtocolCodecSession;
+import org.apache.mina.filter.codec.ProtocolDecoder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * End bytes that the HL7 MLLP MINA decoder reads before a start byte: they do
not end the message that starts after
+ * them.
+ */
+class HL7MLLPDecoderEndBytesBeforeStartTest {
+
+ private static final String START = "\u000b";
+ private static final String END = "\u001c\r";
+
+ private final ProtocolCodecSession session = new ProtocolCodecSession();
+
+ @Test
+ void endByte1RightBeforeTheStartByte() throws Exception {
+ ProtocolDecoder decoder = decoder(true);
+
+ // the end byte 2 (a segment separator) right after the start byte
does not end the message
+ decode(decoder, "\u001c" + START + "\rMSH|A" + END);
+
+ assertEquals("\rMSH|A", session.getDecoderOutputQueue().poll());
+ assertNull(session.getDecoderOutputQueue().poll());
+ }
+
+ @Test
+ void endBytesWithoutStartBeforeTheStartByte() throws Exception {
+ ProtocolDecoder decoder = decoder(true);
+
+ decode(decoder, END + START + "\rMSH|A" + END);
+ decode(decoder, START + "MSH|B" + END);
+
+ assertEquals("\rMSH|A", session.getDecoderOutputQueue().poll());
+ assertEquals("MSH|B", session.getDecoderOutputQueue().poll());
+ assertNull(session.getDecoderOutputQueue().poll());
+ }
+
+ @Test
+ void endBytesWithoutStartBeforeTheStartByteAsBytes() throws Exception {
+ ProtocolDecoder decoder = decoder(false);
+
+ decode(decoder, END + START + "\rMSH|A" + END);
+
+ assertArrayEquals("\rMSH|A".getBytes(StandardCharsets.ISO_8859_1),
(byte[]) session.getDecoderOutputQueue().poll());
+ assertNull(session.getDecoderOutputQueue().poll());
+ }
+
+ @Test
+ void endBytesWithoutStartBeforeAMessage() throws Exception {
+ ProtocolDecoder decoder = decoder(true);
+
+ decode(decoder, END + START + "MSH|A" + END);
+
+ assertEquals("MSH|A", session.getDecoderOutputQueue().poll());
+ assertNull(session.getDecoderOutputQueue().poll());
+ }
+
+ @Test
+ void endByte1BeforeTheStartByteInAnotherRead() throws Exception {
+ ProtocolDecoder decoder = decoder(true);
+
+ decode(decoder, "\u001c");
+ decode(decoder, START + "\rMSH|A" + END);
+
+ assertEquals("\rMSH|A", session.getDecoderOutputQueue().poll());
+ assertNull(session.getDecoderOutputQueue().poll());
+ }
+
+ @Test
+ void endByte1Zero() throws Exception {
+ // the decoder starts with no previous byte, also when the end byte 1
is 0x00
+ HL7MLLPConfig config = new HL7MLLPConfig();
+ config.setCharset(StandardCharsets.ISO_8859_1);
+ config.setEndByte1('\u0000');
+ ProtocolDecoder decoder = new HL7MLLPDecoder(config);
+
+ decode(decoder, START + "\rMSH|A" + "\u0000\r");
+
+ assertEquals("\rMSH|A", session.getDecoderOutputQueue().poll());
+ assertNull(session.getDecoderOutputQueue().poll());
+ }
+
+ private static ProtocolDecoder decoder(boolean produceString) {
+ HL7MLLPConfig config = new HL7MLLPConfig();
+ config.setCharset(StandardCharsets.ISO_8859_1);
+ config.setProduceString(produceString);
+ return new HL7MLLPDecoder(config);
+ }
+
+ private void decode(ProtocolDecoder decoder, String data) throws Exception
{
+ decoder.decode(session,
IoBuffer.wrap(data.getBytes(StandardCharsets.ISO_8859_1)),
session.getDecoderOutput());
+ }
+}