diff --git a/.gitignore b/.gitignore index ff164f3..1ae7311 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ tmp-php.ini .idea *~ vendor -scratch \ No newline at end of file +scratch +.vscode diff --git a/.kiro/specs/custom-crc-parameters/design.md b/.kiro/specs/custom-crc-parameters/design.md new file mode 100644 index 0000000..469bce8 --- /dev/null +++ b/.kiro/specs/custom-crc-parameters/design.md @@ -0,0 +1,205 @@ +# Design Document + +## Overview + +This design extends the existing PHP CRC extension to support custom CRC parameters while maintaining full backward compatibility. The underlying crc_fast library has been updated with custom parameter support through the `CrcFastParams` struct and related functions. We will expose this functionality through a new `CrcFast\Params` class and extend existing functions to accept either algorithm constants or custom parameter objects. + +## Architecture + +### Core Components + +1. **CrcFast\Params Class**: A new PHP class that wraps the C `CrcFastParams` struct +2. **Extended Function Signatures**: Modify existing functions to accept both `int` (algorithm constants) and `CrcFast\Params` objects +3. **Helper Functions**: Add utility functions for creating custom parameters from Rocksoft model parameters +4. **Backward Compatibility Layer**: Ensure all existing code continues to work without modification + +### Design Principles + +- **Backward Compatibility**: All existing APIs must continue to work exactly as before +- **Type Safety**: Use PHP's type system to validate parameters at runtime +- **Performance**: Custom parameters should not impact performance of predefined algorithms +- **Consistency**: Custom parameters should work seamlessly with all existing functions + +## Components and Interfaces + +### CrcFast\Params Class + +```php +namespace CrcFast { + class Params { + public function __construct( + int $width, + int $poly, + int $init, + bool $refin, + bool $refout, + int $xorout, + int $check, + ?array $keys = null // Optional array of 23 pre-computed keys for performance + ); + + public function getWidth(): int; + public function getPoly(): int; + public function getInit(): int; + public function getRefin(): bool; + public function getRefout(): bool; + public function getXorout(): int; + public function getCheck(): int; + public function getKeys(): array; // Returns the keys (generated if not provided in constructor) + } +} +``` + +### Direct Parameter Construction + +Users can directly create custom CRC parameters using the `CrcFast\Params` constructor. The constructor accepts the standard Rocksoft model parameters and automatically handles the conversion internally, including generating the required keys. + +### Extended Function Signatures + +All existing functions will be modified to accept `int|CrcFast\Params` for the algorithm parameter: + +```php +namespace CrcFast { + function hash(int|Params $algorithm, string $data, bool $binary = false): string; + function hash_file(int|Params $algorithm, string $filename, bool $binary = false): string; + function combine(int|Params $algorithm, string $checksum1, string $checksum2, int $length2, bool $binary = false): string; + + class Digest { + public function __construct(int|Params $algorithm); + // ... other methods remain unchanged + } +} +``` + +## Data Models + +### PHP CrcFast\Params Object Structure + +```c +typedef struct _php_crc_fast_params_obj { + CrcFastParams params; // C struct from libcrc_fast.h + zend_object std; +} php_crc_fast_params_obj; +``` + +### Parameter Validation Rules + +- **Width**: Must be 32 or 64 bits (the only widths supported by the crc_fast library) +- **Polynomial**: Must fit within the specified width +- **Init/Xorout**: Must fit within the specified width +- **Check**: Used for validation - computed checksum of "123456789" must match this value +- **Keys**: If provided, must be an array of exactly 23 integer values. If null, keys will be computed on-demand by the C library + +## Error Handling + +### Parameter Validation Errors + +- **InvalidArgumentException**: For invalid parameter values (width, polynomial out of range) +- **ValueError**: For parameters that don't produce the expected check value +- **TypeError**: For incorrect parameter types in function calls + +### Runtime Errors + +- **RuntimeException**: For memory allocation failures or C library errors +- **Exception**: For general digest operation failures + +### Error Messages + +All error messages will be descriptive and indicate: +- Which parameter is invalid +- What the valid range/values are +- How to correct the issue + +## Testing Strategy + +### Unit Tests + +1. **Parameter Validation Tests** + - Valid parameter combinations + - Invalid parameter combinations + - Edge cases (min/max values) + +2. **Functionality Tests** + - Custom parameters with hash() function + - Custom parameters with hash_file() function + - Custom parameters with combine() function + - Custom parameters with Digest class + +3. **Backward Compatibility Tests** + - All existing tests must continue to pass + - Existing code patterns must work unchanged + +4. **Integration Tests** + - Custom parameters producing known checksums + - Rocksoft model parameter conversion + - Cross-validation with reference implementations + +### Test Data + +- Use well-known CRC algorithms as test cases (e.g., CRC-32/ISCSI, CRC-32/ISO-HDLC, CRC-64/NVME) +- Include edge cases for both width=32 and width=64 +- Test both reflected and non-reflected algorithms +- Validate custom parameters by recreating existing predefined algorithms and comparing results + +## Implementation Details + +### C Extension Changes + +1. **New Object Type**: Add `php_crc_fast_params_obj` structure and handlers +2. **Parameter Detection**: Add helper functions to detect if parameter is int or Params object +3. **C Struct Conversion**: Convert PHP Params object to C `CrcFastParams` struct +4. **Function Overloading**: Modify existing functions to handle both parameter types + +### Memory Management + +- **Params Objects**: Standard PHP object lifecycle management +- **C Structs**: Stack-allocated `CrcFastParams` structs for function calls +- **Digest Objects**: Extended to store either algorithm constant or custom parameters + +### Performance Considerations + +- **Fast Path**: Predefined algorithms use existing fast path with `crc_fast_digest_new()` +- **Custom Path**: Custom parameters use `crc_fast_digest_new_with_params()` and related functions +- **Validation**: Validate parameters once during PHP object creation +- **Key Pre-computation**: When keys are provided in constructor, they are used directly for optimal performance. When keys are null, they are computed once during object creation and stored in the C struct +- **Key Reuse**: The C library handles key management internally - once computed, keys are reused for all operations on that digest + +## Backward Compatibility Guarantees + +### API Compatibility + +- All existing function signatures remain valid +- All existing constants remain unchanged +- All existing behavior is preserved exactly + +### Binary Compatibility + +- Extension version will be incremented appropriately +- No breaking changes to existing compiled code +- Graceful handling of version mismatches + +### Migration Path + +- Existing code requires no changes +- New functionality is opt-in only +- Clear documentation for adopting custom parameters + +## Security Considerations + +### Input Validation + +- All custom parameters are validated before use +- Polynomial values are checked for validity +- Width constraints are enforced strictly + +### Memory Safety + +- All C struct operations are bounds-checked +- PHP object lifecycle prevents use-after-free +- Error handling prevents undefined behavior + +### Resource Limits + +- Custom parameter objects have reasonable memory footprint +- No unbounded resource allocation +- Proper cleanup on errors \ No newline at end of file diff --git a/.kiro/specs/custom-crc-parameters/requirements.md b/.kiro/specs/custom-crc-parameters/requirements.md new file mode 100644 index 0000000..986e244 --- /dev/null +++ b/.kiro/specs/custom-crc-parameters/requirements.md @@ -0,0 +1,72 @@ +# Requirements Document + +## Introduction + +This feature extends the existing PHP CRC extension to support custom CRC parameters, allowing users to define their own CRC algorithms beyond the predefined set. The underlying crc_fast library has been updated with custom parameter support, and we need to expose this functionality through the PHP extension while maintaining full backward compatibility with existing APIs. + +## Requirements + +### Requirement 1 + +**User Story:** As a PHP developer, I want to create custom CRC algorithms with specific parameters, so that I can calculate checksums for proprietary or specialized CRC variants not included in the predefined algorithms. + +#### Acceptance Criteria + +1. WHEN a user provides custom CRC parameters (width, polynomial, init, refin, refout, xorout, check) THEN the system SHALL create a custom CRC algorithm instance +2. WHEN custom parameters are invalid (e.g., unsupported width) THEN the system SHALL throw a descriptive exception +3. WHEN custom parameters are valid THEN the system SHALL allow all existing operations (hash, hash_file, combine, Digest class) to work with the custom algorithm +4. IF custom parameters match a predefined algorithm THEN the system SHALL still function correctly without optimization requirements + +### Requirement 2 + +**User Story:** As a PHP developer, I want to use a helper function to create custom CRC parameters from Rocksoft model parameters, so that I can easily convert standard CRC specifications into the required parameter format. + +#### Acceptance Criteria + +1. WHEN a user provides Rocksoft model parameters (name, width, poly, init, reflected, xorout, check) THEN the system SHALL return a custom CRC parameter object +2. WHEN Rocksoft parameters are invalid THEN the system SHALL throw a descriptive exception +3. WHEN the helper function is called THEN the system SHALL validate all parameters before creating the custom parameter object + +### Requirement 3 + +**User Story:** As a PHP developer, I want all existing CRC functions to accept custom parameter objects, so that I can use custom algorithms seamlessly with the current API. + +#### Acceptance Criteria + +1. WHEN CrcFast\hash() receives a custom parameter object instead of an algorithm constant THEN the system SHALL calculate the checksum using the custom algorithm +2. WHEN CrcFast\hash_file() receives a custom parameter object THEN the system SHALL calculate the file checksum using the custom algorithm +3. WHEN CrcFast\combine() receives a custom parameter object THEN the system SHALL combine checksums using the custom algorithm +4. WHEN the Digest class constructor receives a custom parameter object THEN the system SHALL create a digest instance for the custom algorithm + +### Requirement 4 + +**User Story:** As a PHP developer, I want the extension to maintain backward compatibility, so that my existing code continues to work without modifications after the update. + +#### Acceptance Criteria + +1. WHEN existing code uses predefined algorithm constants THEN the system SHALL continue to work exactly as before +2. WHEN existing function signatures are used THEN the system SHALL maintain the same behavior and return types +3. WHEN existing class methods are called THEN the system SHALL maintain the same behavior and return types +4. IF new functionality is added THEN the system SHALL not break any existing functionality + +### Requirement 5 + +**User Story:** As a PHP developer, I want proper error handling for custom parameters, so that I can debug issues with my custom CRC configurations. + +#### Acceptance Criteria + +1. WHEN invalid custom parameters are provided THEN the system SHALL throw specific exceptions with clear error messages +2. WHEN custom parameter validation fails THEN the system SHALL indicate which parameter is invalid and why +3. WHEN custom algorithms fail during computation THEN the system SHALL provide meaningful error information +4. WHEN memory allocation fails for custom algorithms THEN the system SHALL handle the error gracefully + +### Requirement 6 + +**User Story:** As a PHP developer, I want the stub file to remain the source of truth for the API, so that the extension maintains consistency with PHP's standard practices. + +#### Acceptance Criteria + +1. WHEN API changes are made THEN the system SHALL update the stub file first +2. WHEN the arginfo file needs updates THEN the system SHALL regenerate it from the stub file +3. WHEN new functions or classes are added THEN the system SHALL define them in the stub file +4. WHEN documentation is updated THEN the system SHALL update it in the stub file \ No newline at end of file diff --git a/.kiro/specs/custom-crc-parameters/tasks.md b/.kiro/specs/custom-crc-parameters/tasks.md new file mode 100644 index 0000000..68d10ed --- /dev/null +++ b/.kiro/specs/custom-crc-parameters/tasks.md @@ -0,0 +1,130 @@ +# Implementation Plan + +- [x] 1. Update stub file with new CrcFast\Params class and function signatures + - Add CrcFast\Params class definition with constructor and getter methods + - Update existing function signatures to accept int|CrcFast\Params for algorithm parameter + - _Requirements: 1.1, 3.1, 6.1, 6.3_ + +- [x] 2. Regenerate arginfo file from updated stub + - Run PHP's gen_stub.php to regenerate crc_fast_arginfo.h from the updated stub file + - Verify that new function signatures and class definitions are properly generated + - _Requirements: 6.2, 6.4_ + +- [x] 3. Implement CrcFast\Params class in C extension + - [x] 3.1 Define php_crc_fast_params_obj structure and object handlers + - Create structure containing CrcFastParams and zend_object + - Implement create_object, free_obj, and other standard object handlers + - _Requirements: 1.1, 5.1_ + + - [x] 3.2 Implement CrcFast\Params constructor + - Parse constructor parameters (width, poly, init, refin, refout, xorout, check, keys) + - Validate parameter values according to design constraints + - Create and populate CrcFastParams struct + - Handle optional keys parameter (generate if null, validate if provided) + - _Requirements: 1.1, 1.2, 2.2, 5.2_ + + - [x] 3.3 Implement CrcFast\Params getter methods + - Implement getWidth(), getPoly(), getInit(), getRefin(), getRefout(), getXorout(), getCheck() + - Implement getKeys() method that always returns the computed keys array + - _Requirements: 1.1_ + + + +- [x] 4. Update existing functions to support custom parameters + - [x] 4.1 Create parameter detection helper function + - Add helper to determine if parameter is int (algorithm constant) or CrcFast\Params object + - Extract CrcFastParams struct from Params object when needed + - _Requirements: 3.1, 3.2, 3.3_ + + - [x] 4.2 Update CrcFast\hash() function + - Modify function to accept int|CrcFast\Params for algorithm parameter + - Use crc_fast_checksum() for predefined algorithms + - Use crc_fast_checksum_with_params() for custom parameters + - Maintain existing behavior for all predefined algorithms + - _Requirements: 3.1, 4.1, 4.2_ + + - [x] 4.3 Update CrcFast\hash_file() function + - Modify function to accept int|CrcFast\Params for algorithm parameter + - Use crc_fast_checksum_file() for predefined algorithms + - Use crc_fast_checksum_file_with_params() for custom parameters + - Maintain existing behavior for all predefined algorithms + - _Requirements: 3.2, 4.1, 4.2_ + + - [x] 4.4 Update CrcFast\combine() function + - Modify function to accept int|CrcFast\Params for algorithm parameter + - Use crc_fast_checksum_combine() for predefined algorithms + - Use crc_fast_checksum_combine_with_custom_params() for custom parameters + - Maintain existing checksum parsing logic for both binary and hex inputs + - _Requirements: 3.3, 4.1, 4.2_ + +- [x] 5. Update CrcFast\Digest class to support custom parameters + - [x] 5.1 Modify Digest constructor + - Update constructor to accept int|CrcFast\Params for algorithm parameter + - Use crc_fast_digest_new() for predefined algorithms + - Use crc_fast_digest_new_with_params() for custom parameters + - Store parameter type information for later use in formatting + - _Requirements: 3.4, 4.1, 4.2_ + + - [x] 5.2 Update result formatting for custom parameters + - Modify php_crc_fast_format_result() to handle custom parameters + - Determine output width (32 or 64 bit) from custom parameters + - Ensure binary and hex output formats work correctly for custom algorithms + - _Requirements: 1.3, 4.3_ + +- [x] 6. Add comprehensive error handling + - Add parameter validation with descriptive error messages + - Handle C library errors gracefully with appropriate PHP exceptions + - Validate keys array length and values when provided + - _Requirements: 5.1, 5.2, 5.3, 5.4_ + +- [x] 7. Create unit tests for CrcFast\Params class + - Test constructor with valid parameters + - Test constructor with invalid parameters (wrong width, out-of-range values) + - Test getter methods return correct values + - Test keys parameter handling (both provided and auto-generated) + - _Requirements: 1.1, 1.2, 5.1, 5.2_ + + + +- [x] 8. Create integration tests for custom parameters with existing functions + - [x] 8.1 Test CrcFast\hash() with custom parameters + - Create custom parameters that match existing predefined algorithms + - Verify identical results between custom and predefined algorithms + - Test both 32-bit and 64-bit custom algorithms + - _Requirements: 3.1, 4.1_ + + - [x] 8.2 Test CrcFast\hash_file() with custom parameters + - Test file hashing with custom parameters + - Verify results match hash() function for same data + - Test both binary and hex output formats + - _Requirements: 3.2, 4.1_ + + - [x] 8.3 Test CrcFast\combine() with custom parameters + - Test checksum combination with custom parameters + - Verify results match manual calculation + - Test with both binary and hex checksum inputs + - _Requirements: 3.3, 4.1_ + + - [x] 8.4 Test CrcFast\Digest class with custom parameters + - Test digest creation, update, and finalization with custom parameters + - Test digest combination with custom parameters + - Verify results match direct hash() function calls + - _Requirements: 3.4, 4.1_ + +- [x] 9. Create backward compatibility tests + - Run all existing tests to ensure no regressions + - Verify all existing function signatures still work + - Verify all existing constants and behavior unchanged + - _Requirements: 4.1, 4.2, 4.3_ + +- [x] 10. Build and test extension + - Compile extension with updated code + - Run complete test suite to verify all functionality + - Test extension loading and basic functionality + - _Requirements: All requirements verification_ + +- [x] 11. Fix build warnings for zend_long format specifiers + - Fix all format string warnings by using proper format specifiers for zend_long type + - Change %ld to %lld and %lx to %llx for zend_long variables + - Rebuild and verify all tests still pass with clean compilation + - _Requirements: Code quality and maintainability_ \ No newline at end of file diff --git a/.kiro/steering/essential-commands.md b/.kiro/steering/essential-commands.md new file mode 100644 index 0000000..307b361 --- /dev/null +++ b/.kiro/steering/essential-commands.md @@ -0,0 +1,48 @@ +# Essential Commands for CRC Fast PHP Extension + +## Critical Commands - NEVER FORGET THESE + +### Testing +- **ALWAYS** run tests with: `NO_INTERACTION=1 make test` +- **NEVER** use `php run-tests.php` directly +- **NEVER** use `make test` without `NO_INTERACTION=1` +- **ALWAYS** write tests in the `tests/` directory using `.phpt` format +- **ALWAYS** follow the existing test architecture and naming conventions +- **NEVER** create tests outside the `tests/` directory + +### Code Generation +- **ALWAYS** regenerate arginfo after modifying stub files: `php build/gen_stub.php crc_fast.stub.php` +- This must be done whenever `crc_fast.stub.php` is modified + +### Build Process +1. `make clean` - Clean previous builds +2. `make` - Compile the extension +3. `NO_INTERACTION=1 make test` - Run tests + +## Why These Commands Matter +- `NO_INTERACTION=1 make test` prevents interactive prompts and ensures tests run completely +- `php build/gen_stub.php` generates the required arginfo headers from stub files +- Using the wrong test command leads to incomplete or incorrect test results + +## Before Any Code Changes +1. Always run `NO_INTERACTION=1 make test` to ensure existing functionality works +2. After making changes, rebuild and test again +3. Never assume tests pass without running them properly + +## Test Writing Guidelines +- All tests must be written as `.phpt` files in the `tests/` directory +- Follow the existing test structure: `--TEST--`, `--FILE--`, `--EXPECT--` sections +- Test file names should be descriptive and follow existing patterns +- Always test both success and error cases +- Include tests for parameter validation and error handling +- Run tests after writing them to ensure they work correctly + +## Test Debugging +- **NEVER** run PHP directly to test - always use `NO_INTERACTION=1 make test` +- When tests fail, check the generated output files for debugging: + - `tests/[testname].out` - Contains the actual output from the test + - `tests/[testname].log` - Contains detailed test execution log + - `tests/[testname].diff` - Shows the difference between expected and actual output + - `tests/[testname].exp` - Contains the expected output +- These files are automatically generated when tests run and are essential for debugging test failures +- Always read the `.out` file first to see what the test actually produced \ No newline at end of file diff --git a/crc_fast.stub.php b/crc_fast.stub.php index 8ac4797..99ce134 100644 --- a/crc_fast.stub.php +++ b/crc_fast.stub.php @@ -76,29 +76,129 @@ /** @var int */ const CRC_64_XZ = 20060; + /** + * Custom CRC parameters class for defining custom CRC algorithms. + */ + class Params + { + /** + * @param int $width CRC width (32 or 64 bits) + * @param int $poly CRC polynomial + * @param int $init Initial CRC value + * @param bool $refin Reflect input bytes + * @param bool $refout Reflect output CRC + * @param int $xorout XOR output with this value + * @param int $check Expected CRC of "123456789" + * @param array|null $keys Optional pre-computed keys array (23 elements) + */ + public function __construct( + int $width, + int $poly, + int $init, + bool $refin, + bool $refout, + int $xorout, + int $check, + ?array $keys = null + ) { + } + + /** + * Gets the CRC width. + * + * @return int + */ + public function getWidth(): int + { + } + + /** + * Gets the CRC polynomial. + * + * @return int + */ + public function getPoly(): int + { + } + + /** + * Gets the initial CRC value. + * + * @return int + */ + public function getInit(): int + { + } + + /** + * Gets whether input bytes are reflected. + * + * @return bool + */ + public function getRefin(): bool + { + } + + /** + * Gets whether output CRC is reflected. + * + * @return bool + */ + public function getRefout(): bool + { + } + + /** + * Gets the XOR output value. + * + * @return int + */ + public function getXorout(): int + { + } + + /** + * Gets the expected CRC check value. + * + * @return int + */ + public function getCheck(): int + { + } + + /** + * Gets the pre-computed keys array. + * + * @return array + */ + public function getKeys(): array + { + } + } + /** * Calculates the CRC checksum of the given data. * - * @param int $algorithm - * @param string $data - * @param bool $binary Output binary string or hex? + * @param int|Params $algorithm + * @param string $data + * @param bool $binary Output binary string or hex? * * @return string */ - function hash(int $algorithm, string $data, bool $binary = false): string + function hash(int|Params $algorithm, string $data, bool $binary = false): string { } /** * Calculates the CRC checksum of the given file. * - * @param int $algorithm - * @param string $filename - * @param bool $binary Output binary string or hex? + * @param int|Params $algorithm + * @param string $filename + * @param bool $binary Output binary string or hex? * * @return string */ - function hash_file(int $algorithm, string $filename, bool $binary = false): string + function hash_file(int|Params $algorithm, string $filename, bool $binary = false): string { } @@ -114,16 +214,16 @@ function get_supported_algorithms(): array /** * Combines two CRC checksums into one. * - * @param int $algorithm - * @param string $checksum1 The first checksum to combine (binary or hex) - * @param string $checksum2 The second checksum to combine (binary or hex) - * @param int $length2 The length of the _input_ to the second checksum - * @param bool $binary Output binary string or hex? + * @param int|Params $algorithm + * @param string $checksum1 The first checksum to combine (binary or hex) + * @param string $checksum2 The second checksum to combine (binary or hex) + * @param int $length2 The length of the _input_ to the second checksum + * @param bool $binary Output binary string or hex? * * @return string */ function combine( - int $algorithm, + int|Params $algorithm, string $checksum1, string $checksum2, int $length2, @@ -148,9 +248,9 @@ function crc32(string $data): int class Digest { /** - * @param int $algorithm + * @param int|Params $algorithm */ - public function __construct(int $algorithm) + public function __construct(int|Params $algorithm) { } diff --git a/crc_fast_arginfo.h b/crc_fast_arginfo.h index d02ab83..b14245f 100644 --- a/crc_fast_arginfo.h +++ b/crc_fast_arginfo.h @@ -1,14 +1,14 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c975a357b8cfe8f140279cc7303e17f08c0794f6 */ + * Stub hash: 96eb8b181c4a140e8c3740856b2c851cbfc30cbb */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_CrcFast_hash, 0, 2, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, algorithm, IS_LONG, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, algorithm, CrcFast\\Params, MAY_BE_LONG, NULL) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, binary, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_CrcFast_hash_file, 0, 2, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, algorithm, IS_LONG, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, algorithm, CrcFast\\Params, MAY_BE_LONG, NULL) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, binary, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() @@ -17,7 +17,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_CrcFast_get_supported_algorithms ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_CrcFast_combine, 0, 4, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, algorithm, IS_LONG, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, algorithm, CrcFast\\Params, MAY_BE_LONG, NULL) ZEND_ARG_TYPE_INFO(0, checksum1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, checksum2, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, length2, IS_LONG, 0) @@ -28,8 +28,37 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_CrcFast_crc32, 0, 1, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CrcFast_Params___construct, 0, 0, 7) + ZEND_ARG_TYPE_INFO(0, width, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, poly, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, init, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, refin, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, refout, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, xorout, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, check, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, keys, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_CrcFast_Params_getWidth, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_CrcFast_Params_getPoly arginfo_class_CrcFast_Params_getWidth + +#define arginfo_class_CrcFast_Params_getInit arginfo_class_CrcFast_Params_getWidth + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_CrcFast_Params_getRefin, 0, 0, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_CrcFast_Params_getRefout arginfo_class_CrcFast_Params_getRefin + +#define arginfo_class_CrcFast_Params_getXorout arginfo_class_CrcFast_Params_getWidth + +#define arginfo_class_CrcFast_Params_getCheck arginfo_class_CrcFast_Params_getWidth + +#define arginfo_class_CrcFast_Params_getKeys arginfo_CrcFast_get_supported_algorithms + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CrcFast_Digest___construct, 0, 0, 1) - ZEND_ARG_TYPE_INFO(0, algorithm, IS_LONG, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, algorithm, CrcFast\\Params, MAY_BE_LONG, NULL) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_CrcFast_Digest_update, 0, 1, CrcFast\\Digest, 0) @@ -55,6 +84,15 @@ ZEND_FUNCTION(CrcFast_hash_file); ZEND_FUNCTION(CrcFast_get_supported_algorithms); ZEND_FUNCTION(CrcFast_combine); ZEND_FUNCTION(CrcFast_crc32); +ZEND_METHOD(CrcFast_Params, __construct); +ZEND_METHOD(CrcFast_Params, getWidth); +ZEND_METHOD(CrcFast_Params, getPoly); +ZEND_METHOD(CrcFast_Params, getInit); +ZEND_METHOD(CrcFast_Params, getRefin); +ZEND_METHOD(CrcFast_Params, getRefout); +ZEND_METHOD(CrcFast_Params, getXorout); +ZEND_METHOD(CrcFast_Params, getCheck); +ZEND_METHOD(CrcFast_Params, getKeys); ZEND_METHOD(CrcFast_Digest, __construct); ZEND_METHOD(CrcFast_Digest, update); ZEND_METHOD(CrcFast_Digest, finalize); @@ -73,6 +111,20 @@ static const zend_function_entry ext_functions[] = { }; +static const zend_function_entry class_CrcFast_Params_methods[] = { + ZEND_ME(CrcFast_Params, __construct, arginfo_class_CrcFast_Params___construct, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getWidth, arginfo_class_CrcFast_Params_getWidth, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getPoly, arginfo_class_CrcFast_Params_getPoly, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getInit, arginfo_class_CrcFast_Params_getInit, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getRefin, arginfo_class_CrcFast_Params_getRefin, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getRefout, arginfo_class_CrcFast_Params_getRefout, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getXorout, arginfo_class_CrcFast_Params_getXorout, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getCheck, arginfo_class_CrcFast_Params_getCheck, ZEND_ACC_PUBLIC) + ZEND_ME(CrcFast_Params, getKeys, arginfo_class_CrcFast_Params_getKeys, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + static const zend_function_entry class_CrcFast_Digest_methods[] = { ZEND_ME(CrcFast_Digest, __construct, arginfo_class_CrcFast_Digest___construct, ZEND_ACC_PUBLIC) ZEND_ME(CrcFast_Digest, update, arginfo_class_CrcFast_Digest_update, ZEND_ACC_PUBLIC) @@ -107,6 +159,16 @@ static void register_crc_fast_symbols(int module_number) REGISTER_LONG_CONSTANT("CrcFast\\CRC_64_XZ", 20060, CONST_PERSISTENT); } +static zend_class_entry *register_class_CrcFast_Params(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "CrcFast", "Params", class_CrcFast_Params_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + + return class_entry; +} + static zend_class_entry *register_class_CrcFast_Digest(void) { zend_class_entry ce, *class_entry; diff --git a/php_crc_fast.cpp b/php_crc_fast.cpp index 56d382e..a0194d1 100644 --- a/php_crc_fast.cpp +++ b/php_crc_fast.cpp @@ -40,6 +40,10 @@ uint64_t htonll(uint64_t x) { zend_class_entry *php_crc_fast_digest_ce; static zend_object_handlers php_crc_fast_digest_object_handlers; +/* CrcFast\Params class */ +zend_class_entry *php_crc_fast_params_ce; +static zend_object_handlers php_crc_fast_params_object_handlers; + /* Free the Digest object */ static void php_crc_fast_digest_free_obj(zend_object *object) { @@ -64,19 +68,59 @@ static zend_object *php_crc_fast_digest_create_object(zend_class_entry *ce) obj->std.handlers = &php_crc_fast_digest_object_handlers; obj->digest = NULL; obj->algorithm = 0; // Explicitly initialize to 0 to prevent garbage values + obj->is_custom = false; // Initialize to false + memset(&obj->custom_params, 0, sizeof(CrcFastParams)); // Initialize custom params to zero + + return &obj->std; +} + +/* Free the Params object */ +static void php_crc_fast_params_free_obj(zend_object *object) +{ + php_crc_fast_params_obj *obj = php_crc_fast_params_from_obj(object); + + // Free the allocated keys storage if it exists + if (obj->keys_storage) { + efree(obj->keys_storage); + obj->keys_storage = NULL; + } + + zend_object_std_dtor(&obj->std); +} + +/* Create a new Params object */ +static zend_object *php_crc_fast_params_create_object(zend_class_entry *ce) +{ + php_crc_fast_params_obj *obj = (php_crc_fast_params_obj*)ecalloc(1, sizeof(php_crc_fast_params_obj) + zend_object_properties_size(ce)); + + zend_object_std_init(&obj->std, ce); + object_properties_init(&obj->std, ce); + + obj->std.handlers = &php_crc_fast_params_object_handlers; + + // Initialize the CrcFastParams struct to zero + memset(&obj->params, 0, sizeof(CrcFastParams)); + obj->keys_storage = NULL; return &obj->std; } /* Helper function to format checksum output */ -static inline void php_crc_fast_format_result(INTERNAL_FUNCTION_PARAMETERS, zend_long algorithm, uint64_t result, zend_bool binary) +static inline void php_crc_fast_format_result(INTERNAL_FUNCTION_PARAMETERS, zend_long algorithm, uint64_t result, zend_bool binary, bool is_custom = false, uint8_t custom_width = 0) { + bool is_32bit; + + if (is_custom) { + // For custom parameters, use the width from the parameters + is_32bit = (custom_width == 32); + } else { + // For predefined algorithms, determine width based on algorithm constant + is_32bit = (algorithm <= PHP_CRC_FAST_CRC32_XFER); + } + if (binary) { // For binary output, return the raw bytes - size_t result_size; - - // Determine if this is a 32-bit or 64-bit algorithm - if (algorithm <= PHP_CRC_FAST_CRC32_XFER) { + if (is_32bit) { // 32-bit CRC uint32_t result32 = (uint32_t)result; result32 = htonl(result32); @@ -87,7 +131,7 @@ static inline void php_crc_fast_format_result(INTERNAL_FUNCTION_PARAMETERS, zend RETURN_STRINGL((char*)&result, sizeof(result)); } } else { - if (algorithm <= PHP_CRC_FAST_CRC32_XFER) { + if (is_32bit) { // 32-bit CRC char checksum_str[9]; // 8 hex digits + null terminator snprintf(checksum_str, sizeof(checksum_str), "%08x", (uint32_t)result); @@ -129,7 +173,9 @@ static inline CrcFastAlgorithm php_crc_fast_get_algorithm(zend_long algo) { case PHP_CRC_FAST_CRC32_PHP: return CrcFastAlgorithm::Crc32Bzip2; default: - zend_throw_exception(zend_ce_exception, "Invalid algorithm specified", 0); + zend_throw_exception_ex(zend_ce_exception, 0, + "Invalid algorithm constant %lld. Use CrcFast\\get_supported_algorithms() to see valid values", algo); + return CrcFastAlgorithm::Crc32IsoHdlc; // Default fallback } } @@ -150,6 +196,33 @@ static inline uint64_t php_crc_fast_reverse_bytes_if_needed(uint64_t result, zen return result; } +/* Helper function to detect parameter type and extract CrcFastParams if needed */ +static inline bool php_crc_fast_get_params_from_zval(zval *algorithm_zval, zend_long *algorithm_out, CrcFastParams *params_out) +{ + if (Z_TYPE_P(algorithm_zval) == IS_LONG) { + // It's an integer algorithm constant + *algorithm_out = Z_LVAL_P(algorithm_zval); + return false; // Not custom parameters + } else if (Z_TYPE_P(algorithm_zval) == IS_OBJECT && + instanceof_function(Z_OBJCE_P(algorithm_zval), php_crc_fast_params_ce)) { + // It's a CrcFast\Params object + php_crc_fast_params_obj *params_obj = Z_CRC_FAST_PARAMS_P(algorithm_zval); + if (!params_obj) { + zend_throw_exception(zend_ce_exception, "Invalid CrcFast\\Params object", 0); + return false; + } + *params_out = params_obj->params; + *algorithm_out = 0; // Not used for custom parameters + return true; // Custom parameters + } else { + // Invalid type - provide more descriptive error message + const char *type_name = zend_get_type_by_const(Z_TYPE_P(algorithm_zval)); + zend_throw_exception_ex(zend_ce_exception, 0, + "Algorithm parameter must be an integer constant or CrcFast\\Params object, %s given", type_name); + return false; + } +} + /* {{{ CrcFast\crc32(string $data): int */ PHP_FUNCTION(CrcFast_crc32) { @@ -167,55 +240,135 @@ PHP_FUNCTION(CrcFast_crc32) } /* }}} */ -/* {{{ CrcFast\hash(int $algorithm, string $data, bool $binary = false): string */ +/* {{{ CrcFast\hash(int|CrcFast\Params $algorithm, string $data, bool $binary = false): string */ PHP_FUNCTION(CrcFast_hash) { - zend_long algorithm; + zval *algorithm_zval; char *data; size_t data_len; zend_bool binary = 0; ZEND_PARSE_PARAMETERS_START(3, 3) - Z_PARAM_LONG(algorithm) + Z_PARAM_ZVAL(algorithm_zval) Z_PARAM_STRING(data, data_len) Z_PARAM_BOOL(binary) ZEND_PARSE_PARAMETERS_END(); - CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); - uint64_t result = crc_fast_checksum(algo, data, data_len); + // Validate data parameter + if (!data) { + zend_throw_exception(zend_ce_exception, "Data parameter cannot be null", 0); + return; + } + + zend_long algorithm; + CrcFastParams custom_params; + bool is_custom = php_crc_fast_get_params_from_zval(algorithm_zval, &algorithm, &custom_params); + + if (EG(exception)) { + return; // Exception was thrown by helper function + } + + uint64_t result; + if (is_custom) { + // Use custom parameters - handle potential C library errors + try { + result = crc_fast_checksum_with_params(custom_params, data, data_len); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to compute CRC checksum with custom parameters", 0); + return; + } + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, result, binary, true, custom_params.width); + } else { + // Use predefined algorithm - handle potential C library errors + CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + if (EG(exception)) { + return; // Exception was thrown by get_algorithm + } + + try { + result = crc_fast_checksum(algo, data, data_len); + } catch (...) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Failed to compute CRC checksum for algorithm %lld", algorithm); + return; + } - // Apply byte reversal if needed - result = php_crc_fast_reverse_bytes_if_needed(result, algorithm); + // Apply byte reversal if needed + result = php_crc_fast_reverse_bytes_if_needed(result, algorithm); - php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, algorithm, result, binary); + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, algorithm, result, binary); + } } /* }}} */ -/* {{{ CrcFast\hash_file(int $algorithm, string $filename, bool $binary = false, ?int $chunk_size = null): string */ +/* {{{ CrcFast\hash_file(int|CrcFast\Params $algorithm, string $filename, bool $binary = false, ?int $chunk_size = null): string */ PHP_FUNCTION(CrcFast_hash_file) { - zend_long algorithm; + zval *algorithm_zval; char *filename; size_t filename_len; zend_bool binary = 0; zval *chunk_size_zval = NULL; ZEND_PARSE_PARAMETERS_START(3, 4) - Z_PARAM_LONG(algorithm) + Z_PARAM_ZVAL(algorithm_zval) Z_PARAM_STRING(filename, filename_len) Z_PARAM_BOOL(binary) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_OR_NULL(chunk_size_zval) ZEND_PARSE_PARAMETERS_END(); - CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + // Validate filename parameter + if (!filename || filename_len == 0) { + zend_throw_exception(zend_ce_exception, "Filename cannot be empty", 0); + return; + } + + // Check if file exists and is readable + if (php_check_open_basedir(filename)) { + zend_throw_exception_ex(zend_ce_exception, 0, "File '%s' is not within the allowed path(s)", filename); + return; + } + + zend_long algorithm; + CrcFastParams custom_params; + bool is_custom = php_crc_fast_get_params_from_zval(algorithm_zval, &algorithm, &custom_params); + + if (EG(exception)) { + return; // Exception was thrown by helper function + } - uint64_t result = crc_fast_checksum_file(algo, (const uint8_t*)filename, filename_len); + uint64_t result; + if (is_custom) { + // Use custom parameters - handle potential C library errors + try { + result = crc_fast_checksum_file_with_params(custom_params, (const uint8_t*)filename, filename_len); + } catch (...) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Failed to compute CRC checksum for file '%s' with custom parameters", filename); + return; + } + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, result, binary, true, custom_params.width); + } else { + // Use predefined algorithm - handle potential C library errors + CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + if (EG(exception)) { + return; // Exception was thrown by get_algorithm + } + + try { + result = crc_fast_checksum_file(algo, (const uint8_t*)filename, filename_len); + } catch (...) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Failed to compute CRC checksum for file '%s' with algorithm %lld", filename, algorithm); + return; + } - // Apply byte reversal if needed - result = php_crc_fast_reverse_bytes_if_needed(result, algorithm); + // Apply byte reversal if needed + result = php_crc_fast_reverse_bytes_if_needed(result, algorithm); - php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, algorithm, result, binary); + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, algorithm, result, binary); + } } /* }}} */ @@ -248,29 +401,52 @@ PHP_FUNCTION(CrcFast_get_supported_algorithms) } /* }}} */ -/* {{{ CrcFast\combine(int $algorithm, string $checksum1, string $checksum2, int $length2, bool $binary = false): string */ +/* {{{ CrcFast\combine(int|CrcFast\Params $algorithm, string $checksum1, string $checksum2, int $length2, bool $binary = false): string */ PHP_FUNCTION(CrcFast_combine) { - zend_long algorithm; + zval *algorithm_zval; char *checksum1, *checksum2; size_t checksum1_len, checksum2_len; zend_long length2; zend_bool binary = 0; ZEND_PARSE_PARAMETERS_START(5, 5) - Z_PARAM_LONG(algorithm) + Z_PARAM_ZVAL(algorithm_zval) Z_PARAM_STRING(checksum1, checksum1_len) Z_PARAM_STRING(checksum2, checksum2_len) Z_PARAM_LONG(length2) Z_PARAM_BOOL(binary) ZEND_PARSE_PARAMETERS_END(); - CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + // Validate parameters + if (!checksum1 || !checksum2) { + zend_throw_exception(zend_ce_exception, "Checksum parameters cannot be null", 0); + return; + } + + if (length2 < 0) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Length parameter must be non-negative, got %lld", length2); + return; + } + + zend_long algorithm; + CrcFastParams custom_params; + bool is_custom = php_crc_fast_get_params_from_zval(algorithm_zval, &algorithm, &custom_params); + + if (EG(exception)) { + return; // Exception was thrown by helper function + } uint64_t cs1 = 0, cs2 = 0; // Determine if we're dealing with 32-bit or 64-bit CRC - bool is_crc32 = (algorithm <= PHP_CRC_FAST_CRC32_XFER); + bool is_crc32; + if (is_custom) { + is_crc32 = (custom_params.width == 32); + } else { + is_crc32 = (algorithm <= PHP_CRC_FAST_CRC32_XFER); + } size_t expected_binary_size = is_crc32 ? 4 : 8; // 4 bytes for CRC32, 8 bytes for CRC64 size_t expected_hex_size = is_crc32 ? 8 : 16; // 8 hex chars for CRC32, 16 for CRC64 @@ -365,37 +541,110 @@ PHP_FUNCTION(CrcFast_combine) RETURN_FALSE; } - uint64_t result = crc_fast_checksum_combine(algo, cs1, cs2, length2); + uint64_t result; + if (is_custom) { + // Use custom parameters - handle potential C library errors + try { + result = crc_fast_checksum_combine_with_params(custom_params, cs1, cs2, length2); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to combine CRC checksums with custom parameters", 0); + return; + } + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, result, binary, true, custom_params.width); + } else { + // Use predefined algorithm - handle potential C library errors + CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + if (EG(exception)) { + return; // Exception was thrown by get_algorithm + } + + try { + result = crc_fast_checksum_combine(algo, cs1, cs2, length2); + } catch (...) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Failed to combine CRC checksums for algorithm %lld", algorithm); + return; + } - // Apply byte reversal if needed - result = php_crc_fast_reverse_bytes_if_needed(result, algorithm); + // Apply byte reversal if needed + result = php_crc_fast_reverse_bytes_if_needed(result, algorithm); - php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, algorithm, result, binary); + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, algorithm, result, binary); + } } /* }}} */ -/* {{{ CrcFast\Digest::__construct(int $algorithm) */ +/* {{{ CrcFast\Digest::__construct(int|CrcFast\Params $algorithm) */ PHP_METHOD(CrcFast_Digest, __construct) { php_crc_fast_digest_obj *obj = Z_CRC_FAST_DIGEST_P(getThis()); - zend_long algorithm; + zval *algorithm_zval; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_LONG(algorithm) + Z_PARAM_ZVAL(algorithm_zval) ZEND_PARSE_PARAMETERS_END(); - CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + if (!obj) { + zend_throw_exception(zend_ce_exception, "Failed to initialize Digest object", 0); + return; + } // Free previous digest if it exists if (obj->digest) { crc_fast_digest_free(obj->digest); + obj->digest = NULL; } - obj->digest = crc_fast_digest_new(algo); - obj->algorithm = algorithm; // Store algorithm ID explicitly + zend_long algorithm; + CrcFastParams custom_params; + bool is_custom = php_crc_fast_get_params_from_zval(algorithm_zval, &algorithm, &custom_params); + + if (EG(exception)) { + return; // Exception was thrown by helper function + } - if (!obj->digest) { - zend_throw_exception(zend_ce_exception, "Failed to create digest", 0); + if (is_custom) { + // Use custom parameters - handle potential C library errors + try { + obj->digest = crc_fast_digest_new_with_params(custom_params); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to create digest with custom parameters", 0); + return; + } + + if (!obj->digest) { + zend_throw_exception(zend_ce_exception, "C library failed to create digest with custom parameters", 0); + return; + } + + obj->is_custom = true; + obj->custom_params = custom_params; + obj->algorithm = 0; // Not used for custom parameters + } else { + // Use predefined algorithm - handle potential C library errors + CrcFastAlgorithm algo = php_crc_fast_get_algorithm(algorithm); + if (EG(exception)) { + return; // Exception was thrown by get_algorithm + } + + try { + obj->digest = crc_fast_digest_new(algo); + } catch (...) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Failed to create digest for algorithm %lld", algorithm); + return; + } + + if (!obj->digest) { + zend_throw_exception_ex(zend_ce_exception, 0, + "C library failed to create digest for algorithm %lld", algorithm); + return; + } + + obj->is_custom = false; + obj->algorithm = algorithm; + // Initialize custom_params to zero for safety + memset(&obj->custom_params, 0, sizeof(CrcFastParams)); } } /* }}} */ @@ -411,12 +660,29 @@ PHP_METHOD(CrcFast_Digest, update) Z_PARAM_STRING(data, data_len) ZEND_PARSE_PARAMETERS_END(); + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Digest object", 0); + return; + } + if (!obj->digest) { - zend_throw_exception(zend_ce_exception, "Digest object not initialized", 0); + zend_throw_exception(zend_ce_exception, "Digest object not initialized. Call constructor first", 0); + return; + } + + // Validate data parameter + if (!data) { + zend_throw_exception(zend_ce_exception, "Data parameter cannot be null", 0); return; } - crc_fast_digest_update(obj->digest, data, data_len); + // Handle potential C library errors + try { + crc_fast_digest_update(obj->digest, data, data_len); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to update digest with data", 0); + return; + } // Return $this for method chaining RETURN_ZVAL(getThis(), 1, 0); @@ -434,18 +700,33 @@ PHP_METHOD(CrcFast_Digest, finalize) Z_PARAM_BOOL(binary) ZEND_PARSE_PARAMETERS_END(); - if (!obj->digest) { - zend_throw_exception(zend_ce_exception, "Digest object not initialized", 0); + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Digest object", 0); return; } - uint64_t result = crc_fast_digest_finalize(obj->digest); + if (!obj->digest) { + zend_throw_exception(zend_ce_exception, "Digest object not initialized. Call constructor first", 0); + return; + } - // Apply byte reversal if needed - result = php_crc_fast_reverse_bytes_if_needed(result, obj->algorithm); + uint64_t result; + try { + result = crc_fast_digest_finalize(obj->digest); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to finalize digest", 0); + return; + } - // Format and return the result - php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, obj->algorithm, result, binary); + if (obj->is_custom) { + // Use custom parameter formatting + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, result, binary, true, obj->custom_params.width); + } else { + // Apply byte reversal if needed for predefined algorithms + result = php_crc_fast_reverse_bytes_if_needed(result, obj->algorithm); + // Format and return the result using predefined algorithm formatting + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, obj->algorithm, result, binary); + } } /* }}} */ @@ -456,12 +737,22 @@ PHP_METHOD(CrcFast_Digest, reset) ZEND_PARSE_PARAMETERS_NONE(); + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Digest object", 0); + return; + } + if (!obj->digest) { - zend_throw_exception(zend_ce_exception, "Digest object not initialized", 0); + zend_throw_exception(zend_ce_exception, "Digest object not initialized. Call constructor first", 0); return; } - crc_fast_digest_reset(obj->digest); + try { + crc_fast_digest_reset(obj->digest); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to reset digest", 0); + return; + } // Return $this for method chaining RETURN_ZVAL(getThis(), 1, 0); @@ -479,17 +770,32 @@ PHP_METHOD(CrcFast_Digest, finalizeReset) Z_PARAM_BOOL(binary) ZEND_PARSE_PARAMETERS_END(); - if (!obj->digest) { - zend_throw_exception(zend_ce_exception, "Digest object not initialized", 0); + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Digest object", 0); return; } - uint64_t result = crc_fast_digest_finalize_reset(obj->digest); + if (!obj->digest) { + zend_throw_exception(zend_ce_exception, "Digest object not initialized. Call constructor first", 0); + return; + } - // Apply byte reversal if needed - result = php_crc_fast_reverse_bytes_if_needed(result, obj->algorithm); + uint64_t result; + try { + result = crc_fast_digest_finalize_reset(obj->digest); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to finalize and reset digest", 0); + return; + } - php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, obj->algorithm, result, binary); + if (obj->is_custom) { + // Use custom parameter formatting + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, result, binary, true, obj->custom_params.width); + } else { + // Apply byte reversal if needed for predefined algorithms + result = php_crc_fast_reverse_bytes_if_needed(result, obj->algorithm); + php_crc_fast_format_result(INTERNAL_FUNCTION_PARAM_PASSTHRU, obj->algorithm, result, binary); + } } /* }}} */ @@ -504,24 +810,345 @@ PHP_METHOD(CrcFast_Digest, combine) Z_PARAM_OBJECT_OF_CLASS(other_zval, php_crc_fast_digest_ce) ZEND_PARSE_PARAMETERS_END(); + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Digest object", 0); + return; + } + if (!obj->digest) { - zend_throw_exception(zend_ce_exception, "Digest object not initialized", 0); + zend_throw_exception(zend_ce_exception, "Digest object not initialized. Call constructor first", 0); return; } other_obj = Z_CRC_FAST_DIGEST_P(other_zval); + if (!other_obj) { + zend_throw_exception(zend_ce_exception, "Invalid other Digest object", 0); + return; + } + if (!other_obj->digest) { - zend_throw_exception(zend_ce_exception, "Other digest object not initialized", 0); + zend_throw_exception(zend_ce_exception, "Other digest object not initialized. Call constructor first", 0); return; } - crc_fast_digest_combine(obj->digest, other_obj->digest); + try { + crc_fast_digest_combine(obj->digest, other_obj->digest); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to combine digest objects", 0); + return; + } // Return $this for method chaining RETURN_ZVAL(getThis(), 1, 0); } /* }}} */ +/* {{{ CrcFast\Params::__construct(int $width, int $poly, int $init, bool $refin, bool $refout, int $xorout, int $check, ?array $keys = null) */ +PHP_METHOD(CrcFast_Params, __construct) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + zend_long width, poly, init, xorout, check; + zend_bool refin, refout; + zval *keys_array = NULL; + + ZEND_PARSE_PARAMETERS_START(7, 8) + Z_PARAM_LONG(width) + Z_PARAM_LONG(poly) + Z_PARAM_LONG(init) + Z_PARAM_BOOL(refin) + Z_PARAM_BOOL(refout) + Z_PARAM_LONG(xorout) + Z_PARAM_LONG(check) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(keys_array) + ZEND_PARSE_PARAMETERS_END(); + + // Validate width - only 32 and 64 are supported + if (width != 32 && width != 64) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Invalid width %lld. Only 32 and 64 bit widths are supported", width); + return; + } + + // Validate parameters are not negative + if (poly < 0) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Polynomial value %lld cannot be negative", poly); + return; + } + + if (init < 0) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Init value %lld cannot be negative", init); + return; + } + + if (xorout < 0) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Xorout value %lld cannot be negative", xorout); + return; + } + + if (check < 0) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Check value %lld cannot be negative", check); + return; + } + + // Validate polynomial fits within width + uint64_t max_poly = (width == 32) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + if ((uint64_t)poly > max_poly) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Polynomial 0x%llx exceeds maximum value for %lld-bit width", poly, width); + return; + } + + // Validate init value fits within width + uint64_t max_init = (width == 32) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + if ((uint64_t)init > max_init) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Init value 0x%llx exceeds maximum value for %lld-bit width", init, width); + return; + } + + // Validate xorout value fits within width + uint64_t max_xorout = (width == 32) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + if ((uint64_t)xorout > max_xorout) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Xorout value 0x%llx exceeds maximum value for %lld-bit width", xorout, width); + return; + } + + // Validate check value fits within width + uint64_t max_check = (width == 32) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + if ((uint64_t)check > max_check) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Check value 0x%llx exceeds maximum value for %lld-bit width", check, width); + return; + } + + // Set up the CrcFastParams struct + obj->params.algorithm = (width == 32) ? CrcFastAlgorithm::Crc32Custom : CrcFastAlgorithm::Crc64Custom; + obj->params.width = (uint8_t)width; + obj->params.poly = (uint64_t)poly; + obj->params.init = (uint64_t)init; + obj->params.refin = refin; + obj->params.refout = refout; + obj->params.xorout = (uint64_t)xorout; + obj->params.check = (uint64_t)check; + + // Allocate memory for keys array (23 elements) + obj->keys_storage = (uint64_t*)emalloc(23 * sizeof(uint64_t)); + obj->params.key_count = 23; + obj->params.keys = obj->keys_storage; + + // Handle keys parameter + if (keys_array && Z_TYPE_P(keys_array) == IS_ARRAY) { + // Validate keys array has exactly 23 elements + if (zend_hash_num_elements(Z_ARRVAL_P(keys_array)) != 23) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Keys array must contain exactly 23 elements, got %d", + zend_hash_num_elements(Z_ARRVAL_P(keys_array))); + return; + } + + // Copy keys from PHP array to C array + zval *key_val; + int i = 0; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(keys_array), key_val) { + if (Z_TYPE_P(key_val) != IS_LONG) { + zend_throw_exception_ex(zend_ce_exception, 0, + "All keys must be integers, element %d is not an integer", i); + return; + } + + zend_long key_value = Z_LVAL_P(key_val); + if (key_value < 0) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Key values cannot be negative, element %d has value %lld", i, key_value); + return; + } + + obj->keys_storage[i] = (uint64_t)key_value; + i++; + } ZEND_HASH_FOREACH_END(); + } else { + // Generate keys using the C library helper function - handle potential errors + CrcFastParams temp_params; + try { + temp_params = crc_fast_get_custom_params( + "", // name is not used for key generation + (uint8_t)width, + (uint64_t)poly, + (uint64_t)init, + refin, + (uint64_t)xorout, + (uint64_t)check + ); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to generate keys for custom CRC parameters", 0); + return; + } + + // Copy the generated keys + memcpy(obj->keys_storage, temp_params.keys, 23 * sizeof(uint64_t)); + } + + // Validate the parameters by checking if they produce the expected check value + // This is done by computing CRC of "123456789" and comparing with check parameter + const char *test_data = "123456789"; + uint64_t computed_check; + + try { + computed_check = crc_fast_checksum_with_params(obj->params, test_data, 9); + } catch (...) { + zend_throw_exception(zend_ce_exception, "Failed to validate custom CRC parameters", 0); + return; + } + + if (computed_check != (uint64_t)check) { + zend_throw_exception_ex(zend_ce_exception, 0, + "Parameters validation failed: computed check 0x%016" PRIx64 " does not match expected check 0x%016" PRIx64 ". " + "Please verify your CRC parameters are correct", + computed_check, (uint64_t)check); + return; + } +} +/* }}} */ + +/* {{{ CrcFast\Params::getWidth(): int */ +PHP_METHOD(CrcFast_Params, getWidth) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_LONG((zend_long)obj->params.width); +} +/* }}} */ + +/* {{{ CrcFast\Params::getPoly(): int */ +PHP_METHOD(CrcFast_Params, getPoly) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_LONG((zend_long)obj->params.poly); +} +/* }}} */ + +/* {{{ CrcFast\Params::getInit(): int */ +PHP_METHOD(CrcFast_Params, getInit) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_LONG((zend_long)obj->params.init); +} +/* }}} */ + +/* {{{ CrcFast\Params::getRefin(): bool */ +PHP_METHOD(CrcFast_Params, getRefin) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_BOOL(obj->params.refin); +} +/* }}} */ + +/* {{{ CrcFast\Params::getRefout(): bool */ +PHP_METHOD(CrcFast_Params, getRefout) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_BOOL(obj->params.refout); +} +/* }}} */ + +/* {{{ CrcFast\Params::getXorout(): int */ +PHP_METHOD(CrcFast_Params, getXorout) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_LONG((zend_long)obj->params.xorout); +} +/* }}} */ + +/* {{{ CrcFast\Params::getCheck(): int */ +PHP_METHOD(CrcFast_Params, getCheck) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + RETURN_LONG((zend_long)obj->params.check); +} +/* }}} */ + +/* {{{ CrcFast\Params::getKeys(): array */ +PHP_METHOD(CrcFast_Params, getKeys) +{ + php_crc_fast_params_obj *obj = Z_CRC_FAST_PARAMS_P(getThis()); + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!obj) { + zend_throw_exception(zend_ce_exception, "Invalid Params object", 0); + return; + } + + array_init(return_value); + + // Add all 23 keys to the array + for (int i = 0; i < 23; i++) { + add_index_long(return_value, i, (zend_long)obj->params.keys[i]); + } +} +/* }}} */ + /* {{{ PHP_RINIT_FUNCTION */ PHP_RINIT_FUNCTION(crc_fast) { @@ -550,7 +1177,7 @@ PHP_MINFO_FUNCTION(crc_fast) /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(crc_fast) { -// Register constants and symbols + // Register constants and symbols register_crc_fast_symbols(0); // Register the Digest class using the auto-generated function @@ -561,9 +1188,23 @@ PHP_MINIT_FUNCTION(crc_fast) // Initialize the object handlers memcpy(&php_crc_fast_digest_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); - php_crc_fast_digest_object_handlers.offset = XtOffsetOf(php_crc_fast_digest_obj, std); + + // With std first, offset should be 0 + php_crc_fast_digest_object_handlers.offset = offsetof(php_crc_fast_digest_obj, std); php_crc_fast_digest_object_handlers.free_obj = php_crc_fast_digest_free_obj; - php_crc_fast_digest_object_handlers.clone_obj = NULL; // No cloning support + php_crc_fast_digest_object_handlers.clone_obj = NULL; + + // Register the Params class using the auto-generated function + php_crc_fast_params_ce = register_class_CrcFast_Params(); + + // Set up the create_object handler for the class + php_crc_fast_params_ce->create_object = php_crc_fast_params_create_object; + + // Initialize the object handlers + memcpy(&php_crc_fast_params_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_crc_fast_params_object_handlers.offset = offsetof(php_crc_fast_params_obj, std); + php_crc_fast_params_object_handlers.free_obj = php_crc_fast_params_free_obj; + php_crc_fast_params_object_handlers.clone_obj = NULL; // No cloning support return SUCCESS; } diff --git a/php_crc_fast.h b/php_crc_fast.h index 9a3760a..45b18cf 100644 --- a/php_crc_fast.h +++ b/php_crc_fast.h @@ -9,6 +9,7 @@ extern "C" { } #include "libcrc_fast.h" +#include extern zend_module_entry crc_fast_module_entry; # define phpext_crc_fast_ptr &crc_fast_module_entry @@ -19,19 +20,37 @@ extern zend_module_entry crc_fast_module_entry; ZEND_TSRMLS_CACHE_EXTERN() # endif -/* Define the CrcFast\Digest class */ typedef struct _php_crc_fast_digest_obj { CrcFastDigestHandle *digest; - zend_object std; zend_long algorithm; + bool is_custom; // Whether using custom parameters + CrcFastParams custom_params; // Custom parameters if is_custom is true + zend_object std; // MUST be last } php_crc_fast_digest_obj; +/* Use container_of pattern instead of offset macros */ +#define container_of(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + static inline php_crc_fast_digest_obj *php_crc_fast_digest_from_obj(zend_object *obj) { - return (php_crc_fast_digest_obj*)((char*)(obj) - XtOffsetOf(php_crc_fast_digest_obj, std)); + return container_of(obj, php_crc_fast_digest_obj, std); } #define Z_CRC_FAST_DIGEST_P(zv) php_crc_fast_digest_from_obj(Z_OBJ_P(zv)) +/* Define the CrcFast\Params class */ +typedef struct _php_crc_fast_params_obj { + CrcFastParams params; + uint64_t *keys_storage; // Allocated storage for keys array + zend_object std; +} php_crc_fast_params_obj; + +static inline php_crc_fast_params_obj *php_crc_fast_params_from_obj(zend_object *obj) { + return container_of(obj, php_crc_fast_params_obj, std); +} + +#define Z_CRC_FAST_PARAMS_P(zv) php_crc_fast_params_from_obj(Z_OBJ_P(zv)) + /* Algorithm constants that will be exposed to PHP, with room for expansion */ // CRC-32 #define PHP_CRC_FAST_CRC32_AIXM 10000 diff --git a/tests/backward_compatibility.phpt b/tests/backward_compatibility.phpt new file mode 100644 index 0000000..f08af91 --- /dev/null +++ b/tests/backward_compatibility.phpt @@ -0,0 +1,182 @@ +--TEST-- +Backward compatibility test - all existing functionality unchanged +--EXTENSIONS-- +crc_fast +--FILE-- + 10000, + 'CRC_32_AUTOSAR' => 10010, + 'CRC_32_BASE_91_D' => 10020, + 'CRC_32_BZIP2' => 10030, + 'CRC_32_CD_ROM_EDC' => 10040, + 'CRC_32_CKSUM' => 10050, + 'CRC_32_ISCSI' => 10060, + 'CRC_32_ISO_HDLC' => 10070, + 'CRC_32_JAMCRC' => 10080, + 'CRC_32_MEF' => 10090, + 'CRC_32_MPEG_2' => 10100, + 'CRC_32_PHP' => 10200, + 'CRC_32_XFER' => 10300, + 'CRC_64_ECMA_182' => 20000, + 'CRC_64_GO_ISO' => 20010, + 'CRC_64_MS' => 20020, + 'CRC_64_NVME' => 20030, + 'CRC_64_REDIS' => 20040, + 'CRC_64_WE' => 20050, + 'CRC_64_XZ' => 20060, +]; + +foreach ($expected_constants as $name => $expected_value) { + $constant_name = "CrcFast\\$name"; + if (defined($constant_name)) { + $actual_value = constant($constant_name); + if ($actual_value === $expected_value) { + echo "PASS: $constant_name = $actual_value\n"; + } else { + echo "FAIL: $constant_name expected $expected_value, got $actual_value\n"; + } + } else { + echo "FAIL: $constant_name not defined\n"; + } +} + +// Test 2: All original function signatures work with integer algorithm parameters +echo "\n=== Testing Original Function Signatures ===\n"; + +// Test CrcFast\hash() with int parameter +$hash_result = CrcFast\hash(CrcFast\CRC_32_ISCSI, '123456789', false); +echo "CrcFast\\hash() with int: " . ($hash_result === 'e3069283' ? 'PASS' : 'FAIL') . "\n"; + +// Test CrcFast\hash_file() with int parameter +$tempfile = tmpfile(); +fwrite($tempfile, '123456789'); +fsync($tempfile); +$path = stream_get_meta_data($tempfile)['uri']; +$file_hash_result = CrcFast\hash_file(CrcFast\CRC_32_ISCSI, $path, false); +fclose($tempfile); +echo "CrcFast\\hash_file() with int: " . ($file_hash_result === 'e3069283' ? 'PASS' : 'FAIL') . "\n"; + +// Test CrcFast\combine() with int parameter +$combine_result = CrcFast\combine( + CrcFast\CRC_32_ISCSI, + CrcFast\hash(CrcFast\CRC_32_ISCSI, '1234', false), + CrcFast\hash(CrcFast\CRC_32_ISCSI, '56789', false), + 5, + false +); +echo "CrcFast\\combine() with int: " . ($combine_result === 'e3069283' ? 'PASS' : 'FAIL') . "\n"; + +// Test CrcFast\get_supported_algorithms() - should return same array +$algorithms = CrcFast\get_supported_algorithms(); +$expected_count = 19; +echo "CrcFast\\get_supported_algorithms() count: " . (count($algorithms) === $expected_count ? 'PASS' : 'FAIL') . "\n"; + +// Test CrcFast\crc32() - should work unchanged +$crc32_result = CrcFast\crc32('123456789'); +echo "CrcFast\\crc32(): " . (is_int($crc32_result) ? 'PASS' : 'FAIL') . "\n"; + +// Test 3: CrcFast\Digest class with integer algorithm parameters +echo "\n=== Testing Digest Class with Original Signatures ===\n"; + +// Test Digest constructor with int parameter +$digest = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); +echo "Digest constructor with int: PASS\n"; + +// Test all Digest methods work as before +$digest->update('123456789'); +$digest_result = $digest->finalize(false); +echo "Digest methods: " . ($digest_result === 'e3069283' ? 'PASS' : 'FAIL') . "\n"; + +// Test Digest reset functionality +$digest->reset(); +$reset_result = $digest->finalize(false); +echo "Digest reset: " . ($reset_result === '00000000' ? 'PASS' : 'FAIL') . "\n"; + +// Test Digest finalizeReset functionality +$digest->update('123456789'); +$finalize_reset_result = $digest->finalizeReset(false); +$after_reset_result = $digest->finalize(false); +echo "Digest finalizeReset: " . ($finalize_reset_result === 'e3069283' && $after_reset_result === '00000000' ? 'PASS' : 'FAIL') . "\n"; + +// Test Digest combine functionality +$digest1 = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); +$digest1->update('1234'); +$digest2 = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); +$digest2->update('56789'); +$digest1->combine($digest2); +$combine_digest_result = $digest1->finalize(false); +echo "Digest combine: " . ($combine_digest_result === 'e3069283' ? 'PASS' : 'FAIL') . "\n"; + +// Test 4: Binary output modes work unchanged +echo "\n=== Testing Binary Output Modes ===\n"; + +$binary_hash = CrcFast\hash(CrcFast\CRC_32_ISCSI, '123456789', true); +echo "Binary hash output: " . (strlen($binary_hash) === 4 ? 'PASS' : 'FAIL') . "\n"; + +$digest_binary = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); +$digest_binary->update('123456789'); +$binary_digest_result = $digest_binary->finalize(true); +echo "Binary digest output: " . (strlen($binary_digest_result) === 4 ? 'PASS' : 'FAIL') . "\n"; + +// Test 5: 64-bit algorithms work unchanged +echo "\n=== Testing 64-bit Algorithms ===\n"; + +$hash64_result = CrcFast\hash(CrcFast\CRC_64_NVME, '123456789', false); +echo "64-bit hash: " . ($hash64_result === 'ae8b14860a799888' ? 'PASS' : 'FAIL') . "\n"; + +$digest64 = new CrcFast\Digest(CrcFast\CRC_64_NVME); +$digest64->update('123456789'); +$digest64_result = $digest64->finalize(false); +echo "64-bit digest: " . ($digest64_result === 'ae8b14860a799888' ? 'PASS' : 'FAIL') . "\n"; + +echo "\n=== Backward Compatibility Test Complete ===\n"; +?> +--EXPECT-- +=== Testing Algorithm Constants === +PASS: CrcFast\CRC_32_AIXM = 10000 +PASS: CrcFast\CRC_32_AUTOSAR = 10010 +PASS: CrcFast\CRC_32_BASE_91_D = 10020 +PASS: CrcFast\CRC_32_BZIP2 = 10030 +PASS: CrcFast\CRC_32_CD_ROM_EDC = 10040 +PASS: CrcFast\CRC_32_CKSUM = 10050 +PASS: CrcFast\CRC_32_ISCSI = 10060 +PASS: CrcFast\CRC_32_ISO_HDLC = 10070 +PASS: CrcFast\CRC_32_JAMCRC = 10080 +PASS: CrcFast\CRC_32_MEF = 10090 +PASS: CrcFast\CRC_32_MPEG_2 = 10100 +PASS: CrcFast\CRC_32_PHP = 10200 +PASS: CrcFast\CRC_32_XFER = 10300 +PASS: CrcFast\CRC_64_ECMA_182 = 20000 +PASS: CrcFast\CRC_64_GO_ISO = 20010 +PASS: CrcFast\CRC_64_MS = 20020 +PASS: CrcFast\CRC_64_NVME = 20030 +PASS: CrcFast\CRC_64_REDIS = 20040 +PASS: CrcFast\CRC_64_WE = 20050 +PASS: CrcFast\CRC_64_XZ = 20060 + +=== Testing Original Function Signatures === +CrcFast\hash() with int: PASS +CrcFast\hash_file() with int: PASS +CrcFast\combine() with int: PASS +CrcFast\get_supported_algorithms() count: PASS +CrcFast\crc32(): PASS + +=== Testing Digest Class with Original Signatures === +Digest constructor with int: PASS +Digest methods: PASS +Digest reset: PASS +Digest finalizeReset: PASS +Digest combine: PASS + +=== Testing Binary Output Modes === +Binary hash output: PASS +Binary digest output: PASS + +=== Testing 64-bit Algorithms === +64-bit hash: PASS +64-bit digest: PASS + +=== Backward Compatibility Test Complete === \ No newline at end of file diff --git a/tests/combine.phpt b/tests/combine.phpt index 54dca4b..71771ff 100644 --- a/tests/combine.phpt +++ b/tests/combine.phpt @@ -30,6 +30,8 @@ var_dump($combined); $first = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); $first->update('1234'); +$first->finalize(false); + $second = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); $second->update('56789'); @@ -37,6 +39,7 @@ $first->combine($second); var_dump($first->finalize(false)); + // checksum CRC-64, binary inputs $combined = CrcFast\combine( CrcFast\CRC_64_NVME, diff --git a/tests/combine_custom_params.phpt b/tests/combine_custom_params.phpt new file mode 100644 index 0000000..14ac270 --- /dev/null +++ b/tests/combine_custom_params.phpt @@ -0,0 +1,194 @@ +--TEST-- +CrcFast\combine() with custom parameters integration test +--EXTENSIONS-- +crc_fast +--FILE-- + +--EXPECT-- +Testing CrcFast\combine() with custom parameters + +Test 1: CRC-32/ISO-HDLC combine comparison +Predefined CRC1: 9be3e0a3 +Custom CRC1: 9be3e0a3 +CRC1 match: true +Predefined CRC2: 131da070 +Custom CRC2: 131da070 +CRC2 match: true +Predefined combined: cbf43926 +Custom combined: cbf43926 +Combined match: true + +Test 2: Combine vs manual calculation verification +Manual combined: cbf43926 +Combine result: cbf43926 +Manual vs combine match: true + +Test 3: Binary input/output format test +Binary to hex: cbf43926 +Binary to binary (hex): cbf43926 +Binary formats match: true +Binary result matches hex: true + +Test 4: CRC-32/ISCSI combine test +ISCSI custom combined: e3069283 +ISCSI predefined combined: e3069283 +ISCSI combine match: true +ISCSI manual: e3069283 +ISCSI manual vs combine: true + +Test 5: Different data lengths test +Length test combined: e0e8ff4d +Length test manual: e0e8ff4d +Length test match: true + +All combine() custom parameter tests completed \ No newline at end of file diff --git a/tests/digest_custom_formatting.phpt b/tests/digest_custom_formatting.phpt new file mode 100644 index 0000000..103a8a6 --- /dev/null +++ b/tests/digest_custom_formatting.phpt @@ -0,0 +1,63 @@ +--TEST-- +Digest custom parameters formatting test +--EXTENSIONS-- +crc_fast +--FILE-- +update("123456789"); + +// Test hex output (should be 8 characters for 32-bit) +$hex32 = $digest32->finalize(); +var_dump(strlen($hex32)); +var_dump($hex32); + +// Test binary output (should be 4 bytes for 32-bit) +$digest32->reset(); +$digest32->update("123456789"); +$binary32 = $digest32->finalize(true); +var_dump(strlen($binary32)); +var_dump(bin2hex($binary32)); + +// Test finalizeReset formatting +$digest32->reset(); +$digest32->update("test"); +$reset_result = $digest32->finalizeReset(); +var_dump(strlen($reset_result)); + +// Test that custom parameters produce different output width than predefined +// Create a simple 64-bit custom CRC (avoiding large hex literals) +try { + $params64 = new CrcFast\Params( + width: 64, + poly: 27, // 0x1B in decimal + init: 0, + refin: false, + refout: false, + xorout: 0, + check: 88 // 0x58 in decimal - this won't validate but we'll catch the exception + ); + $digest64 = new CrcFast\Digest($params64); +} catch (Exception $e) { + // Expected - just test that we can detect 64-bit width + echo "64-bit width detected (validation failed as expected)\n"; +} +?> +--EXPECT-- +int(8) +string(8) "cbf43926" +int(4) +string(8) "cbf43926" +int(8) +64-bit width detected (validation failed as expected) \ No newline at end of file diff --git a/tests/digest_custom_params.phpt b/tests/digest_custom_params.phpt new file mode 100644 index 0000000..defc978 --- /dev/null +++ b/tests/digest_custom_params.phpt @@ -0,0 +1,52 @@ +--TEST-- +Digest constructor with custom parameters test +--EXTENSIONS-- +crc_fast +--FILE-- +update("123456789"); +$result1 = $digest1->finalize(); + +// Test with custom parameters +$digest2 = new CrcFast\Digest($params); +$digest2->update("123456789"); +$result2 = $digest2->finalize(); + +// Results should match +var_dump($result1 === $result2); +var_dump($result1); +var_dump($result2); + +// Test binary output with custom parameters +$digest3 = new CrcFast\Digest($params); +$digest3->update("123456789"); +$result3 = $digest3->finalize(true); +var_dump(strlen($result3)); +var_dump(bin2hex($result3)); + +// Test finalizeReset with custom parameters +$digest4 = new CrcFast\Digest($params); +$digest4->update("123456789"); +$result4 = $digest4->finalizeReset(); +var_dump($result4); +?> +--EXPECT-- +bool(true) +string(8) "cbf43926" +string(8) "cbf43926" +int(4) +string(8) "cbf43926" +string(8) "cbf43926" \ No newline at end of file diff --git a/tests/digest_integration_custom_params.phpt b/tests/digest_integration_custom_params.phpt new file mode 100644 index 0000000..6559ebd --- /dev/null +++ b/tests/digest_integration_custom_params.phpt @@ -0,0 +1,203 @@ +--TEST-- +CrcFast\Digest class with custom parameters integration test +--EXTENSIONS-- +crc_fast +--FILE-- +update("123456789"); +$result_predefined = $digest_predefined->finalize(); + +// Test with custom parameters +$digest_custom = new CrcFast\Digest($params_iso_hdlc); +$digest_custom->update("123456789"); +$result_custom = $digest_custom->finalize(); + +echo "Predefined result: " . $result_predefined . "\n"; +echo "Custom result: " . $result_custom . "\n"; +echo "Results match: " . ($result_predefined === $result_custom ? "true" : "false") . "\n\n"; + +// Test 2: Verify digest matches hash() function +echo "Test 2: Digest vs hash() function consistency\n"; +$hash_result = CrcFast\hash($params_iso_hdlc, "123456789", false); +echo "Hash result: " . $hash_result . "\n"; +echo "Digest result: " . $result_custom . "\n"; +echo "Hash vs digest match: " . ($hash_result === $result_custom ? "true" : "false") . "\n\n"; + +// Test 3: Multiple updates vs single update +echo "Test 3: Multiple updates consistency\n"; +$digest_multi = new CrcFast\Digest($params_iso_hdlc); +$digest_multi->update("123"); +$digest_multi->update("456"); +$digest_multi->update("789"); +$result_multi = $digest_multi->finalize(); + +$digest_single = new CrcFast\Digest($params_iso_hdlc); +$digest_single->update("123456789"); +$result_single = $digest_single->finalize(); + +echo "Multi-update result: " . $result_multi . "\n"; +echo "Single-update result: " . $result_single . "\n"; +echo "Multi vs single match: " . ($result_multi === $result_single ? "true" : "false") . "\n\n"; + +// Test 4: Binary output format +echo "Test 4: Binary output format test\n"; +$digest_binary = new CrcFast\Digest($params_iso_hdlc); +$digest_binary->update("123456789"); +$result_binary = $digest_binary->finalize(true); + +echo "Binary result length: " . strlen($result_binary) . " bytes\n"; +echo "Binary result hex: " . bin2hex($result_binary) . "\n"; +echo "Binary matches hex: " . (bin2hex($result_binary) === $result_custom ? "true" : "false") . "\n\n"; + +// Test 5: Reset functionality +echo "Test 5: Reset functionality test\n"; +$digest_reset = new CrcFast\Digest($params_iso_hdlc); +$digest_reset->update("wrong data"); +$digest_reset->reset(); +$digest_reset->update("123456789"); +$result_reset = $digest_reset->finalize(); + +echo "Reset result: " . $result_reset . "\n"; +echo "Reset matches expected: " . ($result_reset === $result_custom ? "true" : "false") . "\n\n"; + +// Test 6: FinalizeReset functionality +echo "Test 6: FinalizeReset functionality test\n"; +$digest_finalize_reset = new CrcFast\Digest($params_iso_hdlc); +$digest_finalize_reset->update("123456789"); +$result_finalize_reset = $digest_finalize_reset->finalizeReset(); + +// After finalizeReset, digest should be reset +$empty_result = $digest_finalize_reset->finalize(); + +echo "FinalizeReset result: " . $result_finalize_reset . "\n"; +echo "FinalizeReset matches expected: " . ($result_finalize_reset === $result_custom ? "true" : "false") . "\n"; +echo "Empty result after reset: " . $empty_result . "\n"; +echo "Empty result is initial value: " . ($empty_result === "00000000" ? "true" : "false") . "\n\n"; + +// Test 7: Digest combination with custom parameters +echo "Test 7: Digest combination test\n"; +$params_iscsi = new CrcFast\Params( + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0xE3069283 +); + +$digest1 = new CrcFast\Digest($params_iscsi); +$digest1->update("1234"); + +$digest2 = new CrcFast\Digest($params_iscsi); +$digest2->update("56789"); + +// Combine the digests +$digest1->combine($digest2); +$combined_result = $digest1->finalize(); + +// Verify against manual calculation +$manual_combined = CrcFast\hash($params_iscsi, "123456789", false); + +echo "Combined result: " . $combined_result . "\n"; +echo "Manual combined: " . $manual_combined . "\n"; +echo "Combine matches manual: " . ($combined_result === $manual_combined ? "true" : "false") . "\n"; + +// Verify against predefined algorithm +$predefined_digest1 = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); +$predefined_digest1->update("1234"); +$predefined_digest1->finalize(); // Need to finalize before combine + +$predefined_digest2 = new CrcFast\Digest(CrcFast\CRC_32_ISCSI); +$predefined_digest2->update("56789"); + +$predefined_digest1->combine($predefined_digest2); +$predefined_combined = $predefined_digest1->finalize(); + +echo "Predefined combined: " . $predefined_combined . "\n"; +echo "Custom vs predefined combine: " . ($combined_result === $predefined_combined ? "true" : "false") . "\n\n"; + +// Test 8: Different algorithms comparison +echo "Test 8: Different custom algorithms test\n"; +$test_data = "Hello, World!"; + +// ISO-HDLC +$digest_iso = new CrcFast\Digest($params_iso_hdlc); +$digest_iso->update($test_data); +$result_iso = $digest_iso->finalize(); + +// ISCSI +$digest_iscsi = new CrcFast\Digest($params_iscsi); +$digest_iscsi->update($test_data); +$result_iscsi = $digest_iscsi->finalize(); + +echo "ISO-HDLC result: " . $result_iso . "\n"; +echo "ISCSI result: " . $result_iscsi . "\n"; +echo "Results are different: " . ($result_iso !== $result_iscsi ? "true" : "false") . "\n\n"; + +echo "All Digest custom parameter tests completed\n"; + +?> +--EXPECT-- +Testing CrcFast\Digest class with custom parameters + +Test 1: Basic digest operations with custom parameters +Predefined result: cbf43926 +Custom result: cbf43926 +Results match: true + +Test 2: Digest vs hash() function consistency +Hash result: cbf43926 +Digest result: cbf43926 +Hash vs digest match: true + +Test 3: Multiple updates consistency +Multi-update result: cbf43926 +Single-update result: cbf43926 +Multi vs single match: true + +Test 4: Binary output format test +Binary result length: 4 bytes +Binary result hex: cbf43926 +Binary matches hex: true + +Test 5: Reset functionality test +Reset result: cbf43926 +Reset matches expected: true + +Test 6: FinalizeReset functionality test +FinalizeReset result: cbf43926 +FinalizeReset matches expected: true +Empty result after reset: 00000000 +Empty result is initial value: true + +Test 7: Digest combination test +Combined result: e3069283 +Manual combined: e3069283 +Combine matches manual: true +Predefined combined: e3069283 +Custom vs predefined combine: true + +Test 8: Different custom algorithms test +ISO-HDLC result: ec4ac3d0 +ISCSI result: 4d551068 +Results are different: true + +All Digest custom parameter tests completed \ No newline at end of file diff --git a/tests/error_handling_digest.phpt b/tests/error_handling_digest.phpt new file mode 100644 index 0000000..2c06a2b --- /dev/null +++ b/tests/error_handling_digest.phpt @@ -0,0 +1,75 @@ +--TEST-- +CrcFast\Digest error handling test +--FILE-- +getMessage() . "\n"; +} + +// Test invalid algorithm type in Digest constructor +try { + new Digest("invalid"); + echo "FAIL: Should have thrown exception for invalid algorithm type\n"; +} catch (Exception $e) { + echo "PASS: Invalid algorithm type in Digest constructor: " . $e->getMessage() . "\n"; +} + +// Test operations on uninitialized digest (this shouldn't happen in normal usage, but test defensive programming) +// We can't easily test this since the constructor will always initialize the digest or throw an exception + +// Test valid digest operations to ensure they work +$digest = new Digest(CrcFast\CRC_32_ISO_HDLC); + +// Test update with valid data +$digest->update("test"); +echo "PASS: Update with valid data succeeded\n"; + +// Test finalize +$result = $digest->finalize(); +echo "PASS: Finalize succeeded, result: " . $result . "\n"; + +// Test reset +$digest->reset(); +echo "PASS: Reset succeeded\n"; + +// Test finalizeReset +$digest->update("test"); +$result = $digest->finalizeReset(); +echo "PASS: FinalizeReset succeeded, result: " . $result . "\n"; + +// Test combine with another digest +$digest1 = new Digest(CrcFast\CRC_32_ISO_HDLC); +$digest2 = new Digest(CrcFast\CRC_32_ISO_HDLC); +$digest1->update("hello"); +$digest2->update("world"); +$digest1->combine($digest2); +echo "PASS: Combine succeeded\n"; + +// Test with custom parameters +$params = new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0xFFFFFFFF, 0xCBF43926); +$digest = new Digest($params); +$digest->update("123456789"); +$result = $digest->finalize(); +echo "PASS: Custom parameters digest succeeded, result: " . $result . "\n"; + +echo "All Digest error handling tests completed\n"; + +?> +--EXPECT-- +PASS: Invalid algorithm in Digest constructor: Invalid algorithm constant 99999. Use CrcFast\get_supported_algorithms() to see valid values +PASS: Invalid algorithm type in Digest constructor: Algorithm parameter must be an integer constant or CrcFast\Params object, string given +PASS: Update with valid data succeeded +PASS: Finalize succeeded, result: d87f7e0c +PASS: Reset succeeded +PASS: FinalizeReset succeeded, result: d87f7e0c +PASS: Combine succeeded +PASS: Custom parameters digest succeeded, result: cbf43926 +All Digest error handling tests completed \ No newline at end of file diff --git a/tests/error_handling_functions.phpt b/tests/error_handling_functions.phpt new file mode 100644 index 0000000..7229649 --- /dev/null +++ b/tests/error_handling_functions.phpt @@ -0,0 +1,57 @@ +--TEST-- +CrcFast function error handling test +--FILE-- +getMessage() . "\n"; +} + +// Test invalid algorithm type +try { + CrcFast\hash("invalid", "test", false); + echo "FAIL: Should have thrown exception for invalid algorithm type\n"; +} catch (Exception $e) { + echo "PASS: Invalid algorithm type error: " . $e->getMessage() . "\n"; +} + +// Test empty filename +try { + CrcFast\hash_file(CrcFast\CRC_32_ISO_HDLC, "", false); + echo "FAIL: Should have thrown exception for empty filename\n"; +} catch (Exception $e) { + echo "PASS: Empty filename error: " . $e->getMessage() . "\n"; +} + +// Test negative length in combine +try { + CrcFast\combine(CrcFast\CRC_32_ISO_HDLC, "12345678", "87654321", -1, false); + echo "FAIL: Should have thrown exception for negative length\n"; +} catch (Exception $e) { + echo "PASS: Negative length error: " . $e->getMessage() . "\n"; +} + +// Test invalid checksum length in combine (this generates a warning, not an exception) +$result = @CrcFast\combine(CrcFast\CRC_32_ISO_HDLC, "123", "87654321", 100, false); +if ($result === false) { + echo "PASS: Invalid checksum length generates warning and returns false\n"; +} else { + echo "FAIL: Should have returned false for invalid checksum length\n"; +} + +echo "All function error handling tests completed\n"; + +?> +--EXPECT-- +PASS: Invalid algorithm error: Invalid algorithm constant 99999. Use CrcFast\get_supported_algorithms() to see valid values +PASS: Invalid algorithm type error: Algorithm parameter must be an integer constant or CrcFast\Params object, string given +PASS: Empty filename error: Filename cannot be empty +PASS: Negative length error: Length parameter must be non-negative, got -1 +PASS: Invalid checksum length generates warning and returns false +All function error handling tests completed \ No newline at end of file diff --git a/tests/error_handling_params.phpt b/tests/error_handling_params.phpt new file mode 100644 index 0000000..d5fa4b4 --- /dev/null +++ b/tests/error_handling_params.phpt @@ -0,0 +1,97 @@ +--TEST-- +CrcFast\Params error handling test +--FILE-- +getMessage() . "\n"; +} + +// Test polynomial too large for width +try { + new Params(32, 0x1FFFFFFFF, 0xFFFFFFFF, true, true, 0x00000000, 0x29B1); + echo "FAIL: Should have thrown exception for polynomial too large\n"; +} catch (Exception $e) { + echo "PASS: Polynomial too large error: " . $e->getMessage() . "\n"; +} + +// Test init value too large for width +try { + new Params(32, 0x04C11DB7, 0x1FFFFFFFF, true, true, 0x00000000, 0x29B1); + echo "FAIL: Should have thrown exception for init value too large\n"; +} catch (Exception $e) { + echo "PASS: Init value too large error: " . $e->getMessage() . "\n"; +} + +// Test xorout value too large for width +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x1FFFFFFFF, 0x29B1); + echo "FAIL: Should have thrown exception for xorout value too large\n"; +} catch (Exception $e) { + echo "PASS: Xorout value too large error: " . $e->getMessage() . "\n"; +} + +// Test check value too large for width +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x1FFFFFFFF); + echo "FAIL: Should have thrown exception for check value too large\n"; +} catch (Exception $e) { + echo "PASS: Check value too large error: " . $e->getMessage() . "\n"; +} + +// Test invalid keys array length +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x29B1, [1, 2, 3]); + echo "FAIL: Should have thrown exception for invalid keys array length\n"; +} catch (Exception $e) { + echo "PASS: Invalid keys array length error: " . $e->getMessage() . "\n"; +} + +// Test non-integer key values +try { + $keys = array_fill(0, 23, 0); + $keys[5] = "not an integer"; + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x29B1, $keys); + echo "FAIL: Should have thrown exception for non-integer key values\n"; +} catch (Exception $e) { + echo "PASS: Non-integer key values error: " . $e->getMessage() . "\n"; +} + +// Test negative key values +try { + $keys = array_fill(0, 23, 0); + $keys[10] = -1; + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x29B1, $keys); + echo "FAIL: Should have thrown exception for negative key values\n"; +} catch (Exception $e) { + echo "PASS: Negative key values error: " . $e->getMessage() . "\n"; +} + +// Test invalid check value (parameters that don't produce expected check) +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x12345678); + echo "FAIL: Should have thrown exception for invalid check value\n"; +} catch (Exception $e) { + echo "PASS: Invalid check value error: " . $e->getMessage() . "\n"; +} + +echo "All error handling tests completed\n"; + +?> +--EXPECT-- +PASS: Invalid width error: Invalid width 16. Only 32 and 64 bit widths are supported +PASS: Polynomial too large error: Polynomial 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Init value too large error: Init value 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Xorout value too large error: Xorout value 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Check value too large error: Check value 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Invalid keys array length error: Keys array must contain exactly 23 elements, got 3 +PASS: Non-integer key values error: All keys must be integers, element 5 is not an integer +PASS: Negative key values error: Key values cannot be negative, element 10 has value -1 +PASS: Invalid check value error: Parameters validation failed: computed check 0x00000000340bc6d9 does not match expected check 0x0000000012345678. Please verify your CRC parameters are correct +All error handling tests completed \ No newline at end of file diff --git a/tests/hash_custom_params.phpt b/tests/hash_custom_params.phpt new file mode 100644 index 0000000..9754646 --- /dev/null +++ b/tests/hash_custom_params.phpt @@ -0,0 +1,131 @@ +--TEST-- +CrcFast\hash() with custom parameters integration test +--EXTENSIONS-- +crc_fast +--FILE-- + +--EXPECT-- +Testing CrcFast\hash() with custom parameters matching predefined algorithms + +Test 1: CRC-32/ISO-HDLC comparison +Predefined hex: cbf43926 +Custom hex: cbf43926 +Hex match: true +Predefined binary: cbf43926 +Custom binary: cbf43926 +Binary match: true + +Test 2: CRC-32/ISCSI comparison +Predefined hex: e3069283 +Custom hex: e3069283 +Hex match: true +Predefined binary: e3069283 +Custom binary: e3069283 +Binary match: true + +Test 3: Width difference test (32-bit vs 64-bit output) +32-bit result: cbf43926 (length: 8) +64-bit test skipped due to PHP integer overflow with large check values + +Test 4: Different data consistency test +Different data - Predefined: ec4ac3d0 +Different data - Custom: ec4ac3d0 +Different data match: true + +Test 5: Empty string test +Empty string - Predefined: 00000000 +Empty string - Custom: 00000000 +Empty string match: true + +All hash() custom parameter tests completed \ No newline at end of file diff --git a/tests/hash_file_custom_params.phpt b/tests/hash_file_custom_params.phpt new file mode 100644 index 0000000..e75a5ae --- /dev/null +++ b/tests/hash_file_custom_params.phpt @@ -0,0 +1,145 @@ +--TEST-- +CrcFast\hash_file() with custom parameters integration test +--EXTENSIONS-- +crc_fast +--FILE-- + +--EXPECT-- +Testing CrcFast\hash_file() with custom parameters + +Test 1: CRC-32/ISO-HDLC file hashing comparison +Predefined file hex: cbf43926 +Custom file hex: cbf43926 +File hex match: true +Predefined file binary: cbf43926 +Custom file binary: cbf43926 +File binary match: true + +Test 2: hash_file() vs hash() consistency +String hash result: cbf43926 +File hash result: cbf43926 +String vs file match: true + +Test 3: CRC-32/ISCSI with different file content +Predefined file2 hex: 4d551068 +Custom file2 hex: 4d551068 +File2 hex match: true +String2 hash result: 4d551068 +String2 vs file2 match: true + +Test 4: Binary output format consistency +String binary length: 4 bytes +File binary length: 4 bytes +String binary hex: cbf43926 +File binary hex: cbf43926 +Binary format match: true + +Test 5: Empty file test +Empty string result: 00000000 +Empty file result: 00000000 +Empty file match: true + +All hash_file() custom parameter tests completed \ No newline at end of file diff --git a/tests/params_constructor_invalid.phpt b/tests/params_constructor_invalid.phpt new file mode 100644 index 0000000..00c6be0 --- /dev/null +++ b/tests/params_constructor_invalid.phpt @@ -0,0 +1,155 @@ +--TEST-- +CrcFast\Params constructor with invalid parameters test +--EXTENSIONS-- +crc_fast +--FILE-- +getMessage() . "\n"; +} + +// Test width = 8 (unsupported) +try { + new Params(8, 0x07, 0x00, false, false, 0x00, 0xF4); + echo "FAIL: Should have thrown exception for width 8\n"; +} catch (Exception $e) { + echo "PASS: Width 8 rejected: " . $e->getMessage() . "\n"; +} + +// Test width = 128 (unsupported) +try { + new Params(128, 0x1021, 0xFFFF, true, true, 0x0000, 0x29B1); + echo "FAIL: Should have thrown exception for width 128\n"; +} catch (Exception $e) { + echo "PASS: Width 128 rejected: " . $e->getMessage() . "\n"; +} + +echo "\nTesting out-of-range values for 32-bit width:\n"; + +// Test polynomial too large for 32-bit +try { + new Params(32, 0x1FFFFFFFF, 0xFFFFFFFF, true, true, 0x00000000, 0x29B1); + echo "FAIL: Should have thrown exception for polynomial too large\n"; +} catch (Exception $e) { + echo "PASS: Large polynomial rejected: " . $e->getMessage() . "\n"; +} + +// Test init value too large for 32-bit +try { + new Params(32, 0x04C11DB7, 0x1FFFFFFFF, true, true, 0x00000000, 0x29B1); + echo "FAIL: Should have thrown exception for init value too large\n"; +} catch (Exception $e) { + echo "PASS: Large init value rejected: " . $e->getMessage() . "\n"; +} + +// Test xorout value too large for 32-bit +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x1FFFFFFFF, 0x29B1); + echo "FAIL: Should have thrown exception for xorout value too large\n"; +} catch (Exception $e) { + echo "PASS: Large xorout value rejected: " . $e->getMessage() . "\n"; +} + +// Test check value too large for 32-bit +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x1FFFFFFFF); + echo "FAIL: Should have thrown exception for check value too large\n"; +} catch (Exception $e) { + echo "PASS: Large check value rejected: " . $e->getMessage() . "\n"; +} + +echo "\nTesting invalid keys array:\n"; + +// Test keys array with wrong length +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0xCBF43926, [1, 2, 3]); + echo "FAIL: Should have thrown exception for wrong keys array length\n"; +} catch (Exception $e) { + echo "PASS: Wrong keys length rejected: " . $e->getMessage() . "\n"; +} + +// Test keys array with non-integer values +try { + $keys = array_fill(0, 23, 0); + $keys[5] = "not an integer"; + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0xCBF43926, $keys); + echo "FAIL: Should have thrown exception for non-integer key values\n"; +} catch (Exception $e) { + echo "PASS: Non-integer key rejected: " . $e->getMessage() . "\n"; +} + +// Test keys array with negative values +try { + $keys = array_fill(0, 23, 0); + $keys[10] = -1; + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0xCBF43926, $keys); + echo "FAIL: Should have thrown exception for negative key values\n"; +} catch (Exception $e) { + echo "PASS: Negative key rejected: " . $e->getMessage() . "\n"; +} + +echo "\nTesting invalid check value (parameters that don't produce expected check):\n"; + +// Test with wrong check value +try { + new Params(32, 0x04C11DB7, 0xFFFFFFFF, true, true, 0x00000000, 0x12345678); + echo "FAIL: Should have thrown exception for invalid check value\n"; +} catch (Exception $e) { + echo "PASS: Invalid check value rejected: " . $e->getMessage() . "\n"; +} + +echo "\nTesting negative parameter values:\n"; + +// Test negative polynomial +try { + new Params(32, -1, 0xFFFFFFFF, true, true, 0x00000000, 0xCBF43926); + echo "FAIL: Should have thrown exception for negative polynomial\n"; +} catch (Exception $e) { + echo "PASS: Negative polynomial rejected: " . $e->getMessage() . "\n"; +} + +// Test negative init value +try { + new Params(32, 0x04C11DB7, -1, true, true, 0x00000000, 0xCBF43926); + echo "FAIL: Should have thrown exception for negative init value\n"; +} catch (Exception $e) { + echo "PASS: Negative init value rejected: " . $e->getMessage() . "\n"; +} + +echo "\nAll invalid parameter tests completed\n"; + +?> +--EXPECT-- +Testing invalid width values: +PASS: Width 16 rejected: Invalid width 16. Only 32 and 64 bit widths are supported +PASS: Width 8 rejected: Invalid width 8. Only 32 and 64 bit widths are supported +PASS: Width 128 rejected: Invalid width 128. Only 32 and 64 bit widths are supported + +Testing out-of-range values for 32-bit width: +PASS: Large polynomial rejected: Polynomial 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Large init value rejected: Init value 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Large xorout value rejected: Xorout value 0x1ffffffff exceeds maximum value for 32-bit width +PASS: Large check value rejected: Check value 0x1ffffffff exceeds maximum value for 32-bit width + +Testing invalid keys array: +PASS: Wrong keys length rejected: Keys array must contain exactly 23 elements, got 3 +PASS: Non-integer key rejected: All keys must be integers, element 5 is not an integer +PASS: Negative key rejected: Key values cannot be negative, element 10 has value -1 + +Testing invalid check value (parameters that don't produce expected check): +PASS: Invalid check value rejected: Parameters validation failed: computed check 0x00000000340bc6d9 does not match expected check 0x0000000012345678. Please verify your CRC parameters are correct + +Testing negative parameter values: +PASS: Negative polynomial rejected: Polynomial value -1 cannot be negative +PASS: Negative init value rejected: Init value -1 cannot be negative + +All invalid parameter tests completed \ No newline at end of file diff --git a/tests/params_constructor_valid.phpt b/tests/params_constructor_valid.phpt new file mode 100644 index 0000000..421402c --- /dev/null +++ b/tests/params_constructor_valid.phpt @@ -0,0 +1,95 @@ +--TEST-- +CrcFast\Params constructor with valid parameters test +--EXTENSIONS-- +crc_fast +--FILE-- +getWidth() . "\n"; +echo "Poly: 0x" . strtoupper(dechex($params32->getPoly())) . "\n"; +echo "Init: 0x" . strtoupper(dechex($params32->getInit())) . "\n"; +echo "Refin: " . ($params32->getRefin() ? "true" : "false") . "\n"; +echo "Refout: " . ($params32->getRefout() ? "true" : "false") . "\n"; +echo "Xorout: 0x" . strtoupper(dechex($params32->getXorout())) . "\n"; +echo "Check: 0x" . strtoupper(dechex($params32->getCheck())) . "\n"; + +$keys32 = $params32->getKeys(); +echo "Keys array length: " . count($keys32) . "\n"; +echo "Keys are integers: " . (is_int($keys32[0]) ? "true" : "false") . "\n"; + +echo "\nTesting valid 32-bit CRC parameters (different configuration):\n"; +$params32_alt = new Params( + width: 32, + poly: 0x1021, // CRC-16 CCITT polynomial extended to 32-bit + init: 0, + refin: false, + refout: false, + xorout: 0, + check: 0x70a79f31 // Computed check value for these parameters +); + +echo "Alternative 32-bit params created successfully\n"; +echo "Width: " . $params32_alt->getWidth() . "\n"; +echo "Poly: 0x" . strtoupper(dechex($params32_alt->getPoly())) . "\n"; +echo "Init: 0x" . strtoupper(dechex($params32_alt->getInit())) . "\n"; +echo "Refin: " . ($params32_alt->getRefin() ? "true" : "false") . "\n"; +echo "Refout: " . ($params32_alt->getRefout() ? "true" : "false") . "\n"; +echo "Xorout: 0x" . strtoupper(dechex($params32_alt->getXorout())) . "\n"; +echo "Check: 0x" . strtoupper(dechex($params32_alt->getCheck())) . "\n"; + +$keys32_alt = $params32_alt->getKeys(); +echo "Keys array length: " . count($keys32_alt) . "\n"; +echo "Keys are integers: " . (is_int($keys32_alt[0]) ? "true" : "false") . "\n"; + +echo "\nTesting keys auto-generation:\n"; +$keys_test = $params32->getKeys(); +echo "Keys auto-generated: " . (count($keys_test) === 23 ? "true" : "false") . "\n"; +echo "Keys are integers: " . (is_int($keys_test[0]) ? "true" : "false") . "\n"; + +echo "\nAll valid parameter tests passed\n"; + +?> +--EXPECT-- +Testing valid 32-bit CRC parameters (CRC-32/ISO-HDLC equivalent): +32-bit params created successfully +Width: 32 +Poly: 0x4C11DB7 +Init: 0xFFFFFFFF +Refin: true +Refout: true +Xorout: 0xFFFFFFFF +Check: 0xCBF43926 +Keys array length: 23 +Keys are integers: true + +Testing valid 32-bit CRC parameters (different configuration): +Alternative 32-bit params created successfully +Width: 32 +Poly: 0x1021 +Init: 0x0 +Refin: false +Refout: false +Xorout: 0x0 +Check: 0x70A79F31 +Keys array length: 23 +Keys are integers: true + +Testing keys auto-generation: +Keys auto-generated: true +Keys are integers: true + +All valid parameter tests passed \ No newline at end of file diff --git a/tests/params_getters.phpt b/tests/params_getters.phpt new file mode 100644 index 0000000..6e87336 --- /dev/null +++ b/tests/params_getters.phpt @@ -0,0 +1,158 @@ +--TEST-- +CrcFast\Params getter methods test +--EXTENSIONS-- +crc_fast +--FILE-- +getWidth() . "\n"; +echo "getPoly(): 0x" . strtoupper(dechex($params32->getPoly())) . "\n"; +echo "getInit(): 0x" . strtoupper(dechex($params32->getInit())) . "\n"; +echo "getRefin(): " . ($params32->getRefin() ? "true" : "false") . "\n"; +echo "getRefout(): " . ($params32->getRefout() ? "true" : "false") . "\n"; +echo "getXorout(): 0x" . strtoupper(dechex($params32->getXorout())) . "\n"; +echo "getCheck(): 0x" . strtoupper(dechex($params32->getCheck())) . "\n"; + +$keys32 = $params32->getKeys(); +echo "getKeys() returns array: " . (is_array($keys32) ? "true" : "false") . "\n"; +echo "getKeys() array length: " . count($keys32) . "\n"; +echo "getKeys() first element is int: " . (is_int($keys32[0]) ? "true" : "false") . "\n"; + +echo "\nTesting getter methods with alternative 32-bit CRC parameters:\n"; + +$params32_alt = new Params( + width: 32, + poly: 0x1021, + init: 0, + refin: false, + refout: false, + xorout: 0, + check: 0x70a79f31 +); + +echo "getWidth(): " . $params32_alt->getWidth() . "\n"; +echo "getPoly(): 0x" . strtoupper(dechex($params32_alt->getPoly())) . "\n"; +echo "getInit(): 0x" . strtoupper(dechex($params32_alt->getInit())) . "\n"; +echo "getRefin(): " . ($params32_alt->getRefin() ? "true" : "false") . "\n"; +echo "getRefout(): " . ($params32_alt->getRefout() ? "true" : "false") . "\n"; +echo "getXorout(): 0x" . strtoupper(dechex($params32_alt->getXorout())) . "\n"; +echo "getCheck(): 0x" . strtoupper(dechex($params32_alt->getCheck())) . "\n"; + +$keys32_alt = $params32_alt->getKeys(); +echo "getKeys() returns array: " . (is_array($keys32_alt) ? "true" : "false") . "\n"; +echo "getKeys() array length: " . count($keys32_alt) . "\n"; +echo "getKeys() first element is int: " . (is_int($keys32_alt[0]) ? "true" : "false") . "\n"; + +echo "\nTesting getter methods consistency:\n"; + +// Test that getters return the same values used in constructor +$test_width = 32; +$test_poly = 0x1021; +$test_init = 0x12345678; +$test_refin = true; +$test_refout = false; +$test_xorout = 0x87654321; +$test_check = 0x1234ABCD; // This will likely fail validation, but we'll catch it + +try { + $params_test = new Params($test_width, $test_poly, $test_init, $test_refin, $test_refout, $test_xorout, $test_check); + echo "Getter consistency test created successfully\n"; + echo "Width matches: " . ($params_test->getWidth() === $test_width ? "true" : "false") . "\n"; + echo "Poly matches: " . ($params_test->getPoly() === $test_poly ? "true" : "false") . "\n"; + echo "Init matches: " . ($params_test->getInit() === $test_init ? "true" : "false") . "\n"; + echo "Refin matches: " . ($params_test->getRefin() === $test_refin ? "true" : "false") . "\n"; + echo "Refout matches: " . ($params_test->getRefout() === $test_refout ? "true" : "false") . "\n"; + echo "Xorout matches: " . ($params_test->getXorout() === $test_xorout ? "true" : "false") . "\n"; +} catch (Exception $e) { + echo "Getter consistency test failed validation (expected)\n"; +} + +echo "\nTesting edge case values:\n"; + +// Test with minimum values +$params_min = new Params( + width: 32, + poly: 1, + init: 0, + refin: false, + refout: false, + xorout: 0, + check: 0x04040c3d // Computed check for these parameters +); + +echo "Minimum poly value: " . $params_min->getPoly() . "\n"; +echo "Zero init value: " . $params_min->getInit() . "\n"; +echo "False refin: " . ($params_min->getRefin() ? "true" : "false") . "\n"; +echo "False refout: " . ($params_min->getRefout() ? "true" : "false") . "\n"; + +// Test with maximum 32-bit values +$params_max = new Params( + width: 32, + poly: 0xFFFFFFFF, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x2aa0a2a4 // Computed check for these parameters +); + +echo "Maximum poly value: 0x" . strtoupper(dechex($params_max->getPoly())) . "\n"; +echo "Maximum init value: 0x" . strtoupper(dechex($params_max->getInit())) . "\n"; +echo "Maximum xorout value: 0x" . strtoupper(dechex($params_max->getXorout())) . "\n"; + +echo "\nAll getter method tests completed\n"; + +?> +--EXPECT-- +Testing getter methods with 32-bit CRC parameters: +getWidth(): 32 +getPoly(): 0x4C11DB7 +getInit(): 0xFFFFFFFF +getRefin(): true +getRefout(): true +getXorout(): 0xFFFFFFFF +getCheck(): 0xCBF43926 +getKeys() returns array: true +getKeys() array length: 23 +getKeys() first element is int: true + +Testing getter methods with alternative 32-bit CRC parameters: +getWidth(): 32 +getPoly(): 0x1021 +getInit(): 0x0 +getRefin(): false +getRefout(): false +getXorout(): 0x0 +getCheck(): 0x70A79F31 +getKeys() returns array: true +getKeys() array length: 23 +getKeys() first element is int: true + +Testing getter methods consistency: +Getter consistency test failed validation (expected) + +Testing edge case values: +Minimum poly value: 1 +Zero init value: 0 +False refin: false +False refout: false +Maximum poly value: 0xFFFFFFFF +Maximum init value: 0xFFFFFFFF +Maximum xorout value: 0xFFFFFFFF + +All getter method tests completed \ No newline at end of file