This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch 2.2.X
in repository https://gitbox.apache.org/repos/asf/mina.git


The following commit(s) were added to refs/heads/2.2.X by this push:
     new ec995d1cc Don't replay already consumed data when an error is thrown 
while decoding data
ec995d1cc is described below

commit ec995d1ccf538b693ff51a157df2d2a59ad38a41
Author: Emmanuel Lécharny <[email protected]>
AuthorDate: Tue Sep 8 15:19:46 2026 +0200

    Don't replay already consumed data when an error is thrown while
    decoding data
---
 .../filter/codec/CumulativeProtocolDecoder.java    | 39 +++++++++----
 .../codec/CumulativeProtocolDecoderTest.java       | 68 ++++++++++++++++++++++
 2 files changed, 96 insertions(+), 11 deletions(-)

diff --git 
a/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java
 
b/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java
index 7eddfc0fc..2a4530645 100644
--- 
a/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java
+++ 
b/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java
@@ -172,20 +172,37 @@ public abstract class CumulativeProtocolDecoder extends 
ProtocolDecoderAdapter {
             usingSessionBuffer = false;
         }
 
-        for (;;) {
-            int oldPos = buf.position();
-            boolean decoded = doDecode(session, buf, out);
-            if (decoded) {
-                if (buf.position() == oldPos) {
-                    throw new IllegalStateException("doDecode() can't return 
true when buffer is not consumed.");
-                }
-
-                if (!buf.hasRemaining()) {
+        try { 
+            for (;;) {
+                int oldPos = buf.position();
+                boolean decoded = doDecode(session, buf, out);
+                if (decoded) {
+                    if (buf.position() == oldPos) {
+                        throw new IllegalStateException("doDecode() can't 
return true when buffer is not consumed.");
+                    }
+    
+                    if (!buf.hasRemaining()) {
+                        break;
+                    }
+                } else {
                     break;
                 }
-            } else {
-                break;
             }
+        } catch (Exception | Error e) {
+            // doDecode() threw: the cumulative buffer is still flipped in
+            // read mode, with the messages decoded (and delivered) by the
+            // earlier iterations before its position. If we left it stored
+            // in the session, the next decode() call would append new data
+            // at the current position and flip the buffer again, re-reading
+            // - and re-delivering to the handler - those already-consumed
+            // messages. A decoding error may cost the accumulated remainder,
+            // but it must never replay consumed input, so discard the buffer
+            // before propagating the exception.
+            if (usingSessionBuffer) {
+                removeSessionBuffer(session);
+            }
+
+            throw e;
         }
 
         // if there is any data left that cannot be decoded, we store
diff --git 
a/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java
 
b/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java
index 055b892b4..602a3c0f2 100644
--- 
a/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java
+++ 
b/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java
@@ -143,6 +143,49 @@ public class CumulativeProtocolDecoderTest {
         }
     }
 
+    @Test
+    public void testDecoderExceptionDiscardsSessionBuffer() throws Exception {
+        FaultyIntegerDecoder faultyDecoder = new FaultyIntegerDecoder();
+
+        // First chunk: an incomplete integer, stored in the session buffer.
+        buf.putShort((short) 0);
+        buf.flip();
+        faultyDecoder.decode(session, buf, session.getDecoderOutput());
+        assertEquals(0, session.getDecoderOutputQueue().size());
+
+        // Second chunk: completes integer 1 (decoded and delivered), then
+        // the poisoned integer 0xBAD that makes doDecode() throw after the
+        // first message was already delivered.
+        buf.clear();
+        buf.putShort((short) 1);
+        buf.putInt(0xBAD);
+        buf.flip();
+
+        try {
+            faultyDecoder.decode(session, buf, session.getDecoderOutput());
+            fail("The poisoned integer should have made the decoder throw");
+        } catch (ProtocolDecoderException e) {
+            // OK
+        }
+
+        assertEquals(1, session.getDecoderOutputQueue().size());
+        assertEquals(1, session.getDecoderOutputQueue().poll());
+
+        // Further data must NOT re-deliver integer 1: the cumulative buffer
+        // was left flipped by the exception and must have been discarded,
+        // not replayed on the next decode.
+        buf.clear();
+        buf.putInt(7);
+        buf.flip();
+        faultyDecoder.decode(session, buf, session.getDecoderOutput());
+
+        assertEquals(1, session.getDecoderOutputQueue().size());
+        assertEquals(7, session.getDecoderOutputQueue().poll());
+
+        faultyDecoder.dispose(session);
+    }
+
+
     private static class IntegerDecoder extends CumulativeProtocolDecoder {
         /**
          * Default constructor
@@ -164,6 +207,31 @@ public class CumulativeProtocolDecoderTest {
         }
     }
 
+    private static class FaultyIntegerDecoder extends 
CumulativeProtocolDecoder {
+        /**
+         * Default constructor
+         */
+        public FaultyIntegerDecoder() {
+            super();
+        }
+
+        @Override
+        protected boolean doDecode(IoSession session, IoBuffer in, 
ProtocolDecoderOutput out) throws Exception {
+            if (in.remaining() < 4) {
+                return false;
+            }
+
+            int value = in.getInt();
+
+            if (value == 0xBAD) {
+                throw new ProtocolDecoderException("poisoned integer");
+            }
+
+            out.write(value);
+            return true;
+        }
+    }
+
     private static class WrongDecoder extends CumulativeProtocolDecoder {
         /**
          * Default constructor

Reply via email to