Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CRC-32/ISO-HDLC (aka crc32) #1

Merged
merged 3 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
.PHONY: build
build:
@if [ ! -d "./build" ]; then git clone https://github.com/awesomized/crc64fast-nvme.git build; fi
@cd build && git fetch && git checkout 1.1.0
@cd build && cargo build --release
build: build-crc64nvme build-crc32isohdlc

.PHONY: validate
validate: phpcs php-cs-fixer-check static-analysis test cli
Expand Down Expand Up @@ -46,14 +43,32 @@ phpunit: build

.PHONY: cli
cli: build
@echo "Should result in f8046e40c403f1d0:"
@php cli/calculate.php 'hello, world!'
@echo "CRC-64/NVME should result in f8046e40c403f1d0:"
@php cli/calculateCrc64Nvme.php 'hello, world!'
@echo "CRC-32/ISO-HDLC should result in 58988d13:"
@php cli/calculateCrc32IsoHdlc.php 'hello, world!'

.PHONY: composer
composer:
# Psalm v5.26.1 doesn't like PHP-8.4
composer install --ignore-platform-req=php+

.PHONY: build-directory
build-directory:
@if [ ! -d "./build" ]; then mkdir build; fi

.PHONY: build-crc64nvme
build-crc64nvme: build-directory
@cd build && (if [ ! -d "./crc64fast-nvme" ]; then git clone https://github.com/awesomized/crc64fast-nvme.git; fi || true)
@cd build/crc64fast-nvme && git fetch && git checkout 1.1.0
@cd build/crc64fast-nvme && cargo build --release

.PHONY: build-crc32isohdlc
build-crc32isohdlc: build-directory
@cd build && (if [ ! -d "./crc32fast-lib-rust" ]; then git clone https://github.com/awesomized/crc32fast-lib-rust.git; fi || true)
@cd build/crc32fast-lib-rust && git fetch && git checkout 1.0.0
@cd build/crc32fast-lib-rust && cargo build --release

.PHONY: clean
clean:
rm -rf build
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
[![Static Analysis](https://github.com/awesomized/crc-fast-php/actions/workflows/static-analysis.yml/badge.svg?branch=main)](https://github.com/awesomized/crc-fast-php/actions/workflows/static-analysis.yml)
[![Unit Tests](https://github.com/awesomized/crc-fast-php/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/awesomized/crc-fast-php/actions/workflows/unit-tests.yml)

Fast, SIMD-accelerated CRC computation in PHP via FFI using Rust. Currently supports `CRC-64/NVME`, but will likely support other popular checksums over time, especially `CRC32`.
Fast, SIMD-accelerated CRC computation in PHP via FFI using Rust. Currently supports [CRC-64/NVME](https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme) and [CRC-32/ISO-HDLC aka "crc32"](https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iso-hdlc). Other implementations welcome via PR.

## CRC-64/NVME
Uses the [crc64fast-nvme](https://github.com/awesomized/crc64fast-nvme) Rust package and its C-compatible shared library.

It's capable of generating checksums at >20-50 GiB/s, depending on the CPU. It is much, much faster (>100X) than the native [crc32](https://www.php.net/manual/en/function.crc32.php), crc32b, and crc32c [implementations](https://www.php.net/manual/en/function.hash-algos.php) in PHP.

`CRC-64/NVME` is in use in a variety of large-scale and mission-critical systems, software, and hardware, such as:
[CRC-64/NVME](https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme) is in use in a variety of large-scale and mission-critical systems, software, and hardware, such as:
- AWS S3's [recommended checksum](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html)
- The [Linux kernel](https://github.com/torvalds/linux/blob/786c8248dbd33a5a7a07f7c6e55a7bfc68d2ca48/lib/crc64.c#L66-L73)
- The [NVMe specification](https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0d-2023.12.28-Ratified.pdf)

## CRC-32/ISO-HDLC (aka "crc32")
Uses the [crc32fast-lib]() Rust package (which exposes the [crc32fast](https://github.com/srijs/rust-crc32fast) Rust library as a C-compatible shared library).

It's >10X faster than PHP's native [crc32](https://www.php.net/manual/en/function.crc32.php) implementation.

[CRC-32/ISO-HDLC](https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iso-hdlc) is the de-facto "crc32" checksum, though there are [many other 32-bit variants](https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat-bits.32).

## Changes

See the [change log](CHANGELOG.md).
Expand All @@ -34,16 +41,19 @@ composer require awesomized/crc-fast

## Usage

Examples are for `CRC-64/NVME`, but `CRC-32/ISO-HDLC` is nearly identical, just in a different namespace (`Awesomized\Checksums\Crc32\IsoHdlc`).

### Creating the CRC-64/NVME FFI object

A [helper FFI Class](src/Ffi.php) is provided, which supplies many ways to easily create an FFI object for the [crc64fast-nvme](https://github.com/awesomized/crc64fast-nvme) shared library:

#### - Via [preloaded](https://www.php.net/manual/en/ffi.examples-complete.php) shared library (recommended for any long-running workloads, such as web requests):

```php
use Awesomized\Checksums\Crc64;
use Awesomized\Checksums\Crc64\Nvme;

// uses the opcache preloaded shared library and PHP Class(es)
$crc64Fast = Crc64\Ffi::fromPreloadedScope(
$crc64Fast = Nvme\Ffi::fromPreloadedScope(
scope: 'CRC64NVME', // optional, this is the default
);
```
Expand All @@ -52,20 +62,21 @@ $crc64Fast = Crc64\Ffi::fromPreloadedScope(
Uses a C header file to define the functions and point to the shared library (`.so` on Linux, `.dll` on Windows, `.dylib` on macOS, etc).

```php
use Awesomized\Checksums\Crc64;
use Awesomized\Checksums\Crc64\Nvme;

// uses the FFI_LIB and FFI_SCOPE definitions in the header file
$crc64Fast = Crc64\Ffi::fromHeaderFile(
$crc64Fast = Nvme\Ffi::fromHeaderFile(
headerFile: 'path/to/crc64fast_nvme.h', // optional, can likely be inferred from the OS
);
```

#### - Via C definitions + library:

```php
use Awesomized\Checksums\Crc64;
use Awesomized\Checksums\Crc64\Nvme;

// uses the supplied C definitions and name/location of the shared library
$crc64Fast = Crc64\Ffi::fromCode(
$crc64Fast = Nvme\Ffi::fromCode(
code: 'typedef struct DigestHandle DigestHandle;
DigestHandle* digest_new(void);
void digest_write(DigestHandle* handle, const char* data, size_t len);
Expand All @@ -79,31 +90,32 @@ $crc64Fast = Crc64\Ffi::fromCode(
#### Calculate CRC-64/NVME checksums:

```php
use Awesomized\Checksums\Crc64;
use Awesomized\Checksums\Crc64\Nvme;

/** @var \FFI $crc64Fast */

// calculate the checksum of a string
$checksum = Crc64\Nvme::calculate(
$checksum = Nvme\Computer::calculate(
ffi: $crc64Fast,
string: 'hello, world!'
); // f8046e40c403f1d0

// calculate the checksum of a file, which will chunk through the file optimally,
// limiting RAM usage and maximizing throughput
$checksum = Crc64\Nvme::calculateFile(
$checksum = Nvme\Computer::calculateFile(
ffi: $crc64Fast,
filename: 'path/to/hello-world'
); // f8046e40c403f1d0
```

#### Calculate CRC-64/NVME checksums with a Digest for intermittent / streaming / etc workloads:

```php
use Awesomized\Checksums\Crc64;
use Awesomized\Checksums\Crc64\Nvme;

/** @var \FFI $crc64FastNvme */

$crc64Digest = new Crc64\Nvme(
$crc64Digest = new Nvme\Computer(
crc64Nvme: $crc64FastNvme,
);

Expand Down
31 changes: 31 additions & 0 deletions cli/calculateCrc32IsoHdlc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

use Awesomized\Checksums\Crc32;

if (!isset($argv[1]) || '' === $argv[1]) {
echo 'Usage: php calculateCrc32.php <string or file>' . PHP_EOL;

exit(1);
}

require __DIR__ . '/../vendor/autoload.php';

$ffi = Crc32\IsoHdlc\Ffi::fromHeaderFile();

if (is_readable($argv[1])) {
echo Crc32\IsoHdlc\Computer::calculateFile(
ffi: $ffi,
filename: $argv[1],
) . PHP_EOL;

exit(0);
}

echo Crc32\IsoHdlc\Computer::calculate(
ffi: $ffi,
string: $argv[1],
) . PHP_EOL;

$contents = file_get_contents('/Users/onethumb/Downloads/frankenphp-mac-arm64');
8 changes: 4 additions & 4 deletions cli/calculate.php → cli/calculateCrc64Nvme.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@
use Awesomized\Checksums\Crc64;

if (!isset($argv[1]) || '' === $argv[1]) {
echo 'Usage: php calculateString.php <string or file>' . PHP_EOL;
echo 'Usage: php calculateCrc64Nvme.php <string or file>' . PHP_EOL;

exit(1);
}

require __DIR__ . '/../vendor/autoload.php';

$ffi = Crc64\Ffi::fromHeaderFile();
$ffi = Crc64\Nvme\Ffi::fromHeaderFile();

if (is_readable($argv[1])) {
echo Crc64\Nvme::calculateFile(
echo Crc64\Nvme\Computer::calculateFile(
ffi: $ffi,
filename: $argv[1],
) . PHP_EOL;

exit(0);
}

echo Crc64\Nvme::calculate(
echo Crc64\Nvme\Computer::calculate(
ffi: $ffi,
string: $argv[1],
) . PHP_EOL;
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
}
},
"require": {
"php": "^8.3||^8.4"
"php": "^8.3||^8.4",
"ext-ffi": "*"
},
"autoload": {
"psr-4": {
Expand Down
12 changes: 12 additions & 0 deletions crc32iso-hdlc-darwin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#define FFI_SCOPE "CRC32ISOHDLC"
#define FFI_LIB "build/crc32fast-lib-rust/target/release/libcrc32fast_lib.dylib"

typedef struct HasherHandle HasherHandle;

HasherHandle *hasher_new();

void hasher_write(HasherHandle *handle, const char *data, uintptr_t len);

uint32_t hasher_finalize(HasherHandle *handle);

uint32_t crc32_hash(const char *data, uintptr_t len);
12 changes: 12 additions & 0 deletions crc32iso-hdlc-linux.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#define FFI_SCOPE "CRC32ISOHDLC"
#define FFI_LIB "build/crc32fast-lib-rust/target/release/libcrc32fast_lib.so"

typedef struct HasherHandle HasherHandle;

HasherHandle *hasher_new();

void hasher_write(HasherHandle *handle, const char *data, uintptr_t len);

uint32_t hasher_finalize(HasherHandle *handle);

uint32_t crc32_hash(const char *data, uintptr_t len);
12 changes: 12 additions & 0 deletions crc32iso-hdlc-windows.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#define FFI_SCOPE "CRC32ISOHDLC"
#define FFI_LIB "build/crc32fast-lib-rust/target/release/libcrc32fast_lib.dll"

typedef struct HasherHandle HasherHandle;

HasherHandle *hasher_new();

void hasher_write(HasherHandle *handle, const char *data, uintptr_t len);

uint32_t hasher_finalize(HasherHandle *handle);

uint32_t crc32_hash(const char *data, uintptr_t len);
2 changes: 1 addition & 1 deletion crc64nvme-darwin.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#define FFI_SCOPE "CRC64NVME"
#define FFI_LIB "build/target/release/libcrc64fast_nvme.dylib"
#define FFI_LIB "build/crc64fast-nvme/target/release/libcrc64fast_nvme.dylib"

typedef struct DigestHandle DigestHandle;

Expand Down
2 changes: 1 addition & 1 deletion crc64nvme-linux.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#define FFI_SCOPE "CRC64NVME"
#define FFI_LIB "build/target/release/libcrc64fast_nvme.so"
#define FFI_LIB "build/crc64fast-nvme/target/release/libcrc64fast_nvme.so"

typedef struct DigestHandle DigestHandle;

Expand Down
2 changes: 1 addition & 1 deletion crc64nvme-windows.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#define FFI_SCOPE "CRC64NVME"
#define FFI_LIB "build/target/release/libcrc64fast_nvme.dll"
#define FFI_LIB "build/crc64fast-nvme/target/release/libcrc64fast_nvme.dll"

typedef struct DigestHandle DigestHandle;

Expand Down
11 changes: 8 additions & 3 deletions preload.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php

Check warning on line 1 in preload.php

View workflow job for this annotation

GitHub Actions / code-standards

Found violation(s) of type: phpdoc_order

/**
* Script suitable for preloading the FFI library.
Expand All @@ -11,12 +11,17 @@

declare(strict_types=1);

use Awesomized\Checksums\Crc64;
use Awesomized\Checksums;

require 'src/FfiInterface.php';
require 'src/FfiTrait.php';
require 'src/Crc64/Ffi.php';
require 'src/Crc64/Nvme/Ffi.php';
require 'src/Crc32/IsoHdlc/Ffi.php';

\FFI::load(
Crc64\Ffi::whichHeaderFile(),
Checksums\Crc64\Nvme\Ffi::whichHeaderFile(),
);

\FFI::load(
Checksums\Crc32\IsoHdlc\Ffi::whichHeaderFile(),
);
65 changes: 65 additions & 0 deletions src/ChecksumTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Awesomized\Checksums;

use FFI;

/**
* Supplies shared methods among different checksum implementations.
*/
trait ChecksumTrait
{
public static function calculate(
FFI $ffi,
string $string,
): string {
return (new self($ffi))
->write(
string: $string,
)
->sum();
}

public static function calculateFile(
FFI $ffi,
string $filename,
int $readChunkSize = self::READ_CHUNK_SIZE_DEFAULT,
): string {
$handle = fopen(
filename: $filename,
mode: 'rb',
);

if (false === $handle) {
throw new \InvalidArgumentException(
message: "Could not open file: {$filename}",
);
}

$computer = new self($ffi);

while (
!feof(
stream: $handle,
)
) {
$chunk = fread(
stream: $handle,
length: $readChunkSize,
);
if (false !== $chunk) {
$computer->write(
string: $chunk,
);
}
}

fclose(
stream: $handle,
);

return $computer->sum();
}
}
Loading
Loading