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

markt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new 03a1ccf  Send WINDOW_UPDATE frames correctly when a DATA frame 
includes padding
03a1ccf is described below

commit 03a1ccfa7c3cc48bb9dd4cc134b42cbb4a306c7d
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Aug 28 13:04:43 2020 +0100

    Send WINDOW_UPDATE frames correctly when a DATA frame includes padding
    
    When a Stream was closed the window update frame was not sent for the
    connection. When zero length padding was used, an update frame was not
    sent. In both cases the result would be a smaller flow control window
    than intended.
    
    Bugs detected by Travis CI
---
 .../coyote/http2/Http2AsyncUpgradeHandler.java     | 27 ++++++++--------
 java/org/apache/coyote/http2/Http2Parser.java      |  2 +-
 .../apache/coyote/http2/Http2UpgradeHandler.java   | 27 ++++++++--------
 test/org/apache/coyote/http2/Http2TestBase.java    | 28 +++++++++++++----
 .../apache/coyote/http2/TestHttp2Section_6_1.java  | 36 +++++++++++++++++-----
 webapps/docs/changelog.xml                         | 10 ++++++
 6 files changed, 91 insertions(+), 39 deletions(-)

diff --git a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java 
b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
index 3d17d98..ff58a7e 100644
--- a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
@@ -229,23 +229,26 @@ public class Http2AsyncUpgradeHandler extends 
Http2UpgradeHandler {
     @Override
     void writeWindowUpdate(Stream stream, int increment, boolean 
applicationInitiated)
             throws IOException {
-        if (!stream.canWrite()) {
-            return;
-        }
         // Build window update frame for stream 0
         byte[] frame = new byte[13];
         ByteUtil.setThreeBytes(frame, 0,  4);
         frame[3] = FrameType.WINDOW_UPDATE.getIdByte();
         ByteUtil.set31Bits(frame, 9, increment);
-        // Change stream Id
-        byte[] frame2 = new byte[13];
-        ByteUtil.setThreeBytes(frame2, 0,  4);
-        frame2[3] = FrameType.WINDOW_UPDATE.getIdByte();
-        ByteUtil.set31Bits(frame2, 9, increment);
-        ByteUtil.set31Bits(frame2, 5, stream.getIdAsInt());
-        socketWrapper.write(BlockingMode.SEMI_BLOCK, 
protocol.getWriteTimeout(),
-                TimeUnit.MILLISECONDS, null, SocketWrapperBase.COMPLETE_WRITE, 
errorCompletion,
-                ByteBuffer.wrap(frame), ByteBuffer.wrap(frame2));
+        if (stream.canWrite()) {
+            // Change stream Id
+            byte[] frame2 = new byte[13];
+            ByteUtil.setThreeBytes(frame2, 0,  4);
+            frame2[3] = FrameType.WINDOW_UPDATE.getIdByte();
+            ByteUtil.set31Bits(frame2, 9, increment);
+            ByteUtil.set31Bits(frame2, 5, stream.getIdAsInt());
+            socketWrapper.write(BlockingMode.SEMI_BLOCK, 
protocol.getWriteTimeout(),
+                    TimeUnit.MILLISECONDS, null, 
SocketWrapperBase.COMPLETE_WRITE, errorCompletion,
+                    ByteBuffer.wrap(frame), ByteBuffer.wrap(frame2));
+        } else {
+            socketWrapper.write(BlockingMode.SEMI_BLOCK, 
protocol.getWriteTimeout(),
+                    TimeUnit.MILLISECONDS, null, 
SocketWrapperBase.COMPLETE_WRITE, errorCompletion,
+                    ByteBuffer.wrap(frame));
+        }
         handleAsyncException();
     }
 
diff --git a/java/org/apache/coyote/http2/Http2Parser.java 
b/java/org/apache/coyote/http2/Http2Parser.java
index fa828c9..6b2369b 100644
--- a/java/org/apache/coyote/http2/Http2Parser.java
+++ b/java/org/apache/coyote/http2/Http2Parser.java
@@ -207,7 +207,7 @@ class Http2Parser {
                 output.endRequestBodyFrame(streamId);
             }
         }
-        if (padLength > 0) {
+        if (Flags.hasPadding(flags)) {
             output.swallowedPadding(streamId, padLength);
         }
     }
diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java 
b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
index bb5e038..99792dc 100644
--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
@@ -795,9 +795,6 @@ class Http2UpgradeHandler extends AbstractStream implements 
InternalHttpUpgradeH
      */
     void writeWindowUpdate(Stream stream, int increment, boolean 
applicationInitiated)
             throws IOException {
-        if (!stream.canWrite()) {
-            return;
-        }
         synchronized (socketWrapper) {
             // Build window update frame for stream 0
             byte[] frame = new byte[13];
@@ -805,17 +802,21 @@ class Http2UpgradeHandler extends AbstractStream 
implements InternalHttpUpgradeH
             frame[3] = FrameType.WINDOW_UPDATE.getIdByte();
             ByteUtil.set31Bits(frame, 9, increment);
             socketWrapper.write(true, frame, 0, frame.length);
-            // Change stream Id and re-use
-            ByteUtil.set31Bits(frame, 5, stream.getIdAsInt());
-            try {
-                socketWrapper.write(true, frame, 0, frame.length);
-                socketWrapper.flush(true);
-            } catch (IOException ioe) {
-                if (applicationInitiated) {
-                    handleAppInitiatedIOException(ioe);
-                } else {
-                    throw ioe;
+            if  (stream.canWrite()) {
+                // Change stream Id and re-use
+                ByteUtil.set31Bits(frame, 5, stream.getIdAsInt());
+                try {
+                    socketWrapper.write(true, frame, 0, frame.length);
+                    socketWrapper.flush(true);
+                } catch (IOException ioe) {
+                    if (applicationInitiated) {
+                        handleAppInitiatedIOException(ioe);
+                    } else {
+                        throw ioe;
+                    }
                 }
+            } else {
+                socketWrapper.flush(true);
             }
         }
     }
diff --git a/test/org/apache/coyote/http2/Http2TestBase.java 
b/test/org/apache/coyote/http2/Http2TestBase.java
index 90785f3..167fd94 100644
--- a/test/org/apache/coyote/http2/Http2TestBase.java
+++ b/test/org/apache/coyote/http2/Http2TestBase.java
@@ -468,19 +468,35 @@ public abstract class Http2TestBase extends 
TomcatBaseTest {
 
 
     protected void readSimplePostResponse(boolean padding) throws 
Http2Exception, IOException {
-        if (padding) {
-            // Window updates for padding
-            parser.readFrame(true);
-            parser.readFrame(true);
-        }
+        /*
+         * If there is padding there will always be a window update for the
+         * connection and, depending on timing, there may be an update for the
+         * stream. The Window updates for padding (if present) may appear at 
any
+         * time. The comments in the code below are only indicative of what the
+         * frames are likely to contain. Actual frame order with padding may be
+         * different.
+         */
+
         // Connection window update after reading request body
         parser.readFrame(true);
         // Stream window update after reading request body
         parser.readFrame(true);
         // Headers
         parser.readFrame(true);
-        // Body
+        // Body (includes end of stream)
         parser.readFrame(true);
+
+        if (padding) {
+            // Connection window update for padding
+            parser.readFrame(true);
+
+            // If EndOfStream has not been received then the stream window
+            // update must have been received so a further frame needs to be
+            // read for EndOfStream.
+            if (!output.getTrace().contains("EndOfStream")) {
+                parser.readFrame(true);
+            }
+        }
     }
 
 
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_6_1.java 
b/test/org/apache/coyote/http2/TestHttp2Section_6_1.java
index 20405d4..8e3d948 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_6_1.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_6_1.java
@@ -62,14 +62,21 @@ public class TestHttp2Section_6_1 extends Http2TestBase {
             sendSimplePostRequest(3, padding);
             readSimplePostResponse(true);
 
-            // The window update for the padding could occur anywhere since it
-            // happens on a different thead to the response.
+            // The window updates for padding could occur anywhere since they
+            // happen on a different thread to the response.
+            // The connection window update is always present if there is
+            // padding.
             String trace = output.getTrace();
-            String paddingWindowUpdate = 
"0-WindowSize-[9]\n3-WindowSize-[9]\n";
-
+            String paddingWindowUpdate = "0-WindowSize-[9]\n";
             Assert.assertTrue(trace, trace.contains(paddingWindowUpdate));
             trace = trace.replace(paddingWindowUpdate, "");
 
+            // The stream window update may or may not be present depending on
+            //  timing. Remove it if present.
+            if (trace.contains("3-WindowSize-[9]\n")) {
+                trace = trace.replace("3-WindowSize-[9]\n", "");
+            }
+
             Assert.assertEquals("0-WindowSize-[119]\n" +
                     "3-WindowSize-[119]\n" +
                     "3-HeadersStart\n" +
@@ -155,8 +162,23 @@ public class TestHttp2Section_6_1 extends Http2TestBase {
         byte[] padding = new byte[0];
 
         sendSimplePostRequest(3, padding);
-        // Since padding is zero length, response looks like there is none.
-        readSimplePostResponse(false);
+        readSimplePostResponse(true);
+
+        // The window updates for padding could occur anywhere since they
+        // happen on a different thread to the response.
+        // The connection window update is always present if there is
+        // padding.
+        String trace = output.getTrace();
+        String paddingWindowUpdate = "0-WindowSize-[1]\n";
+        Assert.assertTrue(trace, trace.contains(paddingWindowUpdate));
+        trace = trace.replace(paddingWindowUpdate, "");
+
+        // The stream window update may or may not be present depending on
+        //  timing. Remove it if present.
+        paddingWindowUpdate = "3-WindowSize-[1]\n";
+        if (trace.contains(paddingWindowUpdate)) {
+            trace = trace.replace(paddingWindowUpdate, "");
+        }
 
         Assert.assertEquals("0-WindowSize-[127]\n" +
                 "3-WindowSize-[127]\n" +
@@ -166,6 +188,6 @@ public class TestHttp2Section_6_1 extends Http2TestBase {
                 "3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
                 "3-HeadersEnd\n" +
                 "3-Body-127\n" +
-                "3-EndOfStream\n", output.getTrace());
+                "3-EndOfStream\n", trace);
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 1bc6937..26d7820 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -135,6 +135,16 @@
         the stream immediately if it is waiting for an allocation when the flow
         control error occurs. (markt)
       </fix>
+      <fix>
+        Ensure that window update frames are sent for HTTP/2 connections to
+        account for DATA frames containing padding including when the 
associated
+        stream has been closed. (markt)
+      </fix>
+      <fix>
+        Ensure that window update frames are sent for HTTP/2 connections and
+        streams to account for DATA frames containing zero-length padding.
+        (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="WebSocket">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to