1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http3;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandler;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelPromise;
22 import io.netty.channel.socket.ChannelInputShutdownEvent;
23 import io.netty.util.ReferenceCountUtil;
24 import io.netty.util.internal.ObjectUtil;
25 import org.jetbrains.annotations.Nullable;
26
27 import static io.netty.handler.codec.http3.Http3CodecUtils.closeOnFailure;
28
29 final class Http3ControlStreamOutboundHandler
30 extends Http3FrameTypeDuplexValidationHandler<Http3ControlStreamFrame> {
31 private final boolean server;
32 private final ChannelHandler codec;
33 private Long sentMaxPushId;
34 private Long sendGoAwayId;
35 private Http3SettingsFrame localSettings;
36
37 Http3ControlStreamOutboundHandler(boolean server, Http3SettingsFrame localSettings, ChannelHandler codec) {
38 super(Http3ControlStreamFrame.class);
39 this.server = server;
40 this.localSettings = ObjectUtil.checkNotNull(localSettings, "localSettings");
41 this.codec = ObjectUtil.checkNotNull(codec, "codec");
42 }
43
44
45
46
47
48
49 @Nullable
50 Long sentMaxPushId() {
51 return sentMaxPushId;
52 }
53
54 @Override
55 public void channelActive(ChannelHandlerContext ctx) {
56
57
58
59 ByteBuf buffer = ctx.alloc().buffer(8);
60 Http3CodecUtils.writeVariableLengthInteger(buffer, Http3CodecUtils.HTTP3_CONTROL_STREAM_TYPE);
61 ctx.write(buffer);
62
63
64 ctx.pipeline().addFirst(codec);
65
66 assert localSettings != null;
67
68 closeOnFailure(ctx.writeAndFlush(localSettings));
69
70
71 localSettings = null;
72
73 ctx.fireChannelActive();
74 }
75
76 @Override
77 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
78 if (evt instanceof ChannelInputShutdownEvent) {
79
80 Http3CodecUtils.criticalStreamClosed(ctx);
81 }
82 ctx.fireUserEventTriggered(evt);
83 }
84
85 @Override
86 public void channelInactive(ChannelHandlerContext ctx) {
87
88 Http3CodecUtils.criticalStreamClosed(ctx);
89 ctx.fireChannelInactive();
90 }
91
92 @Override
93 void write(ChannelHandlerContext ctx, Http3ControlStreamFrame msg, ChannelPromise promise) {
94 if (msg instanceof Http3MaxPushIdFrame && !handleHttp3MaxPushIdFrame(promise, (Http3MaxPushIdFrame) msg)) {
95 ReferenceCountUtil.release(msg);
96 return;
97 } else if (msg instanceof Http3GoAwayFrame && !handleHttp3GoAwayFrame(promise, (Http3GoAwayFrame) msg)) {
98 ReferenceCountUtil.release(msg);
99 return;
100 }
101
102 ctx.write(msg, promise);
103 }
104
105 private boolean handleHttp3MaxPushIdFrame(ChannelPromise promise, Http3MaxPushIdFrame maxPushIdFrame) {
106 long id = maxPushIdFrame.id();
107
108
109 if (sentMaxPushId != null && id < sentMaxPushId) {
110 promise.setFailure(new Http3Exception(Http3ErrorCode.H3_ID_ERROR, "MAX_PUSH_ID reduced limit."));
111 return false;
112 }
113
114 sentMaxPushId = maxPushIdFrame.id();
115 return true;
116 }
117
118 private boolean handleHttp3GoAwayFrame(ChannelPromise promise, Http3GoAwayFrame goAwayFrame) {
119 long id = goAwayFrame.id();
120
121
122 if (server && id % 4 != 0) {
123 promise.setFailure(new Http3Exception(Http3ErrorCode.H3_ID_ERROR,
124 "GOAWAY id not valid : " + id));
125 return false;
126 }
127
128 if (sendGoAwayId != null && id > sendGoAwayId) {
129 promise.setFailure(new Http3Exception(Http3ErrorCode.H3_ID_ERROR,
130 "GOAWAY id is bigger then the last sent: " + id + " > " + sendGoAwayId));
131 return false;
132 }
133
134 sendGoAwayId = id;
135 return true;
136 }
137
138 @Override
139 public boolean isSharable() {
140
141 return false;
142 }
143 }