View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http3;
17  
18  import io.netty.channel.ChannelHandlerContext;
19  import io.netty.channel.ChannelOutboundHandlerAdapter;
20  import io.netty.channel.ChannelPromise;
21  import io.netty.handler.codec.http.HttpStatusClass;
22  import org.jetbrains.annotations.Nullable;
23  
24  import static io.netty.handler.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected;
25  
26  final class Http3RequestStreamEncodeStateValidator extends ChannelOutboundHandlerAdapter
27          implements Http3RequestStreamCodecState {
28      enum State {
29          None,
30          Headers,
31          FinalHeaders,
32          Trailers
33      }
34      private State state = State.None;
35  
36      @Override
37      public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
38          if (!(msg instanceof Http3RequestStreamFrame)) {
39              super.write(ctx, msg, promise);
40              return;
41          }
42          final Http3RequestStreamFrame frame = (Http3RequestStreamFrame) msg;
43          final State nextState = evaluateFrame(state, frame);
44          if (nextState == null) {
45              frameTypeUnexpected(ctx, msg);
46              return;
47          }
48          state = nextState;
49          super.write(ctx, msg, promise);
50      }
51  
52      @Override
53      public boolean started() {
54          return isStreamStarted(state);
55      }
56  
57      @Override
58      public boolean receivedFinalHeaders() {
59          return isFinalHeadersReceived(state);
60      }
61  
62      @Override
63      public boolean terminated() {
64          return isTrailersReceived(state);
65      }
66  
67      /**
68       * Evaluates the passed frame and returns the following:
69       * <ul>
70       *     <li>Modified {@link State} if the state should be changed.</li>
71       *     <li>Same {@link State} as the passed {@code state} if no state change is necessary</li>
72       *     <li>{@code null} if the frame is unexpected</li>
73       * </ul>
74       *
75       * @param state Current state.
76       * @param frame to evaluate.
77       * @return Next {@link State} or {@code null} if the frame is invalid.
78       */
79      @Nullable
80      static State evaluateFrame(State state, Http3RequestStreamFrame frame) {
81          if (frame instanceof Http3PushPromiseFrame || frame instanceof Http3UnknownFrame) {
82              // always allow push promise frames.
83              return state;
84          }
85          switch (state) {
86              case None:
87              case Headers:
88                  if (!(frame instanceof Http3HeadersFrame)) {
89                      return null;
90                  }
91                  return isInformationalResponse((Http3HeadersFrame) frame) ? State.Headers : State.FinalHeaders;
92              case FinalHeaders:
93                  if (frame instanceof Http3HeadersFrame) {
94                      if (isInformationalResponse((Http3HeadersFrame) frame)) {
95                          // Information response after final response headers
96                          return null;
97                      }
98                      // trailers
99                      return State.Trailers;
100                 }
101                 return state;
102             case Trailers:
103                 return null;
104             default:
105                 throw new Error();
106         }
107     }
108 
109     static boolean isStreamStarted(State state) {
110         return state != State.None;
111     }
112 
113     static boolean isFinalHeadersReceived(State state) {
114         return isStreamStarted(state) && state != State.Headers;
115     }
116 
117     static boolean isTrailersReceived(State state) {
118         return state == State.Trailers;
119     }
120 
121     private static boolean isInformationalResponse(Http3HeadersFrame headersFrame) {
122         return HttpStatusClass.valueOf(headersFrame.headers().status()) == HttpStatusClass.INFORMATIONAL;
123     }
124 }