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());
+    }
+}

Reply via email to