diff --git a/4dev/tests/Create/CoreLibsCreateCrc64Test.php b/4dev/tests/Create/CoreLibsCreateCrc64Test.php
new file mode 100644
index 00000000..dffa02a7
--- /dev/null
+++ b/4dev/tests/Create/CoreLibsCreateCrc64Test.php
@@ -0,0 +1,73 @@
+ [
+ 'file' => __DIR__ . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'random_10KB.bin',
+ 'crc64_base64' => 'oMgkUkGrDME=',
+ 'crc64_hex' => 'a0c8245241ab0cc1',
+ ],
+ '100kb' => [
+ 'file' => __DIR__ . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'random_100KB.bin',
+ 'crc64_base64' => 'i9Lpf3Y3D3M=',
+ 'crc64_hex' => '8bd2e97f76370f73',
+ ],
+ '1mb' => [
+ 'file' => __DIR__ . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'random_1MB.bin',
+ 'crc64_base64' => 'WqYAirRJ0q8=',
+ 'crc64_hex' => '5aa6008ab449d2af',
+ ],
+ '5mb' => [
+ 'file' => __DIR__ . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'random_5MB.bin',
+ 'crc64_base64' => '9hS5kUxbgR0=',
+ 'crc64_hex' => 'f614b9914c5b811d',
+ ],
+ '10mb' => [
+ 'file' => __DIR__ . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'random_10MB.bin',
+ 'crc64_base64' => 'xDmtkk1JhjM=',
+ 'crc64_hex' => 'c439ad924d498633',
+ ],
+ // everything above 10MB is too slow, sow do not check this
+ ];
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @covers ::crc64
+ * @covers ::crc64hex
+ * @dataProvider providerFiles
+ * @testdox generate crc64 for $file and expecting $crc64_base64 and $crc64_hex [$_dataName]
+ *
+ * @return void
+ */
+ public function testCrc64(string $file, string $crc64_base64, string $crc64_hex): void
+ {
+ $this->assertSame(
+ $crc64_base64,
+ \CoreLibs\Create\Crc64::crc64($file),
+ "Failed asserting base64 CRC64 for $file"
+ );
+ $this->assertSame(
+ $crc64_hex,
+ \CoreLibs\Create\Crc64::crc64hex($file),
+ "Failed asserting hex CRC64 for $file"
+ );
+ }
+}
+
+// __END__
diff --git a/4dev/tests/Create/files/random_100KB.bin b/4dev/tests/Create/files/random_100KB.bin
new file mode 100644
index 00000000..c9ab5eab
Binary files /dev/null and b/4dev/tests/Create/files/random_100KB.bin differ
diff --git a/4dev/tests/Create/files/random_10KB.bin b/4dev/tests/Create/files/random_10KB.bin
new file mode 100644
index 00000000..c6d3b8c4
Binary files /dev/null and b/4dev/tests/Create/files/random_10KB.bin differ
diff --git a/4dev/tests/Create/files/random_10MB.bin b/4dev/tests/Create/files/random_10MB.bin
new file mode 100644
index 00000000..81f205dc
Binary files /dev/null and b/4dev/tests/Create/files/random_10MB.bin differ
diff --git a/4dev/tests/Create/files/random_1MB.bin b/4dev/tests/Create/files/random_1MB.bin
new file mode 100644
index 00000000..0002c672
Binary files /dev/null and b/4dev/tests/Create/files/random_1MB.bin differ
diff --git a/4dev/tests/Create/files/random_5MB.bin b/4dev/tests/Create/files/random_5MB.bin
new file mode 100644
index 00000000..796bfda0
Binary files /dev/null and b/4dev/tests/Create/files/random_5MB.bin differ
diff --git a/www/admin/class_test.crc64.php b/www/admin/class_test.crc64.php
new file mode 100644
index 00000000..9d389dc6
--- /dev/null
+++ b/www/admin/class_test.crc64.php
@@ -0,0 +1,77 @@
+ BASE . LOG,
+ 'log_file_id' => $LOG_FILE_ID,
+ 'log_per_date' => true,
+]);
+
+$PAGE_NAME = 'TEST CLASS: CRC64';
+print "";
+print "
" . $PAGE_NAME . "";
+print "";
+print '';
+print '' . $PAGE_NAME . '
';
+
+
+$file = getcwd() . DIRECTORY_SEPARATOR . 'class_test.file.php';
+print "CRC64: $file: " . Crc64::crc64($file) . "
";
+print "CRC64HEX: $file: " . Crc64::crc64hex($file) . "
";
+
+// big file check including speed
+$base_path = __DIR__ . DIRECTORY_SEPARATOR
+ . '..' . DIRECTORY_SEPARATOR
+ . '..' . DIRECTORY_SEPARATOR
+ . '4dev' . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR .
+ 'Create' . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR;
+$max_size = 1024 * 1024 * 10;
+# open the base path and loop over each file in there
+$files = glob($base_path . '*');
+if (!empty($files)) {
+ foreach ($files as $file) {
+ if (!is_file($file)) {
+ continue;
+ }
+ if (filesize($file) > $max_size) {
+ print "Skipping large file: $file
";
+ continue;
+ }
+ RunningTime::runningTime();
+ print "CRC64: $file: " . Crc64::crc64($file) . "
";
+ RunningTime::runningTime();
+ print "Running Time: $file: " . RunningTime::runningTimeString() . "
";
+ print "Running Time: $file: " . RunningTime::hrRunningTime() . "
";
+ print "CRC64HEX: $file: " . Crc64::crc64hex($file) . "
";
+ RunningTime::runningTime();
+ print "Running Time: $file: " . RunningTime::runningTimeString() . "
";
+ print "Running Time: $file: " . RunningTime::hrRunningTime() . "
";
+ RunningTime::hrRunningTimeReset();
+ }
+} else {
+ print "No files found in base path: $base_path
";
+}
+
+print "";
+
+// __END__
diff --git a/www/admin/class_test.php b/www/admin/class_test.php
index 0e2d6ad4..2ae5c7ac 100644
--- a/www/admin/class_test.php
+++ b/www/admin/class_test.php
@@ -120,6 +120,7 @@ $test_files = [
'class_test.datetime.php' => 'Class Test: DATE/TIME',
'class_test.array.php' => 'Class Test: ARRAY HANDLER',
'class_test.file.php' => 'Class Test: FILE',
+ 'class_test.crc64.php' => 'Class Test: CRC64',
'class_test.randomkey.php' => 'Class Test: RANDOM KEY',
'class_test.system.php' => 'Class Test: SYSTEM',
'class_test.readenvfile.php' => 'Class Test: READ ENV FILE',
diff --git a/www/lib/CoreLibs/Create/Crc64.php b/www/lib/CoreLibs/Create/Crc64.php
new file mode 100644
index 00000000..eab5ac45
--- /dev/null
+++ b/www/lib/CoreLibs/Create/Crc64.php
@@ -0,0 +1,106 @@
+> is arithmetic, so mask off the sign bit
+ $c = ($c & 1)
+ ? ((($c >> 1) & PHP_INT_MAX) ^ $poly)
+ : (($c >> 1) & PHP_INT_MAX);
+ }
+ $table[$i] = $c;
+ }
+ }
+ $crc = ~$crc; // xor 0xFFFFFFFFFFFFFFFF
+ foreach (unpack('C*', $data) ?: [] as $b) {
+ $crc = $table[($crc ^ $b) & 0xFF] ^ (($crc >> 8) & 0x00FFFFFFFFFFFFFF);
+ }
+ return ~$crc;
+ }
+
+ /**
+ * Returns int value of the CRC64 checksum for the given file.
+ *
+ * @param string $path
+ * @param int<1,16777216> $chunkSize
+ * @return int
+ */
+ private function crc64CalcFile(string $path, int $chunkSize = 8 * 1024 * 1024): int
+ {
+ $fh = fopen($path, 'rb');
+ if ($fh === false) {
+ throw new \RuntimeException("Cannot open $path");
+ }
+ $crc = 0;
+ try {
+ while (!feof($fh)) {
+ $chunk = fread($fh, $chunkSize);
+ if ($chunk === false) {
+ throw new \RuntimeException("Read error on $path");
+ }
+ $crc = $this->crc64Calc($chunk, $crc);
+ }
+ } finally {
+ fclose($fh);
+ }
+ return $crc;
+ }
+
+ /**
+ * Returns the Base64-encoded CRC64 checksum for the given file.
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function crc64(string $path): string
+ {
+ return base64_encode(pack('J', (new self())->crc64CalcFile($path))); // J = uint64 big-endian
+ }
+
+ /**
+ * Returns the 16-character lowercase hexadecimal CRC64 checksum for the given file.
+ * Same as base64.b64decode(...).hex()
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function crc64hex(string $path): string
+ {
+ return sprintf('%016x', (new self())->crc64CalcFile($path));
+ }
+}
+
+// __END__