Skip to content

Commit c290104

Browse files
committed
Cross-port conformance for PCF-DCP
Mirror the PCF-SIG cross-port machinery for DCP so all four implementations are held to the same byte-exact canonical vector. - reference/PCF-DCP-v1.0/tests/cross_port_testdata.rs: assert that the testdata/canonical.bin shipped by each language port (TS, PHP, .NET) is byte-identical to the reference 700-byte vector, and that the reference is exactly 700 bytes. - cross-port-interop.yml: add a cross-port-byte-exact-dcp job that regenerates the DCP vector from each writer (Rust example, TS/PHP gen-testvector, an on-the-fly .NET CLI over ReferenceVector.Build) and asserts all four match the pinned SHA-256 b9bb5979… and each other; add DCP trigger paths. Verified locally: the Rust, TypeScript, PHP and .NET writers all emit the same 700-byte file with SHA-256 b9bb59794abed008863063886d8d0daa810c44939c1c5d29449475ced8156b90. https://claude.ai/code/session_01XzcjWWbNiuNX9ZywevfbQu
1 parent d605cb2 commit c290104

2 files changed

Lines changed: 196 additions & 0 deletions

File tree

.github/workflows/cross-port-interop.yml

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ on:
1616
paths:
1717
- 'reference/PCF-SIG-v1.0/**'
1818
- 'implementations/**/pcf-sig/**'
19+
- 'reference/PCF-DCP-v1.0/**'
20+
- 'implementations/**/pcf-dcp/**'
1921
- 'implementations/ts/package.json'
2022
- 'implementations/ts/package-lock.json'
2123
- 'implementations/dotnet/Directory.Build.props'
@@ -25,6 +27,8 @@ on:
2527
paths:
2628
- 'reference/PCF-SIG-v1.0/**'
2729
- 'implementations/**/pcf-sig/**'
30+
- 'reference/PCF-DCP-v1.0/**'
31+
- 'implementations/**/pcf-dcp/**'
2832
- 'implementations/ts/package.json'
2933
- 'implementations/ts/package-lock.json'
3034
- 'implementations/dotnet/Directory.Build.props'
@@ -169,3 +173,111 @@ jobs:
169173
/tmp/ts.bin
170174
/tmp/php.bin
171175
/tmp/dotnet.bin
176+
177+
cross-port-byte-exact-dcp:
178+
name: all DCP writers produce identical bytes
179+
runs-on: ubuntu-latest
180+
# Expected SHA-256 of the canonical 700-byte DCP vector (spec Section 17).
181+
env:
182+
EXPECTED_SHA256: b9bb59794abed008863063886d8d0daa810c44939c1c5d29449475ced8156b90
183+
steps:
184+
- uses: actions/checkout@v4
185+
186+
- uses: dtolnay/rust-toolchain@stable
187+
- uses: Swatinem/rust-cache@v2
188+
189+
- uses: actions/setup-node@v4
190+
with:
191+
node-version: 22
192+
cache: npm
193+
cache-dependency-path: implementations/ts/package-lock.json
194+
195+
- uses: shivammathur/setup-php@v2
196+
with:
197+
php-version: '8.3'
198+
extensions: hash, mbstring
199+
coverage: none
200+
tools: composer:v2
201+
202+
- uses: actions/setup-dotnet@v4
203+
with:
204+
dotnet-version: '8.0.x'
205+
206+
# ---- Rust reference writer --------------------------------------------
207+
- name: Generate Rust reference vector
208+
run: cargo run -p pcf-dcp --example gen_testvector -- /tmp/rust.bin
209+
210+
# ---- TypeScript writer ------------------------------------------------
211+
- name: Install npm deps and build pcf
212+
working-directory: implementations/ts
213+
run: |
214+
npm ci
215+
npm run build -w @kduma-oss/pcf
216+
- name: Generate TS vector
217+
working-directory: implementations/ts
218+
run: npm run gen-testvector -w @kduma-oss/pcf-dcp -- /tmp/ts.bin
219+
220+
# ---- PHP writer -------------------------------------------------------
221+
- name: Install composer deps
222+
working-directory: implementations/php/pcf-dcp
223+
run: composer install --prefer-dist --no-progress --no-interaction
224+
- name: Generate PHP vector
225+
working-directory: implementations/php/pcf-dcp
226+
run: php examples/gen_testvector.php /tmp/php.bin
227+
228+
# ---- .NET writer ------------------------------------------------------
229+
- name: Generate .NET vector
230+
run: |
231+
mkdir -p /tmp/dotnet-gen
232+
cat > /tmp/dotnet-gen/GenTestVector.csproj <<'EOF'
233+
<Project Sdk="Microsoft.NET.Sdk">
234+
<PropertyGroup>
235+
<OutputType>Exe</OutputType>
236+
<TargetFramework>net8.0</TargetFramework>
237+
<Nullable>disable</Nullable>
238+
</PropertyGroup>
239+
<ItemGroup>
240+
<ProjectReference Include="$(GITHUB_WORKSPACE)/implementations/dotnet/pcf-dcp/src/Pcf.Dcp/Pcf.Dcp.csproj" />
241+
</ItemGroup>
242+
</Project>
243+
EOF
244+
cat > /tmp/dotnet-gen/Program.cs <<'EOF'
245+
using System.IO;
246+
using Pcf.Dcp;
247+
File.WriteAllBytes(args[0], ReferenceVector.Build());
248+
EOF
249+
dotnet run --project /tmp/dotnet-gen/GenTestVector.csproj -c Release -- /tmp/dotnet.bin
250+
251+
# ---- Compare ----------------------------------------------------------
252+
- name: All four DCP writers agree on byte-exact output
253+
run: |
254+
set -euo pipefail
255+
ls -l /tmp/rust.bin /tmp/ts.bin /tmp/php.bin /tmp/dotnet.bin
256+
fail=0
257+
for f in /tmp/rust.bin /tmp/ts.bin /tmp/php.bin /tmp/dotnet.bin; do
258+
d=$(sha256sum "$f" | awk '{print $1}')
259+
echo "$f -> $d"
260+
if [ "$d" != "$EXPECTED_SHA256" ]; then
261+
echo "::error::$f sha256 = $d (expected $EXPECTED_SHA256)"
262+
fail=1
263+
fi
264+
if ! cmp -s /tmp/rust.bin "$f"; then
265+
echo "::error::$f differs from /tmp/rust.bin"
266+
fail=1
267+
fi
268+
done
269+
if [ "$fail" != "0" ]; then
270+
echo "Cross-port DCP writer interop FAILED"
271+
exit 1
272+
fi
273+
echo "All four DCP writers produced sha256 = $EXPECTED_SHA256"
274+
275+
- uses: actions/upload-artifact@v4
276+
if: always()
277+
with:
278+
name: cross-port-vectors-dcp
279+
path: |
280+
/tmp/rust.bin
281+
/tmp/ts.bin
282+
/tmp/php.bin
283+
/tmp/dotnet.bin
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//! Cross-port test-vector parity check.
2+
//!
3+
//! Every PCF-DCP language port ships its own copy of the canonical 700-byte
4+
//! container vector under `implementations/<lang>/pcf-dcp/testdata/
5+
//! canonical.bin`. Each port's own test suite asserts that its writer produces
6+
//! this byte sequence; this Rust workspace test additionally asserts that the
7+
//! shipped *files* are byte-identical, so that any future regeneration of the
8+
//! reference vector cannot leave one port out of sync.
9+
10+
use std::fs;
11+
use std::path::{Path, PathBuf};
12+
13+
/// The reference vector compiled into the test binary.
14+
const REFERENCE: &[u8] = include_bytes!("../testdata/canonical.bin");
15+
16+
/// Locate the repository root from this crate's `CARGO_MANIFEST_DIR`.
17+
/// reference/PCF-DCP-v1.0 → repository root is two levels up.
18+
fn repo_root() -> PathBuf {
19+
Path::new(env!("CARGO_MANIFEST_DIR"))
20+
.parent()
21+
.expect("PCF-DCP-v1.0 crate has a parent (reference/)")
22+
.parent()
23+
.expect("reference/ has a parent (repo root)")
24+
.to_path_buf()
25+
}
26+
27+
fn read_port_vector(rel: &str) -> Vec<u8> {
28+
let path = repo_root().join(rel);
29+
fs::read(&path).unwrap_or_else(|e| {
30+
panic!(
31+
"failed to read {}: {e}\n\
32+
every PCF-DCP language port MUST ship a copy of the canonical \
33+
test vector identical to reference/PCF-DCP-v1.0/testdata/canonical.bin",
34+
path.display(),
35+
)
36+
})
37+
}
38+
39+
fn assert_byte_identical(label: &str, port: &[u8]) {
40+
assert_eq!(
41+
port.len(),
42+
REFERENCE.len(),
43+
"{label} ships canonical.bin of length {} bytes; reference is {} bytes",
44+
port.len(),
45+
REFERENCE.len(),
46+
);
47+
if port != REFERENCE {
48+
let first_diff = port
49+
.iter()
50+
.zip(REFERENCE.iter())
51+
.position(|(a, b)| a != b)
52+
.unwrap_or(REFERENCE.len());
53+
panic!(
54+
"{label} canonical.bin diverges from reference at offset {first_diff}: \
55+
port byte = 0x{:02x}, reference byte = 0x{:02x}",
56+
port.get(first_diff).copied().unwrap_or(0),
57+
REFERENCE.get(first_diff).copied().unwrap_or(0),
58+
);
59+
}
60+
}
61+
62+
#[test]
63+
fn typescript_port_testdata_matches_reference() {
64+
let port = read_port_vector("implementations/ts/pcf-dcp/testdata/canonical.bin");
65+
assert_byte_identical("TypeScript port", &port);
66+
}
67+
68+
#[test]
69+
fn php_port_testdata_matches_reference() {
70+
let port = read_port_vector("implementations/php/pcf-dcp/testdata/canonical.bin");
71+
assert_byte_identical("PHP port", &port);
72+
}
73+
74+
#[test]
75+
fn dotnet_port_testdata_matches_reference() {
76+
let port = read_port_vector("implementations/dotnet/pcf-dcp/testdata/canonical.bin");
77+
assert_byte_identical(".NET port", &port);
78+
}
79+
80+
/// Sanity: the reference itself is the canonical 700-byte vector we expect.
81+
#[test]
82+
fn reference_has_canonical_length() {
83+
assert_eq!(REFERENCE.len(), 700);
84+
}

0 commit comments

Comments
 (0)