Telepath   F
last analyzed

Complexity

Total Complexity 80

Size/Duplication

Total Lines 512
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 80
eloc 240
c 0
b 0
f 0
dl 0
loc 512
rs 2

9 Methods

Rating   Name   Duplication   Size   Complexity  
D magic() 0 85 18
A determineTestsuiteParameters() 0 21 5
B CATInternalTests() 0 62 10
A checkFlrServerStatus() 0 26 6
B checkFedEtlrUplink() 0 33 7
D genericAPIStatus() 0 80 20
A checkEtlrStatus() 0 25 6
A checkNROFlow() 0 3 1
B __construct() 0 26 7

How to fix   Complexity   

Complex Class

Complex classes like Telepath often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Telepath, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
namespace core\diag;
24
25
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
27
/**
28
 * The overall coordination class that runs all kinds of tests to find out where
29
 * and what is wrong. Operates on the realm of a user. Can do more magic if it
30
 * also knows which federation the user is currently positioned in, or even 
31
 * which exact hotspot to analyse.
32
 */
33
class Telepath extends AbstractTest
34
{
35
36
    /**
37
     * the realm we are testing
38
     * 
39
     * @var string
40
     */
41
    private $realm;
42
43
    /**
44
     * the federation where the user currently is
45
     * 
46
     * @var string|NULL
47
     */
48
    private $visitedFlr;
49
50
    /**
51
     * the identifier of the hotspot where the user currently is
52
     * 
53
     * @var string|NULL
54
     */
55
    private $visitedHotspot;
56
57
    /**
58
     * the CAT profile to which the realm belongs, if any
59
     * 
60
     * @var integer
61
     */
62
    private $catProfile;
63
64
    /**
65
     * the identifier of the associated IdP in the external DB, if any
66
     * 
67
     * @var string
68
     */
69
    private $dbIdP;
70
71
    /**
72
     * the federation to which the realm belongs; can be NULL if we can't infer
73
     * from domain ending nor find it in the DB
74
     * 
75
     * @var string|NULL
76
     */
77
    private $idPFederation;
78
79
    /**
80
     * instance of the RADIUSTests suite we use to meditate with
81
     * 
82
     * @var \core\diag\RADIUSTests
83
     */
84
    private $testsuite;
85
86
    /**
87
     * prime the Telepath with info it needs to know to successfully meditate over the problem
88
     * @param string      $realm          the realm of the user
89
     * @param string|null $visitedFlr     which NRO is the user visiting
90
     * @param string|null $visitedHotspot external DB ID of the hotspot he visited
91
     */
92
    public function __construct(string $realm, $visitedFlr = NULL, $visitedHotspot = NULL)
93
    {
94
        // Telepath is the first one in a chain, no previous inputs allowed
95
        if (isset($_SESSION) && isset($_SESSION["SUSPECTS"])) {
96
            unset($_SESSION["SUSPECTS"]);
97
        }
98
        if (isset($_SESSION) && isset($_SESSION["EVIDENCE"])) {
99
            unset($_SESSION["EVIDENCE"]);
100
        }
101
        // now fill with default values
102
        parent::__construct();
103
        $this->realm = $realm;
104
        $this->additionalFindings['REALM'] = $this->realm;
105
        $this->visitedFlr = $visitedFlr;
106
        $this->visitedHotspot = $visitedHotspot;
107
        $links = \core\Federation::determineIdPIdByRealm($realm);
108
        $this->catProfile = $links["CAT"];
109
        $this->dbIdP = $links["EXTERNAL"];
110
        $this->idPFederation = $links["FEDERATION"];
111
        // this is NULL if the realm is not known in either DB
112
        // if so, let's try a regex to extract the ccTLD if any
113
        $matches = [];
114
        if ($this->idPFederation === NULL && preg_match("/\.(..)$/", $realm, $matches)) {
115
            $this->idPFederation = strtoupper($matches[1]);
116
        }
117
        $this->loggerInstance->debug(4, "XYZ: IdP-side NRO is " . $this->idPFederation . "\n");
118
    }
119
    /* The eduroam OT monitoring has the following return codes:
120
     * 
121
122
      Status codes
123
124
      0 - O.K.
125
      -1 - Accept O.K. Reject No
126
      -2 - Reject O.K. Accept No
127
      -3 - Accept No Reject No
128
      -9 - system error
129
      -10 - Accept O.K. Reject timeou
130
      -11 - Accept O.K. Reject no EAP
131
      -20 - Reject O.K. Accept timeou
132
      -21 - Reject O.K. Accept no EAP
133
      -31 - Accept No  Reject timeou
134
      -32 - Accept Timeout Reject no
135
      -33 - Accept Timeout Reject timeou
136
      -35 - Accept No Reject no EAP
137
      -36 - Reject No Accept no EAP
138
      -37 - Reject No EAP Accept no EAP
139
      -40 - UDP test error
140
141
     */
142
143
    /**
144
     * ask the monitoring API about the things it knows
145
     * 
146
     * @param string $type   which type of test to execute
147
     * @param string $param1 test-specific parameter number 1, if any
148
     * @param string $param2 test-specific parameter number 2, if any
149
     * @return array
150
     */
151
    private function genericAPIStatus($type, $param1 = NULL, $param2 = NULL)
152
    {
153
        $endpoints = [
154
            'tlr_test' => "https://monitor.eduroam.org/mapi/index.php?type=tlr_test&tlr=$param1",
155
            'federation_via_tlr' => "https://monitor.eduroam.org/mapi/index.php?type=federation_via_tlr&federation=$param1",
156
            'flrs_test' => "https://monitor.eduroam.org/mapi/index.php?type=flrs_test&federation=$param1",
157
            'flr_by_federation' => "https://monitor.eduroam.org/mapi/index.php?type=flr_by_federation&federation=$param2&with=$param1",
158
        ];
159
        $ignore = [
160
            'tlr_test' => 'tlr',
161
            'federation_via_tlr' => 'fed',
162
            'flrs_test' => 'fed',
163
            'flr_by_federation' => 'fed',
164
        ];
165
        $this->loggerInstance->debug(4, "Doing Monitoring API check with $endpoints[$type]\n");
166
        $jsonResult = \core\common\OutsideComm::downloadFile($endpoints[$type]);
167
        $this->loggerInstance->debug(4, "Monitoring API Result: $jsonResult\n");
168
        $retval = [];
169
        if ($jsonResult === FALSE) { // monitoring API didn't respond at all!
170
            $retval["STATUS"] = AbstractTest::STATUS_MONITORINGFAIL;
171
            return $retval;
172
        }
173
        $decoded = json_decode($jsonResult, TRUE);
174
        $retval["RAW"] = $decoded;
175
        $atLeastOneFunctional = FALSE;
176
        $allFunctional = TRUE;
177
        if (!isset($decoded[$type]) || isset($decoded['ERROR'])) {
178
            $retval["STATUS"] = AbstractTest::STATUS_MONITORINGFAIL;
179
            return $retval;
180
        }
181
        foreach ($decoded[$type] as $instance => $resultset) {
182
            if ($instance == $ignore[$type]) {
183
                // don't care
184
                continue;
185
            }
186
            // TLR test has statuscode on this level, otherwise need to recurse
187
            // one more level
188
            switch ($type) {
189
                case "tlr_test":
190
                    switch ($resultset['status_code']) {
191
                        case 0:
192
                            $atLeastOneFunctional = TRUE;
193
                            break;
194
                        case 9: // monitoring itself has an error, no effect on our verdict
195
                        case -1: // Reject test fails, but we diagnose supposed-working connection, so no effect on our verdict
196
                        case -10: // same as previous
197
                        case -11: // same as previous
198
                            break;
199
                        default:
200
                            $allFunctional = FALSE;
201
                    }
202
                    break;
203
                default:
204
                    foreach ($resultset as $particularInstance => $particularResultset) {
205
                        switch ($particularResultset['status_code']) {
206
                            case 0:
207
                                $atLeastOneFunctional = TRUE;
208
                                break;
209
                            case 9: // monitoring itself has an error, no effect on our verdict
210
                            case -1: // Reject test fails, but we diagnose supposed-working connection, so no effect on our verdict
211
                            case -10: // same as previous
212
                            case -11: // same as previous
213
                                break;
214
                            default:
215
                                $allFunctional = FALSE;
216
                        }
217
                    }
218
            }
219
        }
220
221
        if ($allFunctional) {
222
            $retval["STATUS"] = AbstractTest::STATUS_GOOD;
223
            return $retval;
224
        }
225
        if ($atLeastOneFunctional) {
226
            $retval["STATUS"] = AbstractTest::STATUS_PARTIAL;
227
            return $retval;
228
        }
229
        $retval["STATUS"] = AbstractTest::STATUS_DOWN;
230
        return $retval;
231
    }
232
233
    /**
234
     * Are the ETLR servers in order?
235
     * @return array
236
     */
237
    private function checkEtlrStatus()
238
    {
239
        // TODO: we always check the European TLRs even though the connection in question might go via others and/or this one
240
        // needs a table to determine what goes where :-(
241
        $ret = $this->genericAPIStatus("tlr_test", "TLR_EU");
242
        $this->additionalFindings[AbstractTest::INFRA_ETLR][] = $ret;
243
        switch ($ret["STATUS"]) {
244
            case AbstractTest::STATUS_GOOD:
245
                unset($this->possibleFailureReasons[AbstractTest::INFRA_ETLR]);
246
                break;
247
            case AbstractTest::STATUS_PARTIAL:
248
            case AbstractTest::STATUS_MONITORINGFAIL:
249
                // one of the ETLRs is down, or there is a failure in the monitoring system? 
250
                // This probably doesn't impact the user unless he's unlucky and has his session fall into failover.
251
                // keep ETLR as a possible problem with original probability
252
                break;
253
            case AbstractTest::STATUS_DOWN:
254
                // Oh! Well if it is not international roaming, that still doesn't have an effect /in this case/. 
255
                if ($this->idPFederation == $this->visitedFlr) {
256
                    unset($this->possibleFailureReasons[AbstractTest::INFRA_ETLR]);
257
                    break;
258
                }
259
                // But it is about int'l roaming, and we are spot on here.
260
                // Raise probability by much (even monitoring is sometimes wrong, or a few minutes behind reality)
261
                $this->possibleFailureReasons[AbstractTest::INFRA_ETLR] = 0.95;
262
        }
263
    }
264
265
    /**
266
     * Is the uplink between an NRO server and the ETLRs in order?
267
     * @param string $whichSide test towards the IdP or SP side?
268
     * @return array
269
     * @throws Exception
270
     */
271
    private function checkFedEtlrUplink($whichSide)
272
    {
273
        // TODO: we always check the European TLRs even though the connection in question might go via others and/or this one
274
        // needs a table to determine what goes where :-(
275
        switch ($whichSide) {
276
            case AbstractTest::INFRA_NRO_IDP:
277
                $fed = $this->idPFederation;
278
                $linkVariant = AbstractTest::INFRA_LINK_ETLR_NRO_IDP;
279
                break;
280
            case AbstractTest::INFRA_NRO_SP:
281
                $fed = $this->visitedFlr;
282
                $linkVariant = AbstractTest::INFRA_LINK_ETLR_NRO_SP;
283
                break;
284
            default:
285
                throw new Exception("This function operates on the IdP- or SP-side FLR, nothing else!");
286
        }
287
288
        $ret = $this->genericAPIStatus("federation_via_tlr", $fed);
289
        $this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = $ret;
290
        switch ($ret["STATUS"]) {
291
            case AbstractTest::STATUS_GOOD:
292
                unset($this->possibleFailureReasons[$whichSide]);
293
                unset($this->possibleFailureReasons[$linkVariant]);
294
                break;
295
            case AbstractTest::STATUS_PARTIAL:
296
                // a subset of the FLRs is down? This probably doesn't impact the user unless he's unlucky and has his session fall into failover.
297
                // keep FLR as a possible problem with original probability
298
                break;
299
            case AbstractTest::STATUS_DOWN:
300
                // Raise probability by much (even monitoring is sometimes wrong, or a few minutes behind reality)
301
                // if earlier test found the server itself to be the problem, keep it, otherwise put the blame on the link
302
                if ($this->possibleFailureReasons[$whichSide] != 0.95) {
303
                    $this->possibleFailureReasons[$linkVariant] = 0.95;
304
                }
305
        }
306
    }
307
308
    /**
309
     * Is the NRO server itself in order?
310
     * @param string $whichSide test towards the IdP or SP side?
311
     * @return array
312
     * @throws Exception
313
     */
314
    private function checkFlrServerStatus($whichSide)
315
    {
316
        switch ($whichSide) {
317
            case AbstractTest::INFRA_NRO_IDP:
318
                $fed = $this->idPFederation;
319
                break;
320
            case AbstractTest::INFRA_NRO_SP:
321
                $fed = $this->visitedFlr;
322
                break;
323
            default:
324
                throw new Exception("This function operates on the IdP- or SP-side FLR, nothing else!");
325
        }
326
327
        $ret = $this->genericAPIStatus("flrs_test", $fed);
328
        $this->additionalFindings[$whichSide][] = $ret;
329
        switch ($ret["STATUS"]) {
330
            case AbstractTest::STATUS_GOOD:
331
                unset($this->possibleFailureReasons[$whichSide]);
332
                break;
333
            case AbstractTest::STATUS_PARTIAL:
334
                // a subset of the FLRs is down? This probably doesn't impact the user unless he's unlucky and has his session fall into failover.
335
                // keep FLR as a possible problem with original probability
336
                break;
337
            case AbstractTest::STATUS_DOWN:
338
                // Raise probability by much (even monitoring is sometimes wrong, or a few minutes behind reality)
339
                $this->possibleFailureReasons[$whichSide] = 0.95;
340
        }
341
    }
342
343
    /**
344
     * Does authentication traffic flow between a given source and destination NRO?
345
     * @return array
346
     */
347
    private function checkNROFlow()
348
    {
349
        return $this->genericAPIStatus("flr_by_federation", $this->idPFederation, $this->visitedFlr);
350
    }
351
352
    /**
353
     * Runs the CAT-internal diagnostics tests. Determines the state of the 
354
     * realm (and indirectly that of the links and statuses of involved proxies
355
     * and returns a judgment whether external Monitoring API tests are warranted
356
     * or not
357
     * @return boolean TRUE if external tests have to be run
358
     */
359
    private function CATInternalTests()
360
    {
361
        // we are expecting to get a REJECT from all runs, because that means the packet got through to the IdP.
362
        // (the ETLR sometimes does a "Reject instead of Ignore" but that is filtered out and changed into a timeout
363
        // by the test suite automatically, so it does not disturb the measurement)
364
        // If that's true, we can exclude two sources of problems (both proxy levels). Hooray!
365
        $allAreConversationReject = TRUE;
366
        $atLeastOneConversationReject = FALSE;
367
368
        foreach (\config\Diagnostics::RADIUSTESTS['UDP-hosts'] as $probeindex => $probe) {
369
            $reachCheck = $this->testsuite->udpReachability($probeindex);
370
            if ($reachCheck != RADIUSTests::RETVAL_CONVERSATION_REJECT) {
371
                $allAreConversationReject = FALSE;
372
            } else {
373
                $atLeastOneConversationReject = TRUE;
374
            }
375
376
            $this->additionalFindings[AbstractTest::INFRA_ETLR][] = ["DETAIL" => $this->testsuite->consolidateUdpResult($probeindex)];
377
            $this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = ["DETAIL" => $this->testsuite->consolidateUdpResult($probeindex)];
378
            $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["DETAIL" => $this->testsuite->consolidateUdpResult($probeindex)];
379
        }
380
381
        if ($allAreConversationReject) {
382
            $this->additionalFindings[AbstractTest::INFRA_ETLR][] = ["CONNCHECK" => RADIUSTests::RETVAL_CONVERSATION_REJECT];
383
            $this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = ["CONNCHECK" => RADIUSTests::RETVAL_CONVERSATION_REJECT];
384
            $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["CONNCHECK" => RADIUSTests::RETVAL_CONVERSATION_REJECT];
385
            $this->additionalFindings[AbstractTest::INFRA_LINK_ETLR_NRO_IDP][] = ["LINKCHECK" => RADIUSTests::L_OK];
386
            // we have actually reached an IdP, so all links are good, and the
387
            // realm is routable in eduroam. So even if it exists in neither DB
388
            // we can exclude the NONEXISTENTREALM case
389
            unset($this->possibleFailureReasons[AbstractTest::INFRA_ETLR]);
390
            unset($this->possibleFailureReasons[AbstractTest::INFRA_NRO_IDP]);
391
            unset($this->possibleFailureReasons[AbstractTest::INFRA_LINK_ETLR_NRO_IDP]);
392
            unset($this->possibleFailureReasons[AbstractTest::INFRA_NONEXISTENTREALM]);
393
        }
394
395
        if ($atLeastOneConversationReject) {
396
            // at least we can be sure it exists
397
            unset($this->possibleFailureReasons[AbstractTest::INFRA_NONEXISTENTREALM]);
398
            // It could still be an IdP RADIUS problem in that some cert oddities 
399
            // in combination with the device lead to a broken auth
400
            // if there is nothing beyond the "REMARK" level, then it's not an IdP problem
401
            // otherwise, add the corresponding warnings and errors to $additionalFindings
402
            $highestObservedErrorLevel = 0;
403
            foreach ($this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS] as $oneRun) {
404
                $highestObservedErrorLevel = max($highestObservedErrorLevel, $oneRun['DETAIL']['level'] ?? 0);
405
            }
406
            switch ($highestObservedErrorLevel) {
407
                case RADIUSTests::L_OK:
408
                case RADIUSTests::L_REMARK:
409
                    // both are fine - the IdP is working and the user problem
410
                    // is not on the IdP RADIUS level
411
                    $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["ODDITYLEVEL" => $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][0]['DETAIL']['level']];
412
                    unset($this->possibleFailureReasons[AbstractTest::INFRA_IDP_RADIUS]);
413
                    break;
414
                case RADIUSTests::L_WARN:
415
                    $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["ODDITYLEVEL" => RADIUSTests::L_WARN];
416
                    $this->possibleFailureReasons[AbstractTest::INFRA_IDP_RADIUS] = 0.3; // possibly we found the culprit - if RADIUS server is misconfigured AND user is on a device which reacts picky about exactly this oddity.
417
                    break;
418
                case RADIUSTests::L_ERROR:
419
                    $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["ODDITYLEVEL" => RADIUSTests::L_ERROR];
420
                    $this->possibleFailureReasons[AbstractTest::INFRA_IDP_RADIUS] = 0.8; // errors are never good, so we can be reasonably sure we've hit the spot!
421
            }
422
        }
423
    }
424
425
    /**
426
     * can we run thorough checks or not? Thorough can only be done if we can
427
     * deterministically map the realm to be checked against a CAT Profile, and
428
     * then only if the profile is complete.
429
     * 
430
     * @return void
431
     */
432
    private function determineTestsuiteParameters()
433
    {
434
        if ($this->catProfile > 0) {
435
            $profileObject = \core\ProfileFactory::instantiate($this->catProfile);
436
            $readinessLevel = $profileObject->readinessLevel();
437
438
            switch ($readinessLevel) {
439
                case \core\AbstractProfile::READINESS_LEVEL_SHOWTIME:
440
                // fall-througuh intended: use the data even if non-public but complete
441
                case \core\AbstractProfile::READINESS_LEVEL_SUFFICIENTCONFIG:
442
                    $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["Profile" => $profileObject->identifier];
443
                    $this->testsuite = new RADIUSTests($this->realm, $profileObject->getRealmCheckOuterUsername(), $profileObject->getEapMethodsinOrderOfPreference(1), $profileObject->getCollapsedAttributes()['eap:server_name'], $profileObject->getCollapsedAttributes()["eap:ca_file"]);
444
                    break;
445
                case \core\AbstractProfile::READINESS_LEVEL_NOTREADY:
446
                    $this->additionalFindings[AbstractTest::INFRA_IDP_RADIUS][] = ["Profile" => "UNCONCLUSIVE"];
447
                    $this->testsuite = new RADIUSTests($this->realm, "anonymous@" . $this->realm);
448
                    break;
449
                default:
450
            }
451
        } else {
452
            $this->testsuite = new RADIUSTests($this->realm, "anonymous@" . $this->realm);
453
        }
454
    }
455
456
    /**
457
     * Does the main meditation job
458
     * @return array the findings
459
     */
460
    public function magic()
461
    {
462
        $this->testId = \core\CAT::uuid();
463
        $this->databaseHandle->exec("INSERT INTO diagnosticrun (test_id, realm, suspects, evidence) VALUES ('$this->testId', '$this->realm', NULL, NULL)");
464
        // simple things first: do we know anything about the realm, either
465
        // because it's a CAT participant or because it's in the eduroam DB?
466
        // if so, we can exclude the INFRA_NONEXISTENTREALM cause
467
        $this->additionalFindings[AbstractTest::INFRA_NONEXISTENTREALM]['DATABASE_STATUS'] = ["ID1" => $this->catProfile, "ID2" => $this->dbIdP];
468
        if ($this->catProfile != \core\Federation::UNKNOWN_IDP || $this->dbIdP != \core\Federation::UNKNOWN_IDP) {
469
            unset($this->possibleFailureReasons[AbstractTest::INFRA_NONEXISTENTREALM]);
470
        }
471
        // do we operate on a non-ambiguous, fully configured CAT profile? Then
472
        // we run the more thorough check, otherwise the shallow one.
473
        $this->determineTestSuiteParameters();
474
        // let's do the least amount of testing needed:
475
        // - The CAT reachability test already covers ELTRs, IdP NRO level and the IdP itself.
476
        //   if the realm maps to a CAT IdP, we can run the more thorough tests; otherwise just
477
        //   the normal shallow ones
478
        // these are the normal "realm check" tests covering ETLR, LINK_NRO_IDP, NRO, IDP_RADIUS
479
        $this->CATInternalTests();
480
        // - if the test does NOT go through, we need to find out which of the three is guilty
481
        // - then, the international "via ETLR" check can be used to find out if the IdP alone
482
        //   is guilty. If that one fails, the direct monitoring of servers and ETLRs themselves
483
        //   closes the loop.
484
        // let's see if the ETLRs are up
485
        if (array_key_exists(AbstractTest::INFRA_ETLR, $this->possibleFailureReasons)) {
486
            $this->checkEtlrStatus();
487
        }
488
489
        // then let's check the IdP's FLR, if we know the IdP federation at all
490
        if ($this->idPFederation !== NULL) {
491
            if (array_key_exists(AbstractTest::INFRA_NRO_IDP, $this->possibleFailureReasons)) {
492
                // first the direct connectivity to the server
493
                $this->checkFlrServerStatus(AbstractTest::INFRA_NRO_IDP);
494
            }
495
            // now let's theck the link
496
            if (array_key_exists(AbstractTest::INFRA_LINK_ETLR_NRO_IDP, $this->possibleFailureReasons)) {
497
                $this->checkFedEtlrUplink(AbstractTest::INFRA_NRO_IDP);
498
            }
499
        }
500
        // now, if we know the country the user is currently in, let's see 
501
        // if the NRO SP-side is up
502
        if ($this->visitedFlr !== NULL) {
503
            $this->checkFlrServerStatus(AbstractTest::INFRA_NRO_SP);
504
            // and again its uplink to the ETLR
505
            $this->checkFedEtlrUplink(AbstractTest::INFRA_NRO_SP);
506
        }
507
        // the last thing we can do (but it's a bit redundant): check the country-to-country link
508
        // it's only needed if all three and their links are up, but we want to exclude funny routing blacklists 
509
        // which occur only in the *combination* of source and dest
510
        // if there is an issue at that point, blame the SP: once a request
511
        // would have reached the ETLRs, things would be all good (assuming
512
        // perfection on the ETLRs here!). So the SP has a wrong config.
513
        if ($this->idPFederation !== NULL &&
514
                $this->visitedFlr !== NULL &&
515
                !array_key_exists(AbstractTest::INFRA_ETLR, $this->possibleFailureReasons) &&
516
                !array_key_exists(AbstractTest::INFRA_LINK_ETLR_NRO_IDP, $this->possibleFailureReasons) &&
517
                !array_key_exists(AbstractTest::INFRA_NRO_IDP, $this->possibleFailureReasons) &&
518
                !array_key_exists(AbstractTest::INFRA_LINK_ETLR_NRO_SP, $this->possibleFailureReasons) &&
519
                !array_key_exists(AbstractTest::INFRA_NRO_SP, $this->possibleFailureReasons)
520
        ) {
521
            $countryToCountryStatus = $this->checkNROFlow();
522
            $this->additionalFindings[AbstractTest::INFRA_NRO_SP][] = $countryToCountryStatus;
523
            $this->additionalFindings[AbstractTest::INFRA_ETLR][] = $countryToCountryStatus;
524
            $this->additionalFindings[AbstractTest::INFRA_NRO_IDP][] = $countryToCountryStatus;
525
            switch ($countryToCountryStatus["STATUS"]) {
526
                case AbstractTest::STATUS_GOOD:
527
                    // all routes work
528
                    break;
529
                case AbstractTest::STATUS_PARTIAL:
530
                // at least one, or even all have a routing problem
531
                case AbstractTest::STATUS_DOWN:
532
                    // that's rather telling.
533
                    $this->possibleFailureReasons[AbstractTest::INFRA_NRO_SP] = 0.95;
534
            }
535
        }
536
537
        $this->normaliseResultSet();
538
        $jsonSuspects = json_encode($this->possibleFailureReasons, JSON_PRETTY_PRINT);
539
        $jsonEvidence = json_encode($this->additionalFindings, JSON_PRETTY_PRINT);
540
        $this->databaseHandle->exec("UPDATE diagnosticrun SET realm = ?, visited_flr = ?, visited_hotspot = ?, suspects = ?, evidence = ? WHERE test_id = ?", "ssssss", $this->realm, $this->visitedFlr, $this->visitedHotspot, $jsonSuspects, $jsonEvidence, $this->testId);
541
        $_SESSION['TESTID'] = $this->testId;
542
        $_SESSION["SUSPECTS"] = $this->possibleFailureReasons;
543
        $_SESSION["EVIDENCE"] = $this->additionalFindings;
544
        return ["SUSPECTS" => $this->possibleFailureReasons, "EVIDENCE" => $this->additionalFindings];
545
    }
546
}