• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20254

29 Apr 2026 05:08PM UTC coverage: 88.827% (+0.01%) from 88.814%
#20254

push

github

web-flow
xds: Trust Manager fix for when SAN validation against SNI sent doesn't apply (#12775)

Fixes a bug in propagation of `autoSniSanValidationDoesNotApply` (from
PR #12422). It added an argument `autoSniSanValidationDoesNotApply` to
`SslContextProviderSupplier.updateSslContext` that sets it on the
`DynamicSslContextProvider` but because `UpstreamTlsContext` equals
wasn't implemented, it was getting replaced by a new instance and the
flag getting lost. This issue was identified when fixing an incorrect
merge caused error in `CertProviderClientSslContextProvider` that
recreated the trust manager without consideration to
`autoSniSanValidationDoesNotApply`. It ought to have caused failure in
the test
`XdsSecurityClientServerTest.tlsClientServer_autoSniValidation_noSniApplicable_usesMatcherFromCmnVdnCtx`
but it wasn't, because even though `autoSniSanValidationDoesNotApply`
was false due to not getting the propagated true value, SAN matcher
fallback was still happening because there was no server SNI sent.

With the new changes, in addition to fixing the equals method, by moving
the decision about autoSniSanValidationDoesNotApply to
TlsContextManagerImpl.findOrCreateClientSslContextProvider I have
eliminated the need to have a deferred setting of this decision via
DynamicSslContextProvider.setAutoSniSanValidationDoesNotApply called
from SslContextProviderSupplier.updateSslContext.

Summary of Changes:

   1. Enhanced `UpstreamTlsContext` (EnvoyServerProtoData.java):
* Modified Caching Behavior: Implemented full equals() and hashCode()
overrides for `UpstreamTlsContext`. Previously, it relied on the base
class which only compared the commonTlsContext, causing different SNI or
auto-validation settings to incorrectly share the same cache entry.
* Normalization: Updated constructors to normalize the sni field to an
empty string ("") if null. This prevents equality mismatches between
context objects created from different sources (e.g., test helpers vs.
Envoy proto... (continued)

36141 of 40687 relevant lines covered (88.83%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.16
/../okhttp/src/main/java/io/grpc/okhttp/ExceptionHandlingFrameWriter.java
1
/*
2
 * Copyright 2018 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://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,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.okhttp;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.base.Preconditions;
23
import io.grpc.okhttp.internal.framed.ErrorCode;
24
import io.grpc.okhttp.internal.framed.FrameWriter;
25
import io.grpc.okhttp.internal.framed.Header;
26
import io.grpc.okhttp.internal.framed.Settings;
27
import java.io.IOException;
28
import java.util.List;
29
import java.util.logging.Level;
30
import java.util.logging.Logger;
31
import okio.Buffer;
32
import okio.ByteString;
33

34
/**
35
 * FrameWriter that propagates IOExceptions via callback instead of throwing. This allows
36
 * centralized handling of errors. Exceptions only impact the single call that throws them; callers
37
 * should be sure to kill the connection after an exception (potentially after sending a GOAWAY) as
38
 * otherwise additional frames after the failed/omitted one could cause HTTP/2 confusion.
39
 */
40
final class ExceptionHandlingFrameWriter implements FrameWriter {
41

42
  private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName());
1✔
43

44
  private final TransportExceptionHandler transportExceptionHandler;
45

46
  private final FrameWriter frameWriter;
47

48
  private final OkHttpFrameLogger frameLogger =
1✔
49
      new OkHttpFrameLogger(Level.FINE, OkHttpClientTransport.class);
50

51
  ExceptionHandlingFrameWriter(
52
      TransportExceptionHandler transportExceptionHandler, FrameWriter frameWriter) {
1✔
53
    this.transportExceptionHandler =
1✔
54
        checkNotNull(transportExceptionHandler, "transportExceptionHandler");
1✔
55
    this.frameWriter = Preconditions.checkNotNull(frameWriter, "frameWriter");
1✔
56
  }
1✔
57

58
  @Override
59
  public void connectionPreface() {
60
    try {
61
      frameWriter.connectionPreface();
1✔
62
    } catch (IOException e) {
×
63
      transportExceptionHandler.onException(e);
×
64
    }
1✔
65
  }
1✔
66

67
  @Override
68
  public void ackSettings(Settings peerSettings) {
69
    frameLogger.logSettingsAck(OkHttpFrameLogger.Direction.OUTBOUND);
1✔
70
    try {
71
      frameWriter.ackSettings(peerSettings);
1✔
72
    } catch (IOException e) {
×
73
      transportExceptionHandler.onException(e);
×
74
    }
1✔
75
  }
1✔
76

77
  @Override
78
  public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {
79
    frameLogger.logPushPromise(OkHttpFrameLogger.Direction.OUTBOUND,
1✔
80
        streamId, promisedStreamId, requestHeaders);
81
    try {
82
      frameWriter.pushPromise(streamId, promisedStreamId, requestHeaders);
1✔
83
    } catch (IOException e) {
×
84
      transportExceptionHandler.onException(e);
×
85
    }
1✔
86
  }
1✔
87

88
  @Override
89
  public void flush() {
90
    try {
91
      frameWriter.flush();
1✔
92
    } catch (IOException e) {
1✔
93
      transportExceptionHandler.onException(e);
1✔
94
    }
1✔
95
  }
1✔
96

97
  @Override
98
  public void synStream(
99
      boolean outFinished,
100
      boolean inFinished,
101
      int streamId,
102
      int associatedStreamId,
103
      List<Header> headerBlock) {
104
    try {
105
      frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, headerBlock);
1✔
106
    } catch (IOException e) {
×
107
      transportExceptionHandler.onException(e);
×
108
    }
1✔
109
  }
1✔
110

111
  @Override
112
  public void synReply(boolean outFinished, int streamId,
113
      List<Header> headerBlock) {
114
    try {
115
      frameWriter.synReply(outFinished, streamId, headerBlock);
1✔
116
    } catch (IOException e) {
1✔
117
      transportExceptionHandler.onException(e);
1✔
118
    }
1✔
119
  }
1✔
120

121
  @Override
122
  public void headers(int streamId, List<Header> headerBlock) {
123
    frameLogger.logHeaders(OkHttpFrameLogger.Direction.OUTBOUND, streamId, headerBlock, false);
1✔
124
    try {
125
      frameWriter.headers(streamId, headerBlock);
1✔
126
    } catch (IOException e) {
×
127
      transportExceptionHandler.onException(e);
×
128
    }
1✔
129
  }
1✔
130

131
  @Override
132
  public void rstStream(int streamId, ErrorCode errorCode) {
133
    frameLogger.logRstStream(OkHttpFrameLogger.Direction.OUTBOUND, streamId, errorCode);
1✔
134
    try {
135
      frameWriter.rstStream(streamId, errorCode);
1✔
136
    } catch (IOException e) {
1✔
137
      transportExceptionHandler.onException(e);
1✔
138
    }
1✔
139
  }
1✔
140

141
  @Override
142
  public int maxDataLength() {
143
    return frameWriter.maxDataLength();
1✔
144
  }
145

146
  @Override
147
  public void data(boolean outFinished, int streamId, Buffer source, int byteCount) {
148
    frameLogger.logData(OkHttpFrameLogger.Direction.OUTBOUND,
1✔
149
        streamId, source.buffer(), byteCount, outFinished);
1✔
150
    try {
151
      frameWriter.data(outFinished, streamId, source, byteCount);
1✔
152
    } catch (IOException e) {
1✔
153
      transportExceptionHandler.onException(e);
1✔
154
    }
1✔
155
  }
1✔
156

157
  @Override
158
  public void settings(Settings okHttpSettings) {
159
    frameLogger.logSettings(OkHttpFrameLogger.Direction.OUTBOUND, okHttpSettings);
1✔
160
    try {
161
      frameWriter.settings(okHttpSettings);
1✔
162
    } catch (IOException e) {
×
163
      transportExceptionHandler.onException(e);
×
164
    }
1✔
165
  }
1✔
166

167
  @Override
168
  public void ping(boolean ack, int payload1, int payload2) {
169
    if (ack) {
1✔
170
      frameLogger.logPingAck(OkHttpFrameLogger.Direction.OUTBOUND,
1✔
171
          ((long) payload1 << 32) | (payload2 & 0xFFFFFFFFL));
172
    } else {
173
      frameLogger.logPing(OkHttpFrameLogger.Direction.OUTBOUND,
1✔
174
          ((long) payload1 << 32) | (payload2 & 0xFFFFFFFFL));
175
    }
176
    try {
177
      frameWriter.ping(ack, payload1, payload2);
1✔
178
    } catch (IOException e) {
1✔
179
      transportExceptionHandler.onException(e);
1✔
180
    }
1✔
181
  }
1✔
182

183
  @Override
184
  public void goAway(int lastGoodStreamId, ErrorCode errorCode,
185
      byte[] debugData) {
186
    frameLogger.logGoAway(OkHttpFrameLogger.Direction.OUTBOUND,
1✔
187
        lastGoodStreamId, errorCode, ByteString.of(debugData));
1✔
188
    try {
189
      frameWriter.goAway(lastGoodStreamId, errorCode, debugData);
1✔
190
      // Flush it since after goAway, we are likely to close this writer.
191
      frameWriter.flush();
1✔
192
    } catch (IOException e) {
1✔
193
      transportExceptionHandler.onException(e);
1✔
194
    }
1✔
195
  }
1✔
196

197
  @Override
198
  public void windowUpdate(int streamId, long windowSizeIncrement) {
199
    frameLogger.logWindowsUpdate(OkHttpFrameLogger.Direction.OUTBOUND,
1✔
200
        streamId, windowSizeIncrement);
201
    try {
202
      frameWriter.windowUpdate(streamId, windowSizeIncrement);
1✔
203
    } catch (IOException e) {
×
204
      transportExceptionHandler.onException(e);
×
205
    }
1✔
206
  }
1✔
207

208
  @Override
209
  public void close() {
210
    try {
211
      frameWriter.close();
1✔
212
    } catch (IOException e) {
×
213
      log.log(getLogLevel(e), "Failed closing connection", e);
×
214
    }
1✔
215
  }
1✔
216

217
  /**
218
   * Accepts a throwable and returns the appropriate logging level. Uninteresting exceptions
219
   * should not clutter the log.
220
   */
221
  @VisibleForTesting
222
  static Level getLogLevel(Throwable t) {
223
    if (t.getClass().equals(IOException.class)) {
1✔
224
      return Level.FINE;
1✔
225
    }
226
    return Level.INFO;
1✔
227
  }
228

229
  /** A class that handles transport exception. */
230
  interface TransportExceptionHandler {
231
    /** Handles exception. */
232
    void onException(Throwable throwable);
233
  }
234
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc