Skip to content

Option to disable strict dictionary key ordering in decoder #2

Description

@EdouardCourty

Option to disable strict dictionary key ordering in decoder

Summary

The decoder currently enforces lexicographic key ordering in bencode dictionaries (BEP 3 spec-compliant). While this is correct for encoding, applying this constraint to decoding breaks interoperability with real-world BitTorrent trackers that routinely emit out-of-order keys.

Affected version: 4.3.2


Reproduction

use Arokettu\Bencode\Bencode;
use Arokettu\Bencode\Bencode\Collection;

// ✅ Spec-compliant: keys in lexicographic order
$correct = 'd8:completei5e10:incompletei2e8:intervali1800e5:peers0:e';
var_dump(Bencode::decode($correct, dictType: Collection::ARRAY));
// Works fine

// ❌ Non-compliant: 'peers' before 'complete' — real output from chihaya tracker
$incorrect = 'd8:intervali1800e5:peers0:e8:completei5e10:incompletei2ee';
var_dump(Bencode::decode($incorrect, dictType: Collection::ARRAY));
// ParseErrorException: Invalid order of dictionary keys: 'complete' after 'peers'

To reproduce with a live tracker, run chihaya via Docker and capture its raw response:

docker run -d -p 6969:6969 quay.io/jzelinskie/chihaya-git:latest
curl -s "http://localhost:6969/announce?info_hash=aaaaaaaaaaaaaaaaaaaa&peer_id=bbbbbbbbbbbbbbbbbbbb&port=1234&uploaded=0&downloaded=0&left=0&compact=1" | xxd
# Output: d8:intervali10e12:min intervali5e5:peers6:...8:completei1e10:incompletei0ee
#                                                    ^^^^^                ^^^^^^^^^
#                                         'peers' comes before 'complete' — invalid order

Expected behavior

An option to decode leniently, tolerating out-of-order dictionary keys:

// Proposed API (naming up for discussion)
Bencode::decode($data, dictType: Collection::ARRAY, strictDictOrder: false);

The fix in Engine/Reader.php is minimal — a single guard on line 193:

// Current:
if ($prevKey && strcmp($prevKey, $dictKey) >= 0) {
    throw new ParseErrorException("Invalid order of dictionary keys: ...");
}

// With option:
if ($this->strictDictOrder && $prevKey && strcmp($prevKey, $dictKey) >= 0) {
    throw new ParseErrorException("Invalid order of dictionary keys: ...");
}

Context

Tested against two popular tracker implementations:

  • opentracker (C) — outputs correctly sorted keys ✅
  • chihaya (Go) — outputs unsorted keys ❌ (e.g. peers before complete)

The BEP 3 spec requires sorted keys for encoding but says nothing about decoding strictness. A lenient decoder is necessary for any PHP client that wants to communicate with real-world trackers.

Default behaviour should remain unchanged (strict = true) for backward compatibility.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions