From 7f657cb01792d548fd0e2356b179e7345f79fb0d Mon Sep 17 00:00:00 2001 From: hellcat Date: Sun, 18 Oct 2026 19:41:05 +0800 Subject: [PATCH] Isolate invalid nodes using native error indices before bisection --- app/Services/Freenode/NativeValidator.php | 19 +++++++++++++++++- tests/Unit/Freenode/NativeValidatorTest.php | 22 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Freenode/NativeValidatorTest.php diff --git a/app/Services/Freenode/NativeValidator.php b/app/Services/Freenode/NativeValidator.php index 4388d9a8..84b5be90 100644 --- a/app/Services/Freenode/NativeValidator.php +++ b/app/Services/Freenode/NativeValidator.php @@ -13,6 +13,7 @@ final class NativeValidator public array $rejected = []; private string $dir; private float $startedAt; + private ?int $failureIndex = null; public function __construct(private string $singbox, private string $mihomo, private ?array $clashTemplate = null, ?string $dataDirectory = null) { @@ -47,6 +48,13 @@ final class NativeValidator try { $process->run(); $this->checks++; + $this->failureIndex = null; + // Native diagnostics stay in memory. Extract only the structured node index. + $diagnostic = $process->getErrorOutput().$process->getOutput(); + $pattern = $format === 'singbox' ? '/outbound(?:s)?\[(\d+)\]/' : '/proxy (\d+):/'; + if (!$process->isSuccessful() && preg_match($pattern, $diagnostic, $match)) { + $this->failureIndex = (int) $match[1] - ($format === 'singbox' ? 2 : 0); + } return $process->isSuccessful(); } finally { @unlink($file); @@ -67,7 +75,16 @@ final class NativeValidator private function partition(string $format, array $nodes, callable $render): array { - if (!$nodes || $this->check($format, $render($nodes))) return $nodes; + if (!$nodes) return []; + while ($nodes) { + if ($this->check($format, $render($nodes))) return $nodes; + $keys = array_keys($nodes); + if ($this->failureIndex === null || !isset($keys[$this->failureIndex])) break; + $key = $keys[$this->failureIndex]; + $this->rejected[$format][] = $key; + unset($nodes[$key]); + } + if (!$nodes) return []; if (count($nodes) === 1) { $this->rejected[$format][] = array_key_first($nodes); return []; diff --git a/tests/Unit/Freenode/NativeValidatorTest.php b/tests/Unit/Freenode/NativeValidatorTest.php new file mode 100644 index 00000000..770f636d --- /dev/null +++ b/tests/Unit/Freenode/NativeValidatorTest.php @@ -0,0 +1,22 @@ + 'synthetic-'.$i, 'type' => 'ss', 'server' => 'example.invalid', 'port' => 443, 'cipher' => $i % 4 === 0 ? 'invalid-synthetic' : 'aes-128-gcm', 'password' => 'synthetic']; + } + $validator = new NativeValidator('', $mh); + $good = $validator->filter('clash', $rows); + self::assertCount(24, $good); + self::assertCount(8, $validator->rejected['clash']); + self::assertLessThanOrEqual(10, $validator->checks); + } +}