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  public class TestRouteTracker {
48  
49      // a selection of constants for generating routes
50      public final static
51          HttpHost TARGET1 = new HttpHost("target1.test.invalid", 80);
52      public final static
53          HttpHost TARGET2 = new HttpHost("target2.test.invalid", 8080);
54      // It is not necessary to have extra targets for https.
55      // The 'layered' and 'secure' flags are specified explicitly
56      // for routes, they will not be determined from the scheme.
57  
58      public final static
59          HttpHost PROXY1 = new HttpHost("proxy1.test.invalid", 80);
60      public final static
61          HttpHost PROXY2 = new HttpHost("proxy2.test.invalid", 1080);
62      public final static
63          HttpHost PROXY3 = new HttpHost("proxy3.test.invalid", 88);
64  
65      public final static InetAddress LOCAL41;
66      public final static InetAddress LOCAL42;
67      public final static InetAddress LOCAL61;
68      public final static InetAddress LOCAL62;
69  
70      // need static initializer to deal with exceptions
71      static {
72          try {
73              LOCAL41 = InetAddress.getByAddress(new byte[]{ 127, 0, 0, 1 });
74              LOCAL42 = InetAddress.getByAddress(new byte[]{ 127, 0, 0, 2 });
75  
76              LOCAL61 = InetAddress.getByAddress(new byte[]{
77                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
78              });
79              LOCAL62 = InetAddress.getByAddress(new byte[]{
80                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
81              });
82  
83          } catch (final Exception x) {
84              throw new ExceptionInInitializerError(x);
85          }
86      }
87  
88      @SuppressWarnings("unused")
89      @Test
90      public void testCstrTargetLocal() {
91  
92          RouteTracker rt = new RouteTracker(TARGET1, null);
93          Assertions.assertEquals(TARGET1, rt.getTargetHost(), "wrong target (target,null)");
94          Assertions.assertNull(rt.getLocalAddress(), "wrong local address (target,null)");
95          Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (target,null)");
96          Assertions.assertNull(rt.getProxyHost(), "wrong proxy (target,null)");
97          Assertions.assertNull(rt.toRoute(), "wrong route (target,null)");
98          checkCTLS(rt, false, false, false, false);
99  
100 
101         rt = new RouteTracker(TARGET2, LOCAL61);
102         Assertions.assertEquals(TARGET2, rt.getTargetHost(), "wrong target (target,local)");
103         Assertions.assertEquals(LOCAL61, rt.getLocalAddress(), "wrong local address (target,local)");
104         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (target,local)");
105         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (target,local)");
106         Assertions.assertNull(rt.toRoute(), "wrong route (target,local)");
107         checkCTLS(rt, false, false, false, false);
108 
109         Assertions.assertThrows(NullPointerException.class, () -> new RouteTracker(null, LOCAL41));
110     }
111 
112     @SuppressWarnings("unused")
113     @Test
114     public void testCstrRoute() {
115 
116         HttpRoute r  = new HttpRoute(TARGET1);
117         RouteTracker rt = new RouteTracker(r);
118         Assertions.assertEquals(TARGET1, rt.getTargetHost(), "wrong target (r1)");
119         Assertions.assertNull(rt.getLocalAddress(), "wrong local address (r1)");
120         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (r1)");
121         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (r1)");
122         Assertions.assertNull(rt.toRoute(), "wrong route (r1)");
123         checkCTLS(rt, false, false, false, false);
124 
125         r  = new HttpRoute(TARGET2, LOCAL61, true);
126         rt = new RouteTracker(r);
127         Assertions.assertEquals(TARGET2, rt.getTargetHost(), "wrong target (r2)");
128         Assertions.assertEquals(LOCAL61, rt.getLocalAddress(), "wrong local address (r2)");
129         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (r2)");
130         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (r2)");
131         Assertions.assertNull(rt.toRoute(), "wrong route (r2)");
132         checkCTLS(rt, false, false, false, false);
133 
134 
135         r  = new HttpRoute(TARGET1, LOCAL42, PROXY3, true);
136         rt = new RouteTracker(r);
137         Assertions.assertEquals(TARGET1, rt.getTargetHost(), "wrong target (r3)");
138         Assertions.assertEquals(LOCAL42, rt.getLocalAddress(), "wrong local address (r3)");
139         Assertions.assertEquals(0, rt.getHopCount(), "wrong hop count (r3)");
140         Assertions.assertNull(rt.getProxyHost(), "wrong proxy (r3)");
141         Assertions.assertNull(rt.toRoute(), "wrong route (r3)");
142         checkCTLS(rt, false, false, false, false);
143 
144         Assertions.assertThrows(NullPointerException.class, () -> new RouteTracker(null));
145     }
146 
147     @Test
148     public void testIllegalArgs() {
149 
150         final RouteTracker rt = new RouteTracker(TARGET2, null);
151 
152         Assertions.assertThrows(NullPointerException.class, () -> rt.connectProxy(null, true));
153         Assertions.assertThrows(NullPointerException.class, () -> rt.connectProxy(null, false));
154 
155         rt.connectProxy(PROXY1, false);
156 
157         Assertions.assertThrows(NullPointerException.class, () -> rt.tunnelProxy(null, false));
158         Assertions.assertThrows(NullPointerException.class, () -> rt.tunnelProxy(null, true));
159         Assertions.assertThrows(IllegalArgumentException.class, () -> rt.getHopTarget(-1));
160         Assertions.assertThrows(IllegalArgumentException.class, () -> rt.getHopTarget(2));
161     }
162 
163     @Test
164     public void testIllegalStates() {
165 
166         final RouteTracker rt = new RouteTracker(TARGET1, null);
167 
168         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelTarget(false));
169         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelProxy(PROXY1, false));
170         Assertions.assertThrows(IllegalStateException.class, () -> rt.layerProtocol(true));
171 
172         // connect directly
173         rt.connectTarget(false);
174 
175         Assertions.assertThrows(IllegalStateException.class, () -> rt.connectTarget(false));
176         Assertions.assertThrows(IllegalStateException.class, () -> rt.connectProxy(PROXY2, false));
177         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelTarget(false));
178         Assertions.assertThrows(IllegalStateException.class, () -> rt.tunnelProxy(PROXY1, false));
179     }
180 
181     @Test
182     public void testDirectRoutes() {
183 
184         final HttpRouteDirector rd = BasicRouteDirector.INSTANCE;
185         HttpRoute r = new HttpRoute(TARGET1, LOCAL41, false);
186         RouteTracker rt = new RouteTracker(r);
187         boolean complete = checkVia(rt, r, rd, 2);
188         Assertions.assertTrue(complete, "incomplete route 1");
189 
190         r = new HttpRoute(TARGET2, LOCAL62, true);
191         rt = new RouteTracker(r);
192         complete = checkVia(rt, r, rd, 2);
193         Assertions.assertTrue(complete, "incomplete route 2");
194     }
195 
196     @Test
197     public void testProxyRoutes() {
198 
199         final HttpRouteDirector rd = BasicRouteDirector.INSTANCE;
200         HttpRoute r = new HttpRoute(TARGET2, null, PROXY1, false);
201         RouteTracker rt = new RouteTracker(r);
202         boolean complete = checkVia(rt, r, rd, 2);
203         Assertions.assertTrue(complete, "incomplete route 1");
204 
205         // tunnelled, but neither secure nor layered
206         r = new HttpRoute(TARGET1, LOCAL61, PROXY3, false,
207                           TunnelType.TUNNELLED, LayerType.PLAIN);
208         rt = new RouteTracker(r);
209         complete = checkVia(rt, r, rd, 3);
210         Assertions.assertTrue(complete, "incomplete route 2");
211 
212         // tunnelled, layered, but not secure
213         r = new HttpRoute(TARGET1, LOCAL61, PROXY3, false,
214                           TunnelType.TUNNELLED, LayerType.LAYERED);
215         rt = new RouteTracker(r);
216         complete = checkVia(rt, r, rd, 4);
217         Assertions.assertTrue(complete, "incomplete route 3");
218 
219         // tunnelled, layered, secure
220         r = new HttpRoute(TARGET1, LOCAL61, PROXY3, true);
221         rt = new RouteTracker(r);
222         complete = checkVia(rt, r, rd, 4);
223         Assertions.assertTrue(complete, "incomplete route 4");
224     }
225 
226     @Test
227     public void testProxyChainRoutes() {
228 
229         final HttpRouteDirector rd = BasicRouteDirector.INSTANCE;
230         HttpHost[] proxies = { PROXY1, PROXY2 };
231         HttpRoute r = new HttpRoute(TARGET2, LOCAL42, proxies, false,
232                                     TunnelType.PLAIN, LayerType.PLAIN);
233         RouteTracker rt = new RouteTracker(r);
234         boolean complete = checkVia(rt, r, rd, 3);
235         Assertions.assertTrue(complete, "incomplete route 1");
236 
237         // tunnelled, but neither secure nor layered
238         proxies = new HttpHost[]{ PROXY3, PROXY2 };
239         r = new HttpRoute(TARGET1, null, proxies, false,
240                           TunnelType.TUNNELLED, LayerType.PLAIN);
241         rt = new RouteTracker(r);
242         complete = checkVia(rt, r, rd, 4);
243         Assertions.assertTrue(complete, "incomplete route 2");
244 
245         // tunnelled, layered, but not secure
246         proxies = new HttpHost[]{ PROXY3, PROXY2, PROXY1 };
247         r = new HttpRoute(TARGET2, LOCAL61, proxies, false,
248                           TunnelType.TUNNELLED, LayerType.LAYERED);
249         rt = new RouteTracker(r);
250         complete = checkVia(rt, r, rd, 6);
251         Assertions.assertTrue(complete, "incomplete route 3");
252 
253         // tunnelled, layered, secure
254         proxies = new HttpHost[]{ PROXY1, PROXY3 };
255         r = new HttpRoute(TARGET1, LOCAL61, proxies, true,
256                           TunnelType.TUNNELLED, LayerType.LAYERED);
257         rt = new RouteTracker(r);
258         complete = checkVia(rt, r, rd, 5);
259         Assertions.assertTrue(complete, "incomplete route 4");
260     }
261 
262     @Test
263     public void testEqualsHashcodeCloneToString()
264         throws CloneNotSupportedException {
265 
266         final RouteTracker rt0 = new RouteTracker(TARGET1, null);
267         final RouteTracker rt1 = new RouteTracker(TARGET2, null);
268         final RouteTracker rt2 = new RouteTracker(TARGET1, null);
269         final RouteTracker rt3 = new RouteTracker(TARGET1, null);
270         final RouteTracker rt4 = new RouteTracker(TARGET1, LOCAL41);
271         final RouteTracker rt6 = new RouteTracker(TARGET1, LOCAL62);
272 
273         Assertions.assertNotEquals(null, rt0, "rt0");
274         Assertions.assertEquals(rt0, rt0, "rt0");
275         Assertions.assertNotEquals("rt0", rt0, rt0.toString());
276 
277         Assertions.assertNotEquals(rt0, rt4, "rt0 == rt4");
278         Assertions.assertNotEquals(rt0, rt1, "rt0 == rt1"); // Check host takes part in equals
279 
280         // Check that connection takes part in equals
281         Assertions.assertEquals(rt0, rt2, "rt0 != rt2");
282         rt2.connectTarget(false);
283         Assertions.assertNotEquals(rt0, rt2, "rt0 == rt2");
284 
285         Assertions.assertEquals(rt0, rt3, "rt0 != rt3");
286         rt3.connectTarget(true);
287         Assertions.assertNotEquals(rt0, rt3, "rt0 == rt3");
288         Assertions.assertNotEquals(rt2, rt3, "rt2 == rt3"); // Test secure takes part
289 
290         // TODO needs tests for tunnel and layered
291 
292         Assertions.assertNotEquals(rt4, rt0, "rt4 == rt0");
293         Assertions.assertNotEquals(rt0, rt6, "rt0 == rt6");
294         Assertions.assertNotEquals(rt6, rt0, "rt6 == rt0");
295         Assertions.assertNotEquals(rt4, rt6, "rt4 == rt6");
296         Assertions.assertNotEquals(rt6, rt4, "rt6 == rt4");
297 
298         // it is likely but not guaranteed that the hashcodes are different
299         Assertions.assertNotEquals(rt0.hashCode(), rt4.hashCode(), "rt0 == rt4 (hashcode)");
300         Assertions.assertNotEquals(rt0.hashCode(), rt6.hashCode(), "rt0 == rt6 (hashcode)");
301         Assertions.assertNotEquals(rt6.hashCode(), rt4.hashCode(), "rt6 == rt4 (hashcode)");
302 
303         Assertions.assertEquals(rt0, rt0.clone(), "rt0 (clone)");
304         Assertions.assertEquals(rt4, rt4.clone(), "rt4 (clone)");
305         Assertions.assertEquals(rt6, rt6.clone(), "rt6 (clone)");
306 
307 
308         // we collect (clones of) the different tracked routes along the way
309         // rt0 -> direct connection
310         // rt1 -> via single proxy
311         // rt2 -> via proxy chain
312         final Set<RouteTracker> hs = new HashSet<>();
313 
314         // we also collect hashcodes for the different paths
315         // since we can't guarantee what influence the HttpHost hashcodes have,
316         // we keep separate sets here
317         final Set<Integer> hc0 = new HashSet<>();
318         final Set<Integer> hc4 = new HashSet<>();
319         final Set<Integer> hc6 = new HashSet<>();
320 
321         RouteTracker rt = null;
322 
323         Assertions.assertTrue(hs.add(rt0));
324         Assertions.assertTrue(hs.add(rt4));
325         Assertions.assertTrue(hs.add(rt6));
326 
327         Assertions.assertTrue(hc0.add(rt0.hashCode()));
328         Assertions.assertTrue(hc4.add(rt4.hashCode()));
329         Assertions.assertTrue(hc6.add(rt6.hashCode()));
330 
331         rt = (RouteTracker) rt0.clone();
332         rt.connectTarget(false);
333         Assertions.assertTrue(hs.add(rt));
334         Assertions.assertTrue(hc0.add(rt.hashCode()));
335 
336         rt = (RouteTracker) rt0.clone();
337         rt.connectTarget(true);
338         Assertions.assertTrue(hs.add(rt));
339         Assertions.assertTrue(hc0.add(rt.hashCode()));
340 
341 
342         // proxy (insecure) -> tunnel (insecure) -> layer (secure)
343         rt = (RouteTracker) rt4.clone();
344         rt.connectProxy(PROXY1, false);
345         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
346         // this is not guaranteed to be unique...
347         Assertions.assertTrue(hc4.add(rt.hashCode()));
348 
349         rt.tunnelTarget(false);
350         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
351         Assertions.assertTrue(hc4.add(rt.hashCode()));
352 
353         rt.layerProtocol(true);
354         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
355         Assertions.assertTrue(hc4.add(rt.hashCode()));
356 
357 
358         // proxy (secure) -> tunnel (secure) -> layer (insecure)
359         rt = (RouteTracker) rt4.clone();
360         rt.connectProxy(PROXY1, true);
361         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
362         // this is not guaranteed to be unique...
363         Assertions.assertTrue(hc4.add(rt.hashCode()));
364 
365         rt.tunnelTarget(true);
366         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
367         Assertions.assertTrue(hc4.add(rt.hashCode()));
368 
369         rt.layerProtocol(false);
370         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
371         Assertions.assertTrue(hc4.add(rt.hashCode()));
372 
373 
374         // PROXY1/i -> PROXY2/i -> tunnel/i -> layer/s
375         rt = (RouteTracker) rt6.clone();
376         rt.connectProxy(PROXY1, 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.tunnelProxy(PROXY2, false);
382         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
383         // this is not guaranteed to be unique...
384         Assertions.assertTrue(hc6.add(rt.hashCode()));
385 
386         rt.tunnelTarget(false);
387         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
388         Assertions.assertTrue(hc6.add(rt.hashCode()));
389 
390         rt.layerProtocol(true);
391         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
392         Assertions.assertTrue(hc6.add(rt.hashCode()));
393 
394 
395         // PROXY1/s -> PROXY2/s -> tunnel/s -> layer/i
396         rt = (RouteTracker) rt6.clone();
397         rt.connectProxy(PROXY1, 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.tunnelProxy(PROXY2, true);
403         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
404         // this is not guaranteed to be unique...
405         Assertions.assertTrue(hc6.add(rt.hashCode()));
406 
407         rt.tunnelTarget(true);
408         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
409         Assertions.assertTrue(hc6.add(rt.hashCode()));
410 
411         rt.layerProtocol(false);
412         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
413         Assertions.assertTrue(hc6.add(rt.hashCode()));
414 
415 
416         // PROXY2/i -> PROXY1/i -> tunnel/i -> layer/s
417         rt = (RouteTracker) rt6.clone();
418         rt.connectProxy(PROXY2, false);
419         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
420         // this is not guaranteed to be unique...
421         Assertions.assertTrue(hc6.add(rt.hashCode()));
422 
423         rt.tunnelProxy(PROXY1, 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.tunnelTarget(false);
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         rt.layerProtocol(true);
434         Assertions.assertTrue(hs.add((RouteTracker) rt.clone()));
435         // proxy chain sequence does not affect hashcode, so duplicate:
436         // Assertions.assertTrue(hc6.add(Integer.valueOf(rt.hashCode())));
437 
438 
439         // check that all toString are OK and different
440         final Set<String> rtstrings = new HashSet<>();
441         for (final RouteTracker current: hs) {
442             final String rts = checkToString(current);
443             Assertions.assertTrue(rtstrings.add(rts), "duplicate toString: " + rts);
444         }
445     }
446 
447 
448     /** Helper to check the status of the four flags. */
449     public final static void checkCTLS(final RouteTracker rt,
450                                        final boolean c, final boolean t,
451                                        final boolean l, final boolean s) {
452         final String rts = rt.toString();
453         Assertions.assertEquals(c, rt.isConnected(), "wrong flag connected: " + rts);
454         Assertions.assertEquals(t, rt.isTunnelled(), "wrong flag tunnelled: " + rts);
455         Assertions.assertEquals(t ? TunnelType.TUNNELLED : TunnelType.PLAIN,
456                      rt.getTunnelType(), "wrong enum tunnelled: " + rts);
457         Assertions.assertEquals(l, rt.isLayered(), "wrong flag layered: "   + rts);
458         Assertions.assertEquals(l ? LayerType.LAYERED : LayerType.PLAIN,
459                      rt.getLayerType(), "wrong enum layered: "   + rts);
460         Assertions.assertEquals(s, rt.isSecure(), "wrong flag secure: " + rts);
461     }
462 
463 
464     /**
465      * Helper to check tracking of a route.
466      * This uses a {@link HttpRouteDirector} to fake establishing the route,
467      * checking the intermediate steps.
468      *
469      * @param rt        the tracker to check with
470      * @param r         the route to establish
471      * @param rd        the director to check with
472      * @param steps     the step count for this invocation
473      *
474      * @return  {@code true} iff the route is complete
475      */
476     public final static boolean checkVia(final RouteTracker rt, final HttpRoute r,
477                                          final HttpRouteDirector rd, final int steps) {
478 
479         final String msg = r + " @ " + rt;
480 
481         boolean complete = false;
482         int n = steps;
483         while (!complete && (n > 0)) {
484 
485             final int action = rd.nextStep(r, rt.toRoute());
486             switch (action) {
487 
488             case HttpRouteDirector.COMPLETE:
489                 complete = true;
490                 Assertions.assertEquals(r, rt.toRoute());
491                 break;
492 
493             case HttpRouteDirector.CONNECT_TARGET: {
494                 final boolean sec = r.isSecure();
495                 rt.connectTarget(sec);
496                 checkCTLS(rt, true, false, false, sec);
497                 Assertions.assertEquals(1, rt.getHopCount(), "wrong hop count "+msg);
498                 Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(0), "wrong hop0 "+msg);
499             } break;
500 
501             case HttpRouteDirector.CONNECT_PROXY: {
502                 // we assume an insecure proxy connection
503                 final boolean sec = false;
504                 rt.connectProxy(r.getProxyHost(), sec);
505                 checkCTLS(rt, true, false, false, sec);
506                 Assertions.assertEquals(2, rt.getHopCount(), "wrong hop count "+msg);
507                 Assertions.assertEquals(r.getProxyHost(), rt.getHopTarget(0), "wrong hop0 "+msg);
508                 Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(1), "wrong hop1 "+msg);
509             } break;
510 
511             case HttpRouteDirector.TUNNEL_TARGET: {
512                 final int hops = rt.getHopCount();
513                 // we assume an insecure tunnel
514                 final boolean sec = false;
515                 rt.tunnelTarget(sec);
516                 checkCTLS(rt, true, true, false, sec);
517                 Assertions.assertEquals(hops, rt.getHopCount(), "wrong hop count "+msg);
518                 Assertions.assertEquals(r.getProxyHost(), rt.getHopTarget(0), "wrong hop0 "+msg);
519                 Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(hops-1), "wrong hopN "+msg);
520             } break;
521 
522             case HttpRouteDirector.TUNNEL_PROXY: {
523                 final int hops = rt.getHopCount(); // before tunnelling
524                 // we assume an insecure tunnel
525                 final boolean  sec = false;
526                 final HttpHost pxy = r.getHopTarget(hops-1);
527                 rt.tunnelProxy(pxy, sec);
528                 // Since we're tunnelling to a proxy and not the target,
529                 // the 'tunelling' flag is false: no end-to-end tunnel.
530                 checkCTLS(rt, true, false, false, sec);
531                 Assertions.assertEquals(hops+1, rt.getHopCount(), "wrong hop count "+msg);
532                 Assertions.assertEquals(r.getProxyHost(), rt.getHopTarget(0), "wrong hop0 "+msg);
533                 Assertions.assertEquals(pxy, rt.getHopTarget(hops-1), "wrong hop"+hops+" "+msg);
534                 Assertions.assertEquals(r.getTargetHost(), rt.getHopTarget(hops), "wrong hopN "+msg);
535             } break;
536 
537             case HttpRouteDirector.LAYER_PROTOCOL: {
538                 final int    hops = rt.getHopCount();
539                 final boolean tun = rt.isTunnelled();
540                 final boolean sec = r.isSecure();
541                 rt.layerProtocol(sec);
542                 checkCTLS(rt, true, tun, true, sec);
543                 Assertions.assertEquals(hops, rt.getHopCount(), "wrong hop count "+msg);
544                 Assertions.assertEquals(r.getProxyHost(), rt.getProxyHost(), "wrong proxy "+msg);
545                 Assertions.assertEquals(r.getTargetHost(), rt.getTargetHost(), "wrong target "+msg);
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 }