View Javadoc
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 }