1 /*
2 * Copyright 2020 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.quic;
17
18 import io.netty.buffer.ByteBuf;
19
20 import java.net.InetSocketAddress;
21
22 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
23
24 /**
25 * Handle token related operations.
26 */
27 public interface QuicTokenHandler {
28
29 /**
30 * The result of a token validation.
31 */
32 final class TokenValidationResult {
33 private static final TokenValidationResult INVALID_TOKEN = new TokenValidationResult(-1, false);
34 private static final TokenValidationResult ODCID_FROM_DESTINATION_CONNECTION_ID =
35 new TokenValidationResult(-1, true);
36
37 private final int tokenOffset;
38 private final boolean odcidFromDestinationConnectionId;
39
40 private TokenValidationResult(int tokenOffset, boolean odcidFromDestinationConnectionId) {
41 this.tokenOffset = tokenOffset;
42 this.odcidFromDestinationConnectionId = odcidFromDestinationConnectionId;
43 }
44
45 /**
46 * Returns a result that indicates the token is invalid.
47 *
48 * @return the invalid token result.
49 */
50 public static TokenValidationResult invalidToken() {
51 return INVALID_TOKEN;
52 }
53
54 /**
55 * Returns a result that indicates the token is valid and the ODCID should be taken from the token suffix
56 * starting at the specified offset.
57 * <p>
58 * This is typically used for tokens from Retry packets; see
59 * {@link QuicTokenHandler#validateToken(ByteBuf, InetSocketAddress, ByteBuf)}.
60 *
61 * @param offset the start index of the ODCID in the token.
62 * @return the validation result.
63 */
64 public static TokenValidationResult odcidFromToken(int offset) {
65 return new TokenValidationResult(checkPositiveOrZero(offset, "offset"), false);
66 }
67
68 /**
69 * Returns a result that indicates the token is valid and the ODCID should be taken from the current
70 * destination connection id of the Initial packet.
71 * <p>
72 * This is typically used for tokens from NEW_TOKEN frames; see
73 * {@link QuicTokenHandler#validateToken(ByteBuf, InetSocketAddress, ByteBuf)}.
74 *
75 * @return the validation result.
76 */
77 public static TokenValidationResult odcidFromDestinationConnectionId() {
78 return ODCID_FROM_DESTINATION_CONNECTION_ID;
79 }
80
81 /**
82 * Returns {@code true} if the token is valid.
83 *
84 * @return {@code true} if the token is valid.
85 */
86 public boolean isValid() {
87 return this != INVALID_TOKEN;
88 }
89
90 ByteBuf originalDestinationConnectionId(ByteBuf token, ByteBuf dcid) {
91 if (!isValid()) {
92 throw new IllegalStateException("token is not valid");
93 }
94 if (odcidFromDestinationConnectionId) {
95 return dcid.slice();
96 }
97 if (tokenOffset > token.readableBytes()) {
98 throw new IllegalArgumentException("offset " + tokenOffset + " exceeds token length "
99 + token.readableBytes());
100 }
101 return token.slice(tokenOffset, token.readableBytes() - tokenOffset);
102 }
103 }
104
105 /**
106 * Generate a new token for the given destination connection id and address. This token is written to {@code out}.
107 * If no token should be generated and so no token validation should take place at all this method should return
108 * {@code false}.
109 *
110 * @param out {@link ByteBuf} into which the token will be written.
111 * @param dcid the destination connection id. The {@link ByteBuf#readableBytes()} will be at most
112 * {@link Quic#MAX_CONN_ID_LEN}.
113 * @param address the {@link InetSocketAddress} of the sender.
114 * @return {@code true} if a token was written and so validation should happen, {@code false} otherwise.
115 */
116 boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address);
117
118 /**
119 * Validate the token and return the offset, {@code -1} is returned if the token is not valid. The returned offset
120 * identifies where the ODCID starts in the token. Implementations that support tokens from NEW_TOKEN frames should
121 * override {@link #validateToken(ByteBuf, InetSocketAddress, ByteBuf)}.
122 *
123 * @param token the {@link ByteBuf} that contains the token. The caller retains ownership of the buffer:
124 * implementations must not release it and must retain, duplicate or copy it before using it after
125 * this method returns.
126 * @param address the {@link InetSocketAddress} of the sender.
127 * @return the start index after the token or {@code -1} if the token was not valid.
128 */
129 int validateToken(ByteBuf token, InetSocketAddress address);
130
131 /**
132 * Validate the token and return a structured result that determines how the ODCID should be derived.
133 * <p>
134 * RFC 9000 distinguishes tokens sent in Retry packets from tokens sent in NEW_TOKEN frames, and requires token
135 * construction to let the server identify how the token was provided to the client; see RFC 9000 Sections 8.1.1,
136 * 8.1.2 and 8.1.3.
137 * <p>
138 * A token from a Retry packet, as described in RFC 9000 Section 8.1.2 and carried by the Retry packet in
139 * Section 17.2.5, validates the same connection attempt after the server selected a new connection id. In this
140 * case the result should identify the original destination connection id from the client's first Initial packet.
141 * Use {@link TokenValidationResult#odcidFromToken(int)} if the token stores that connection id as a suffix, which
142 * is the convention used by the legacy {@link #validateToken(ByteBuf, InetSocketAddress)} method.
143 * <p>
144 * A token from a NEW_TOKEN frame, as described in RFC 9000 Section 8.1.3, validates a future connection attempt.
145 * No Retry packet has been sent for that new attempt, so the original destination connection id is the destination
146 * connection id of the current Initial packet as described in RFC 9000 Section 7.2. Use
147 * {@link TokenValidationResult#odcidFromDestinationConnectionId()} for this case.
148 *
149 * @param token the {@link ByteBuf} that contains the token. The caller retains ownership of the buffer:
150 * implementations must not release it and must retain, duplicate or copy it before using it after
151 * this method returns.
152 * @param address the {@link InetSocketAddress} of the sender.
153 * @param dcid the destination connection id of the current Initial packet. The caller retains ownership of the
154 * buffer: implementations must not release it and must retain, duplicate or copy it before using
155 * it after this method returns.
156 * @return the validation result.
157 */
158 default TokenValidationResult validateToken(ByteBuf token, InetSocketAddress address, ByteBuf dcid) {
159 int offset = validateToken(token, address);
160 return offset == -1 ? TokenValidationResult.invalidToken() : TokenValidationResult.odcidFromToken(offset);
161 }
162
163 /**
164 * Return the maximal token length.
165 *
166 * @return the maximal supported token length.
167 */
168 int maxTokenLength();
169 }