View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.impl.routing;
29  
30  import java.net.InetAddress;
31  import java.util.HashSet;
32  import java.util.Set;
33  
34  import org.apache.hc.client5.http.HttpRoute;
35  import org.apache.hc.client5.http.RouteInfo.LayerType;
36  import org.apache.hc.client5.http.RouteInfo.TunnelType;
37  import org.apache.hc.client5.http.RouteTracker;
38  import org.apache.hc.client5.http.routing.HttpRouteDirector;
39  import org.apache.hc.core5.http.HttpHost;
40  import org.junit.jupiter.api.Assertions;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * Tests for {@link RouteTracker}.
45   */
46  @SuppressWarnings("boxing") // test code
47  class TestRouteTracker {
48  
49      // a selection of constants for generating routes
50      public final static HttpHost TARGET1 = new HttpHost("target1.test.invalid", 80);
51      public final static HttpHost TARGET2 = new HttpHost("target2.test.invalid", 8080);
52      // It is not necessary to have extra targets for https.
53      // The 'layered' and 'secure' flags are specified explicitly
54      // for routes, they will not be determined from the scheme.
55  
56      public final static HttpHost PROXY1 = new HttpHost("proxy1.test.invalid", 80);
57      public final static HttpHost PROXY2 = new HttpHost("proxy2.test.invalid", 1080);
58      public final static HttpHost PROXY3 = new HttpHost("proxy3.test.invalid", 88);
59  
60      public final static InetAddress LOCAL41;
61      public final static InetAddress LOCAL42;
62      public final static InetAddress LOCAL61;
63      public final static InetAddress LOCAL62;
64  
65      // need static initializer to deal with exceptions
66      static {
67          try {
68              LOCAL41 = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
69              LOCAL42 = InetAddress.getByAddress(new byte[]{127, 0, 0, 2});
70  
71              LOCAL61 = InetAddress.getByAddress(new byte[]{
72                      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
73              });
74              LOCAL62 = InetAddress.getByAddress(new byte[]{
75                      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
76              });
77  
78          } catch (final Exception x) {
79              throw new ExceptionInInitializerError(x);
80          }
81      }
82  
83      @SuppressWarnings("unused")
84      @Test
85      void testCstrTargetLocal() {
86  
87          RouteTracker rt = new RouteTracker(TARGET1, null);
88          Assertions.assertEquals(TARGET1, rt.getTargetHost(), "wrong target (target,null)");
89          Assertions.assertNull(rt.getLocalAddress(), "wrong local address (target,null)");
90          Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (target,null)");
91          Assertions.assertNull(rt.getProxyHost(), "wrong proxy (target,null)");
92          Assertions.assertNull(rt.toRoute(), "wrong route (target,null)");
93          checkCTLS(rt, false, false, false, false);
94  
95  
96          rt = new RouteTracker(TARGET2, LOCAL61);
97          Assertions.assertEquals(TARGET2, rt.getTargetHost(), "wrong target (target,local)");
98          Assertions.assertEquals(LOCAL61, rt.getLocalAddress(), "wrong local address (target,local)");
99          Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (target,local)");
100         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (target,local)");
101         Assertions.assertNull(rt.toRoute(), "wrong route (target,local)");
102         checkCTLS(rt, false, false, false, false);
103 
104         Assertions.assertThrows(NullPointerException.class, () -> new RouteTracker(null, LOCAL41));
105     }
106 
107     @SuppressWarnings("unused")
108     @Test
109     void testCstrRoute() {
110 
111         HttpRoute r = new HttpRoute(TARGET1);
112         RouteTracker rt = new RouteTracker(r);
113         Assertions.assertEquals(TARGET1, rt.getTargetHost(), "wrong target (r1)");
114         Assertions.assertNull(rt.getLocalAddress(), "wrong local address (r1)");
115         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (r1)");
116         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (r1)");
117         Assertions.assertNull(rt.toRoute(), "wrong route (r1)");
118         checkCTLS(rt, false, false, false, false);
119 
120         r = new HttpRoute(TARGET2, LOCAL61, true);
121         rt = new RouteTracker(r);
122         Assertions.assertEquals(TARGET2, rt.getTargetHost(), "wrong target (r2)");
123         Assertions.assertEquals(LOCAL61, rt.getLocalAddress(), "wrong local address (r2)");
124         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (r2)");
125         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (r2)");
126         Assertions.assertNull(rt.toRoute(), "wrong route (r2)");
127         checkCTLS(rt, false, false, false, false);
128 
129 
130         r = new HttpRoute(TARGET1, LOCAL42, PROXY3, true);
131         rt = new RouteTracker(r);
132         Assertions.assertEquals(TARGET1, rt.getTargetHost(), "wrong target (r3)");
133         Assertions.assertEquals(LOCAL42, rt.getLocalAddress(), "wrong local address (r3)");
134         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (r3)");
135         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (r3)");
136         Assertions.assertNull(rt.toRoute(), "wrong route (r3)");
137         checkCTLS(rt, false, false, false, false);
138 
139         Assertions.assertThrows(NullPointerException.class, () -> new RouteTracker(null));
140     }
141 
142     @Test
143     void testIllegalArgs() {
144 
145         final RouteTracker rt = new RouteTracker(TARGET2, null);
146 
147         Assertions.assertThrows(NullPointerException.class, () -> rt.connectProxy(null, true));
148         Assertions.assertThrows(NullPointerException.class, () -> rt.connectProxy(null, false));
149 
150         rt.connectProxy(PROXY1, false);
151 
152         Assertions.assertThrows(NullPointerException.class, () -> rt.tunnelProxy(null, false));
153         Assertions.assertThrows(NullPointerException.class, () -> rt.tunnelProxy(null, true));
154         Assertions.assertThrows(IllegalArgumentException.class, () -> rt.getHopTarget(-1));
155         Assertions.assertThrows(IllegalArgumentException.class, () -> rt.getHopTarget(2));
156     }
157 
158     @Test
159     void testIllegalStates() {
160 
161         final RouteTracker rt = new RouteTracker(TARGET1, null);
162 
163         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelTarget(false));
164         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelProxy(PROXY1, false));
165         Assertions.assertThrows(IllegalStateException.class, () -> rt.layerProtocol(true));
166 
167         // connect directly
168         rt.connectTarget(false);
169 
170         Assertions.assertThrows(IllegalStateException.class, () -> rt.connectTarget(false));
171         Assertions.assertThrows(IllegalStateException.class, () -> rt.connectProxy(PROXY2, false));
172         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelTarget(false));
173         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelProxy(PROXY1, false));
174     }
175 
176     @Test
177     void testDirectRoutes() {
178 
179         final HttpRouteDirector rd = BasicRouteDirector.INSTANCE;
180         HttpRoute r = new HttpRoute(TARGET1, LOCAL41, false);
181         RouteTracker rt = new RouteTracker(r);
182         boolean complete = checkVia(rt, r, rd, 2);
183         Assertions.assertTrue(complete, "incomplete route 1");
184 
185         r = new HttpRoute(TARGET2, LOCAL62, true);
186         rt = new RouteTracker(r);
187         complete = checkVia(rt, r, rd, 2);
188         Assertions.assertTrue(complete, "incomplete route 2");
189     }
190 
191     @Test
192     void testProxyRoutes() {
193 
194         final HttpRouteDirector rd = BasicRouteDirector.INSTANCE;
195         HttpRoute r = new HttpRoute(TARGET2, null, PROXY1, false);
196         RouteTracker rt = new RouteTracker(r);
197         boolean complete = checkVia(rt, r, rd, 2);
198         Assertions.assertTrue(complete, "incomplete route 1");
199 
200         // tunnelled, but neither secure nor layered
201         r = new HttpRoute(TARGET1, LOCAL61, PROXY3, false,
202                 TunnelType.TUNNELLED, LayerType.PLAIN);
203         rt = new RouteTracker(r);
204         complete = checkVia(rt, r, rd, 3);
205         Assertions.assertTrue(complete, "incomplete route 2");
206 
207         // tunnelled, layered, but not secure
208         r = new HttpRoute(TARGET1, LOCAL61, PROXY3, false,
209                 TunnelType.TUNNELLED, LayerType.LAYERED);
210         rt = new RouteTracker(r);
211         complete = checkVia(rt, r, rd, 4);
212         Assertions.assertTrue(complete, "incomplete route 3");
213 
214         // tunnelled, layered, secure
215         r = new HttpRoute(TARGET1, LOCAL61, PROXY3, true);
216         rt = new RouteTracker(r);
217         complete = checkVia(rt, r, rd, 4);
218         Assertions.assertTrue(complete, "incomplete route 4");
219     }
220 
221     @Test
222     void testProxyChainRoutes() {
223 
224         final HttpRouteDirector rd = BasicRouteDirector.INSTANCE;
225         HttpHost[] proxies = { PROXY1, PROXY2 };
226         HttpRoute r = new HttpRoute(TARGET2, LOCAL42, proxies, false,
227                 TunnelType.PLAIN, LayerType.PLAIN);
228         RouteTracker rt = new RouteTracker(r);
229         boolean complete = checkVia(rt, r, rd, 3);
230         Assertions.assertTrue(complete, "incomplete route 1");
231 
232         // tunnelled, but neither secure nor layered
233         proxies = new HttpHost[]{PROXY3, PROXY2};
234         r = new HttpRoute(TARGET1, null, proxies, false,
235                 TunnelType.TUNNELLED, LayerType.PLAIN);
236         rt = new RouteTracker(r);
237         complete = checkVia(rt, r, rd, 4);
238         Assertions.assertTrue(complete, "incomplete route 2");
239 
240         // tunnelled, layered, but not secure
241         proxies = new HttpHost[]{PROXY3, PROXY2, PROXY1};
242         r = new HttpRoute(TARGET2, LOCAL61, proxies, false,
243                 TunnelType.TUNNELLED, LayerType.LAYERED);
244         rt = new RouteTracker(r);
245         complete = checkVia(rt, r, rd, 6);
246         Assertions.assertTrue(complete, "incomplete route 3");
247 
248         // tunnelled, layered, secure
249         proxies = new HttpHost[]{PROXY1, PROXY3};
250         r = new HttpRoute(TARGET1, LOCAL61, proxies, true,
251                 TunnelType.TUNNELLED, LayerType.LAYERED);
252         rt = new RouteTracker(r);
253         complete = checkVia(rt, r, rd, 5);
254         Assertions.assertTrue(complete, "incomplete route 4");
255     }
256 
257     @Test
258     void testEqualsHashcodeCloneToString()
259             throws CloneNotSupportedException {
260 
261         final RouteTracker rt0 = new RouteTracker(TARGET1, null);
262         final RouteTracker rt1 = new RouteTracker(TARGET2, null);
263         final RouteTracker rt2 = new RouteTracker(TARGET1, null);
264         final RouteTracker rt3 = new RouteTracker(TARGET1, null);
265         final RouteTracker rt4 = new RouteTracker(TARGET1, LOCAL41);
266         final RouteTracker rt6 = new RouteTracker(TARGET1, LOCAL62);
267 
268         Assertions.assertNotEquals(null, rt0, "rt0");
269         Assertions.assertEquals(rt0, rt0, "rt0");
270         Assertions.assertNotEquals("rt0", rt0, rt0.toString());
271 
272         Assertions.assertNotEquals(rt0, rt4, "rt0 == rt4");
273         Assertions.assertNotEquals(rt0, rt1, "rt0 == rt1"); // Check host takes part in equals
274 
275         // Check that connection takes part in equals
276         Assertions.assertEquals(rt0, rt2, "rt0 != rt2");
277         rt2.connectTarget(false);
278         Assertions.assertNotEquals(rt0, rt2, "rt0 == rt2");
279 
280         Assertions.assertEquals(rt0, rt3, "rt0 != rt3");
281         rt3.connectTarget(true);
282         Assertions.assertNotEquals(rt0, rt3, "rt0 == rt3");
283         Assertions.assertNotEquals(rt2, rt3, "rt2 == rt3"); // Test secure takes part
284 
285         // TODO needs tests for tunnel and layered
286 
287         Assertions.assertNotEquals(rt4, rt0, "rt4 == rt0");
288         Assertions.assertNotEquals(rt0, rt6, "rt0 == rt6");
289         Assertions.assertNotEquals(rt6, rt0, "rt6 == rt0");
290         Assertions.assertNotEquals(rt4, rt6, "rt4 == rt6");
291         Assertions.assertNotEquals(rt6, rt4, "rt6 == rt4");
292 
293         // it is likely but not guaranteed that the hashcodes are different
294         Assertions.assertNotEquals(rt0.hashCode(), rt4.hashCode(), "rt0 == rt4 (hashcode)");
295         Assertions.assertNotEquals(rt0.hashCode(), rt6.hashCode(), "rt0 == rt6 (hashcode)");
296         Assertions.assertNotEquals(rt6.hashCode(), rt4.hashCode(), "rt6 == rt4 (hashcode)");
297 
298         Assertions.assertEquals(rt0, rt0.clone(), "rt0 (clone)");
299         Assertions.assertEquals(rt4, rt4.clone(), "rt4 (clone)");
300         Assertions.assertEquals(rt6, rt6.clone(), "rt6 (clone)");
301 
302 
303         // we collect (clones of) the different tracked routes along the way
304         // rt0 -> direct connection
305         // rt1 -> via single proxy
306         // rt2 -> via proxy chain
307         final Set<RouteTracker> hs = new HashSet<>();
308 
309         // we also collect hashcodes for the different paths
310         // since we can't guarantee what influence the HttpHost hashcodes have,
311         // we keep separate sets here
312         final Set<Integer> hc0 = new HashSet<>();
313         final Set<Integer> hc4 = new HashSet<>();
314         final Set<Integer> hc6 = new HashSet<>();
315 
316         RouteTracker rt = null;
317 
318         Assertions.assertTrue(hs.add(rt0));
319         Assertions.assertTrue(hs.add(rt4));
320         Assertions.assertTrue(hs.add(rt6));
321 
322         Assertions.assertTrue(hc0.add(rt0.hashCode()));
323         Assertions.assertTrue(hc4.add(rt4.hashCode()));
324         Assertions.assertTrue(hc6.add(rt6.hashCode()));
325 
326         rt = (RouteTracker) rt0.clone();
327         rt.connectTarget(false);
328         Assertions.assertTrue(hs.add(rt));
329         Assertions.assertTrue(hc0.add(rt.hashCode()));
330 
331         rt = (RouteTracker) rt0.clone();
332         rt.connectTarget(true);
333         Assertions.assertTrue(hs.add(rt));
334         Assertions.assertTrue(hc0.add(rt.hashCode()));
335 
336 
337         // proxy (insecure) -> tunnel (insecure) -> layer (secure)
338         rt = (RouteTracker) rt4.clone();
339         rt.connectProxy(PROXY1, false);
340         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
341         // this is not guaranteed to be unique...
342         Assertions.assertTrue(hc4.add(rt.hashCode()));
343 
344         rt.tunnelTarget(false);
345         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
346         Assertions.assertTrue(hc4.add(rt.hashCode()));
347 
348         rt.layerProtocol(true);
349         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
350         Assertions.assertTrue(hc4.add(rt.hashCode()));
351 
352 
353         // proxy (secure) -> tunnel (secure) -> layer (insecure)
354         rt = (RouteTracker) rt4.clone();
355         rt.connectProxy(PROXY1, true);
356         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
357         // this is not guaranteed to be unique...
358         Assertions.assertTrue(hc4.add(rt.hashCode()));
359 
360         rt.tunnelTarget(true);
361         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
362         Assertions.assertTrue(hc4.add(rt.hashCode()));
363 
364         rt.layerProtocol(false);
365         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
366         Assertions.assertTrue(hc4.add(rt.hashCode()));
367 
368 
369         // PROXY1/i -> PROXY2/i -> tunnel/i -> layer/s
370         rt = (RouteTracker) rt6.clone();
371         rt.connectProxy(PROXY1, false);
372         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
373         // this is not guaranteed to be unique...
374         Assertions.assertTrue(hc6.add(rt.hashCode()));
375 
376         rt.tunnelProxy(PROXY2, false);
377         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
378         // this is not guaranteed to be unique...
379         Assertions.assertTrue(hc6.add(rt.hashCode()));
380 
381         rt.tunnelTarget(false);
382         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
383         Assertions.assertTrue(hc6.add(rt.hashCode()));
384 
385         rt.layerProtocol(true);
386         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
387         Assertions.assertTrue(hc6.add(rt.hashCode()));
388 
389 
390         // PROXY1/s -> PROXY2/s -> tunnel/s -> layer/i
391         rt = (RouteTracker) rt6.clone();
392         rt.connectProxy(PROXY1, true);
393         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
394         // this is not guaranteed to be unique...
395         Assertions.assertTrue(hc6.add(rt.hashCode()));
396 
397         rt.tunnelProxy(PROXY2, true);
398         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
399         // this is not guaranteed to be unique...
400         Assertions.assertTrue(hc6.add(rt.hashCode()));
401 
402         rt.tunnelTarget(true);
403         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
404         Assertions.assertTrue(hc6.add(rt.hashCode()));
405 
406         rt.layerProtocol(false);
407         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
408         Assertions.assertTrue(hc6.add(rt.hashCode()));
409 
410 
411         // PROXY2/i -> PROXY1/i -> tunnel/i -> layer/s
412         rt = (RouteTracker) rt6.clone();
413         rt.connectProxy(PROXY2, false);
414         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
415         // this is not guaranteed to be unique...
416         Assertions.assertTrue(hc6.add(rt.hashCode()));
417 
418         rt.tunnelProxy(PROXY1, false);
419         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
420         // proxy chain sequence does not affect hashcode, so duplicate:
421         // Assertions.assertTrue(hc6.add(Integer.valueOf(rt.hashCode())));
422 
423         rt.tunnelTarget(false);
424         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
425         // proxy chain sequence does not affect hashcode, so duplicate:
426         // Assertions.assertTrue(hc6.add(Integer.valueOf(rt.hashCode())));
427 
428         rt.layerProtocol(true);
429         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
430         // proxy chain sequence does not affect hashcode, so duplicate:
431         // Assertions.assertTrue(hc6.add(Integer.valueOf(rt.hashCode())));
432 
433 
434         // check that all toString are OK and different
435         final Set<String> rtstrings = new HashSet<>();
436         for (final RouteTracker current : hs) {
437             final String rts = checkToString(current);
438             Assertions.assertTrue(rtstrings.add(rts), "duplicate toString: " + rts);
439         }
440     }
441 
442 
443     /** Helper to check the status of the four flags. */
444     public final static void checkCTLS(final RouteTracker rt,
445                                        final boolean c, final boolean t,
446                                        final boolean l, final boolean s) {
447         final String rts = rt.toString();
448         Assertions.assertEquals(c, rt.isConnected(), "wrong flag connected: " + rts);
449         Assertions.assertEquals(t, rt.isTunnelled(), "wrong flag tunnelled: " + rts);
450         Assertions.assertEquals(t ? TunnelType.TUNNELLED : TunnelType.PLAIN,
451                 rt.getTunnelType(), "wrong enum tunnelled: " + rts);
452         Assertions.assertEquals(l, rt.isLayered(), "wrong flag layered: " + rts);
453         Assertions.assertEquals(l ? LayerType.LAYERED : LayerType.PLAIN,
454                 rt.getLayerType(), "wrong enum layered: " + rts);
455         Assertions.assertEquals(s, rt.isSecure(), "wrong flag secure: " + rts);
456     }
457 
458 
459     /**
460      * Helper to check tracking of a route.
461      * This uses a {@link HttpRouteDirector} to fake establishing the route,
462      * checking the intermediate steps.
463      *
464      * @param rt        the tracker to check with
465      * @param r         the route to establish
466      * @param rd        the director to check with
467      * @param steps     the step count for this invocation
468      *
469      * @return  {@code true} iff the route is complete
470      */
471     public final static boolean checkVia(final RouteTracker rt, final HttpRoute r,
472                                          final HttpRouteDirector rd, final int steps) {
473 
474         final String msg = r + " @ " + rt;
475 
476         boolean complete = false;
477         int n = steps;
478         while (!complete && n > 0) {
479 
480             final int action = rd.nextStep(r, rt.toRoute());
481             switch (action) {
482 
483                 case HttpRouteDirector.COMPLETE:
484                     complete = true;
485                     Assertions.assertEquals(r, rt.toRoute());
486                     break;
487 
488                 case HttpRouteDirector.CONNECT_TARGET: {
489                     final boolean sec = r.isSecure();
490                     rt.connectTarget(sec);
491                     checkCTLS(rt, true, false, false, sec);
492                     Assertions.assertEquals(1, rt.getHopCount(), "wrong hop count " + msg);
493                     Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(0), "wrong hop0 " + msg);
494                 }
495                 break;
496 
497                 case HttpRouteDirector.CONNECT_PROXY: {
498                     // we assume an insecure proxy connection
499                     final boolean sec = false;
500                     rt.connectProxy(r.getProxyHost(), sec);
501                     checkCTLS(rt, true, false, false, sec);
502                     Assertions.assertEquals(2, rt.getHopCount(), "wrong hop count " + msg);
503                     Assertions.assertEquals(r.getProxyHost(), rt.getHopTarget(0), "wrong hop0 " + msg);
504                     Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(1), "wrong hop1 " + msg);
505                 }
506                 break;
507 
508                 case HttpRouteDirector.TUNNEL_TARGET: {
509                     final int hops = rt.getHopCount();
510                     // we assume an insecure tunnel
511                     final boolean sec = false;
512                     rt.tunnelTarget(sec);
513                     checkCTLS(rt, true, true, false, sec);
514                     Assertions.assertEquals(hops, rt.getHopCount(), "wrong hop count " + msg);
515                     Assertions.assertEquals(r.getProxyHost(), rt.getHopTarget(0), "wrong hop0 " + msg);
516                     Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(hops - 1), "wrong hopN " + msg);
517                 }
518                 break;
519 
520                 case HttpRouteDirector.TUNNEL_PROXY: {
521                     final int hops = rt.getHopCount(); // before tunnelling
522                     // we assume an insecure tunnel
523                     final boolean sec = false;
524                     final HttpHost pxy = r.getHopTarget(hops - 1);
525                     rt.tunnelProxy(pxy, sec);
526                     // Since we're tunnelling to a proxy and not the target,
527                     // the 'tunelling' flag is false: no end-to-end tunnel.
528                     checkCTLS(rt, true, false, false, sec);
529                     Assertions.assertEquals(hops + 1, rt.getHopCount(), "wrong hop count " + msg);
530                     Assertions.assertEquals(r.getProxyHost(), rt.getHopTarget(0), "wrong hop0 " + msg);
531                     Assertions.assertEquals(pxy, rt.getHopTarget(hops - 1), "wrong hop" + hops + " " + msg);
532                     Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(hops), "wrong hopN " + msg);
533                 }
534                 break;
535 
536                 case HttpRouteDirector.LAYER_PROTOCOL: {
537                     final int hops = rt.getHopCount();
538                     final boolean tun = rt.isTunnelled();
539                     final boolean sec = r.isSecure();
540                     rt.layerProtocol(sec);
541                     checkCTLS(rt, true, tun, true, sec);
542                     Assertions.assertEquals(hops, rt.getHopCount(), "wrong hop count " + msg);
543                     Assertions.assertEquals(r.getProxyHost(), rt.getProxyHost(), "wrong proxy " + msg);
544                     Assertions.assertEquals(r.getTargetHost(), rt.getTargetHost(), "wrong target " + msg);
545                 }
546                 break;
547 
548 
549                 // UNREACHABLE
550                 default:
551                     Assertions.fail("unexpected action " + action + " from director, " + msg);
552                     break;
553 
554             } // switch
555             n--;
556         }
557 
558         return complete;
559     } // checkVia
560 
561 
562     /**
563      * Checks the output of {@code toString}.
564      *
565      * @param rt        the tracker for which to check the output
566      *
567      * @return  the result of {@code rt.toString()}
568      */
569     public final static String checkToString(final RouteTracker rt) {
570         if (rt == null) {
571             return null;
572         }
573 
574         final String rts = rt.toString();
575 
576         if (rt.getLocalAddress() != null) {
577             final String las = rt.getLocalAddress().toString();
578             Assertions.assertTrue(rts.contains(las), "no local address in toString(): " + rts);
579         }
580 
581         for (int i = 0; i < rt.getHopCount(); i++) {
582             final String hts = rt.getHopTarget(i).toString();
583             Assertions.assertTrue(rts.contains(hts), "hop " + i + " (" + hts + ") missing in toString(): " + rts);
584         }
585 
586         return rts;
587     }
588 
589 }