diff --git a/bin/console.php b/bin/console.php
index 6ec3aab..b6989d9 100755
--- a/bin/console.php
+++ b/bin/console.php
@@ -6,6 +6,7 @@
use Gared\EtherScan\Console\GenerateFileHashesCommand;
use Gared\EtherScan\Console\GenerateRevisionLookupCommand;
use Gared\EtherScan\Console\ScanCommand;
+use Gared\EtherScan\Console\GenerateFileHashesAllVersionsCommand;
use Symfony\Component\Console\Application;
require __DIR__ . '/../vendor/autoload.php';
@@ -15,4 +16,5 @@
$application->add(new GenerateRevisionLookupCommand());
$application->add(new GenerateFileHashesCommand());
$application->add(new CheckFileHashesCommand());
-$application->run();
\ No newline at end of file
+$application->add(new GenerateFileHashesAllVersionsCommand());
+$application->run();
diff --git a/src/Console/GenerateFileHashesAllVersionsCommand.php b/src/Console/GenerateFileHashesAllVersionsCommand.php
new file mode 100644
index 0000000..d30f3d1
--- /dev/null
+++ b/src/Console/GenerateFileHashesAllVersionsCommand.php
@@ -0,0 +1,226 @@
+addOption('matches-count', null, InputArgument::OPTIONAL, 'Minimum count of matches for version to be considered valid', 3)
+ ->addArgument('file', InputArgument::REQUIRED, 'File path to check');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $filePath = $input->getArgument('file');
+ $countVersionsMatch = $input->getOption('matches-count');
+
+ $allInstances = $this->getInstances();
+ $instanceResults = new InstanceResults();
+
+ foreach ($this->getAllVersions($allInstances) as $version) {
+ $this->scanVersionInstances($allInstances, $version, $filePath, $instanceResults, $output, $countVersionsMatch);
+ }
+
+ $listInstances = $instanceResults->getInstancesByVersion();
+ uksort($listInstances, function ($a, $b) {
+ return version_compare($a, $b);
+ });
+
+ $versionRanges = [];
+
+ $table = new Table($output);
+ $table->setHeaders(['Version', 'Count Instances', 'File Hashes']);
+ foreach ($listInstances as $version => $instances) {
+ $fileHashes = [];
+ $fileHashForVersion = null;
+ foreach ($instances as $instance) {
+ if ($instance->fileHash !== null) {
+ $fileHashes[] = $instance->fileHash;
+ }
+ }
+
+ $fileHashesWithCount = array_count_values($fileHashes);
+ foreach ($fileHashesWithCount as $fileHash => $count) {
+ if ($count >= $countVersionsMatch) {
+ $fileHashForVersion = $fileHash;
+ }
+ }
+
+ if ($fileHashForVersion !== null) {
+ $versionRanges[$fileHashForVersion][] = $version;
+ }
+
+
+ $versionString = '' . $version . '';
+ if (count($instances) < $countVersionsMatch) {
+ $versionString = '' . $version . '';
+ } else if ($fileHashForVersion === null) {
+ $versionString = '' . $version . '';
+ }
+
+ $table->addRow([$versionString, count($instances), ...$fileHashes]);
+ }
+
+ $table->render();
+
+
+ $table = new Table($output);
+ $table->setHeaders(['File Hash', 'Minimum Version', 'Maximum Version']);
+
+ foreach ($versionRanges as $fileHash => $versions) {
+ usort($versions, function ($a, $b) {
+ return version_compare($a, $b);
+ });
+
+ $minimumVersion = $versions[array_key_first($versions)];
+ $maximumVersion = $versions[array_key_last($versions)];
+
+ $table->addRow([$fileHash, $minimumVersion, $maximumVersion]);
+ }
+
+ $table->render();
+
+ return self::SUCCESS;
+ }
+
+ /**
+ * @param list}> $allInstances
+ */
+ private function scanVersionInstances(array $allInstances, string $version, string $file, InstanceResults $instanceResults, OutputInterface $output, int $countVersionsMatchNeeded): void
+ {
+ $foundMatchesForHash = [];
+ $scannedInstances = 0;
+
+ foreach ($this->getInstancesByVersion($allInstances, $version) as $instance) {
+ $fileContent = $this->getFile($instance['name'], $file);
+ $fileHash = $fileContent !== null ? hash('md5', $fileContent) : null;
+ $scannedInstances++;
+
+ $instanceResult = new InstanceResult($instance['name'], $version, $fileHash, $fileContent);
+ $instanceResults->add($instanceResult);
+
+ if ($instanceResult->fileHash === null) {
+ $output->writeln('Could not get hash for instance ' . $instance['name'] . '', OutputInterface::VERBOSITY_VERY_VERBOSE);
+
+ if ($scannedInstances > 4 && count($foundMatchesForHash) === 0) {
+ break;
+ }
+
+ continue;
+ }
+
+ if ($this->matches($instanceResults, $instanceResult, $version)) {
+ $output->writeln('Match found for version ' . $version . ' and hash ' . $instanceResult->fileHash);
+
+ if (!isset($foundMatchesForHash[$instanceResult->fileHash])) {
+ $foundMatchesForHash[$instanceResult->fileHash] = 0;
+ }
+
+ $foundMatchesForHash[$instanceResult->fileHash]++;
+ if ($foundMatchesForHash[$instanceResult->fileHash] === $countVersionsMatchNeeded) {
+ break;
+ }
+ } else {
+// $output->writeln('Mismatch found for version ' . $version . ' and hashes ' . $fileHash . ' != ' . $lastInstanceResult->fileHash . ' for servers: ' . $instanceResult->name . ', ' . $lastInstanceResult->name . '');
+// $output->writeln($lastInstanceResult->name . ': (' . mb_strlen($lastInstanceResult->fileContent) . ') ' . mb_substr($lastInstanceResult->fileContent, 0, 500), OutputInterface::VERBOSITY_DEBUG);
+// $output->writeln($instanceResult->name . ': (' . mb_strlen($instanceResult->fileContent) . ') ' . mb_substr($instanceResult->fileContent, 0, 500), OutputInterface::VERBOSITY_DEBUG);
+ }
+ }
+ }
+
+ private function matches(InstanceResults $instanceResults, InstanceResult $instanceResult, string $version): bool
+ {
+ foreach ($instanceResults->getInstancesForVersion($version) as $instance) {
+ if ($instance->fileHash === $instanceResult->fileHash) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param list}> $instances
+ * @param string $version
+ * @return list}>
+ */
+ private function getInstancesByVersion(array $instances, string $version): array
+ {
+ $filteredInstances = [];
+ foreach ($instances as $instance) {
+ if ($instance['scan']['version'] === $version) {
+ $filteredInstances[] = $instance;
+ }
+ }
+ shuffle($filteredInstances);
+ return $filteredInstances;
+ }
+
+ /**
+ * @param list}> $instances
+ * @return list
+ */
+ private function getAllVersions(array $instances): array
+ {
+ $versions = [];
+ foreach ($instances as $instance) {
+ $version = $instance['scan']['version'];
+ if (!in_array($version, $versions, true)) {
+ $versions[] = $version;
+ }
+ }
+ shuffle($versions);
+ return $versions;
+ }
+
+ /**
+ * @return list}>
+ */
+ private function getInstances(): array
+ {
+ $client = new Client();
+ $response = $client->get('https://ether-scan.stefans-entwicklerecke.de/api/instances');
+
+ $body = (string)$response->getBody();
+ $data = json_decode($body, true);
+ return $data['instances'] ?? [];
+ }
+
+ private function getFile(string $url, string $path): ?string
+ {
+ try {
+ $client = new Client([
+ 'base_uri' => $url,
+ RequestOptions::TIMEOUT => 3.0,
+ RequestOptions::CONNECT_TIMEOUT => 1.0,
+ 'verify' => false,
+// 'debug' => true,
+ ]);
+ $response = $client->get($path, [
+ 'headers' => ['Accept-Encoding' => 'gzip'],
+ ]);
+
+ return (string)$response->getBody();
+ } catch (GuzzleException) {
+ }
+
+ return null;
+ }
+}
diff --git a/src/Console/InstanceResult.php b/src/Console/InstanceResult.php
new file mode 100644
index 0000000..d4ab438
--- /dev/null
+++ b/src/Console/InstanceResult.php
@@ -0,0 +1,16 @@
+
+ */
+ private array $instances = [];
+
+ public function add(InstanceResult $instanceResult): void
+ {
+ $this->instances[] = $instanceResult;
+ }
+
+ /**
+ * @return array>
+ */
+ public function getInstancesByVersion(): array
+ {
+ $result = [];
+ foreach ($this->instances as $instance) {
+ $version = $instance->version;
+ if (!isset($result[$version])) {
+ $result[$version] = [];
+ }
+ $result[$version][] = $instance;
+ }
+ return $result;
+ }
+
+ /**
+ * @return list
+ */
+ public function getInstancesForVersion(string $version): array
+ {
+ $result = [];
+ foreach ($this->instances as $instance) {
+ if ($instance->version === $version) {
+ $result[] = $instance;
+ }
+ }
+ return $result;
+ }
+}
diff --git a/src/Service/FileHashLookupService.php b/src/Service/FileHashLookupService.php
index 21ccd88..b21aaa0 100644
--- a/src/Service/FileHashLookupService.php
+++ b/src/Service/FileHashLookupService.php
@@ -10,7 +10,7 @@ class FileHashLookupService
/**
* @var array>>
*/
- private const FILE_HASH_VERSIONS = [
+ private const array FILE_HASH_VERSIONS = [
'static/js/AttributePool.js' => [
'4edf12f374e005bfa7d0fc6681caa67f' => [null, '1.8.0'],
'64ac4ec21f716d36d37a4b1a9aa0debe' => ['1.8.3', '1.8.4'],
@@ -71,15 +71,15 @@ class FileHashLookupService
'fc1965c84113e78fb5b29b68c8fc84f8' => ['1.9.1', '1.9.1'],
'e1d8c5fc1e4fcfe28b527828543a4729' => ['1.9.2', '2.1.0'],
'96fd880e3e348fe4b45170b7c750a0b1' => ['2.1.1', '2.1.1'],
- 'a9aa5b16c8e3ff79933156220cb87dbf' => ['2.2.2', null],
+ 'a9aa5b16c8e3ff79933156220cb87dbf' => ['2.2.2', '2.2.2'],
],
'static/css/pad.css' => [
'169c79ec1a44c5c45dfce64c0f62c7ef' => [null, '1.9.7'],
'2a37d1ffbd906c905fe7f1b42564caa5' => ['2.0.0', '2.1.0'],
'8fab111c95434eac9414f0d8ea5d81b8' => ['2.1.1', '2.1.1'],
'8ae26862f7716d1bada457fdc92bb1d1' => ['2.2.2', '2.3.2'],
- '12ba3a5933f399b882cf847d407c31f0' => ['2.4.1', '2.5.0'],
- '53c72fe8218c95773dcfce173dacb3f6' => ['2.5.1', null],
+ '12ba3a5933f399b882cf847d407c31f0' => ['2.4.1', '2.5.1'],
+ '53c72fe8218c95773dcfce173dacb3f6' => ['2.5.2', null],
],
'static/skins/colibris/index.js' => [
'eb3857ee08d0c2217649dcb61b1c8e73' => ['2.1.1', '2.2.7'],