This section of the documentation is for developers that wish to contribute to the PKD server software.
Where possible, the software adheres to the requirements set forth in the Public Key Directory specification.
This package does not rely on any popular PHP framework (Symfony, Laravel, etc.). Instead, we adopted reusable components (such as The League's Router).
The runtime configuration is defined in autoload.php, which instantiates
a ServerConfig object with the various files inside
config/.
The ServerConfig object contains several classes used throughout the entire application.
Commonly called "Controllers" in MVC frameworks, we instead have a bunch of classes inside `src/RequestHandlers that implement PSR-15.
The routes are defined in config/routes.php, although you may override this by creating
a routes.php file inside config/local/.
Commonly called "Models" in MVC frameworks, the classes inside src/Tables
extend the base Table class.
Generally, each Table class corresponds to a distinct table in the SQL database.
Important
MerkleState is a bit of an exception to this rule, but it
generally holds.
Most updates to other tables are guarded by MerkleState::insertLeaf().
This is to ensure the exclusive locking of the pkd_merkle_state database table is respected by concurrent PHP
processes, and (more to the point) to keep the "append only" nature of the Merkle Tree intact (whereas multiple
concurrent writes would result in an invalid state).
Each change to an Actor, their list of currently-trusted Public Keys, or their list of currently-valid Auxiliary Data MUST be tied to a specific, unique record in the Merkle Tree history.
Note
This is not normative to the spec, but an implementation detail we do in the PHP software.
When messages are to be stored encrypted in the database, we re-wrap the client-provided symmetric key (if you recall, this is required in order for attributes to be crypto-shreddable) using CipherSweet.
Specifically, we have a custom implementation of CipherSweet's EncryptedRow class called
WrappedEncryptedRow that reads/writes the per-field key to
another field in the same SQL row.
ActivityPub messages are accepted at the Inbox Request Handler. This loads them up into a message queue to be processed asynchronously at a later time.
Next, the ASQueue class processes the queued up ActivityStream messages.
From the ASQueue class, messages will be passed to the appropriate method inside Protocol.
Finally, updates to the appropriate SQL table will occur in a transaction on the appropriate table class.
Tip
We recommend running unit tests on a Unix-based OS, since Redis doesn't support Windows.
Make sure you update your dependencies with Composer, then run PHPUnit:
vendor/bin/phpunitThis will run a battery of unit tests against the server software.
See .github/workflows/fuzz.yml for an up-to-date list of fuzzing commands.
Next, install PHP-Fuzzer globally:
composer global require --dev nikic/php-fuzzerOnce PHP-Fuzzer is installed, you can run this in a terminal window:
php-fuzzer fuzz --max-runs 100000000 fuzzing/PHP_SCRIPT_NAME_GOES_HEREMake sure you replace the 100000000 with the desired number of fuzz runs and PHP_SCRIPT_NAME_GOES_HERE with an
actual filename from the fuzzing/ directory.
Not to mince words: Mutation testing can be very annoying. If you don't add enough #[UsesClass] attributes to your
PHPUnit test case classes, it will bail out before any mutation tests are actually executed.
For this reason, you are not required to run mutation tests locally. If anything breaks, we'll assume responsibility for playing attribute whack-a-mole with the PHPUnit coverage driver that our mutation testing framework uses.
Caution
Mutation testing is also somewhat resource-intensive. If you run it on a laptop, we cannot guarantee your battery will remain at full charge.
However, if you'd like to go the extra mile, you first need the pcov extension installed on your device.
Next, install Infection, like so:
composer require --dev infection/infection Finally, run this command:
vendor/bin/infectionIf everything goes well, you should get an infection.log file with the escaped mutants. The MSI score will also be printed out to your terminal.
We currently employ three different static analysis tools in CI.
Psalm is installed by default. You can run it like so:
vendor/bin/psalmPHPStan is installed by default. You can run it like so:
vendor/bin/phpstan analyze --level 5 --memory-limit 1024MSee the Semgrep website for setup instructions.
Once you have Semgrep installed locally, you can run the following command to generate a SARIF file.
semgrep scan \
--config auto \
--config p/phpcs-security-audit \
--config p/security-audit \
--config p/secure-defaults \
--config p/owasp-top-ten \
--config p/cwe-top-25 \
--config p/trailofbits \
--sarif \
--sarif-output=semgrep.sarifIf you use an IDE with SARIF viewer support, any security issues identified by Semgrep will show when you look at the relevant files.
We use php-cs-fixer to ensure the source code adheres to a consistent style. The desired code style is similar to PSR-12, with a few tweaks (for personal preference).
To identify code style violations:
vendor/bin/php-cs-fixer fix --dry-run --allow-risky=yesTo automatically fix code style violations, simply drop --dry-run:
vendor/bin/php-cs-fixer fix --allow-risky=yesTip
This is optional for third-party contributors. If a PR fails because the reference docs are out of date, don't sweat
it. We'll run it on the main branch after merging.
To regenerate the reference docs, simply run this PHP script:
php docs/reference/generate.php