Skip to content

Commit d1e87c3

Browse files
authored
Merge pull request #16 from kduma-OSS/claude/dynamic-nonlinear-partitions-vSXIn
2 parents 3d3afc6 + 484866d commit d1e87c3

29 files changed

Lines changed: 5196 additions & 6 deletions

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,30 @@ jobs:
135135
- run: cargo clippy -p pcf-compact --all-targets -- -D warnings
136136
- run: cargo build -p pcf-compact --verbose
137137
- run: cargo test -p pcf-compact --verbose
138+
139+
pcf-dcp:
140+
name: pcf-dcp profile
141+
runs-on: ubuntu-latest
142+
defaults:
143+
run:
144+
working-directory: .
145+
steps:
146+
- uses: actions/checkout@v4
147+
- uses: dtolnay/rust-toolchain@stable
148+
with:
149+
components: rustfmt, clippy
150+
- uses: Swatinem/rust-cache@v2
151+
- run: cargo fmt -p pcf-dcp -- --check
152+
- run: cargo clippy -p pcf-dcp --all-targets -- -D warnings
153+
- run: cargo build -p pcf-dcp --verbose
154+
- run: cargo test -p pcf-dcp --verbose
155+
- name: Regenerate the spec test vector
156+
run: cargo run -p pcf-dcp --example gen_testvector -- pcf_dcp_testvector.bin
157+
- name: Inspect generated test vector (spec Section 17 is 700 bytes)
158+
run: |
159+
ls -l pcf_dcp_testvector.bin
160+
test "$(wc -c < pcf_dcp_testvector.bin)" = "700"
161+
- uses: actions/upload-artifact@v4
162+
with:
163+
name: pcf-dcp-testvector
164+
path: pcf_dcp_testvector.bin

.github/workflows/release-prepare.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,16 @@ jobs:
7676
sed -i 's/^version = "[^"]*"/version = "'"$NEW"'"/' reference/PCF-v1.0/Cargo.toml
7777
sed -i 's/^version = "[^"]*"/version = "'"$NEW"'"/' reference/PFS-MS-v1.0/Cargo.toml
7878
sed -i 's/^version = "[^"]*"/version = "'"$NEW"'"/' reference/PCF-SIG-v1.0/Cargo.toml
79+
sed -i 's/^version = "[^"]*"/version = "'"$NEW"'"/' reference/PCF-DCP-v1.0/Cargo.toml
7980
sed -i 's/^version = "[^"]*"/version = "'"$NEW"'"/' tools/pcf-debug/Cargo.toml
8081
sed -i 's/^version = "[^"]*"/version = "'"$NEW"'"/' tools/pcf-compact/Cargo.toml
8182
# path-dep version pins on pcf
8283
sed -i 's|pcf = { path = "\.\./PCF-v1.0", version = "[^"]*" }|pcf = { path = "../PCF-v1.0", version = "'"$NEW"'" }|' reference/PFS-MS-v1.0/Cargo.toml
8384
sed -i 's|pcf = { path = "\.\./PCF-v1.0", version = "[^"]*" }|pcf = { path = "../PCF-v1.0", version = "'"$NEW"'" }|' reference/PCF-SIG-v1.0/Cargo.toml
85+
sed -i 's|pcf = { path = "\.\./PCF-v1.0", version = "[^"]*" }|pcf = { path = "../PCF-v1.0", version = "'"$NEW"'" }|' reference/PCF-DCP-v1.0/Cargo.toml
8486
sed -i 's|pcf = { path = "\.\./\.\./reference/PCF-v1.0", version = "[^"]*" }|pcf = { path = "../../reference/PCF-v1.0", version = "'"$NEW"'" }|' tools/pcf-debug/Cargo.toml
8587
sed -i 's|pcf-sig = { path = "\.\./\.\./reference/PCF-SIG-v1.0", version = "[^"]*" }|pcf-sig = { path = "../../reference/PCF-SIG-v1.0", version = "'"$NEW"'" }|' tools/pcf-debug/Cargo.toml
88+
sed -i 's|pcf-dcp = { path = "\.\./\.\./reference/PCF-DCP-v1.0", version = "[^"]*" }|pcf-dcp = { path = "../../reference/PCF-DCP-v1.0", version = "'"$NEW"'" }|' tools/pcf-debug/Cargo.toml
8689
sed -i 's|pcf = { path = "\.\./\.\./reference/PCF-v1.0", version = "[^"]*" }|pcf = { path = "../../reference/PCF-v1.0", version = "'"$NEW"'" }|' tools/pcf-compact/Cargo.toml
8790
8891
- name: Bump TypeScript packages

.github/workflows/release.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ jobs:
132132
if: needs.resolve.outputs.dry_run != 'true'
133133
run: sleep 45
134134

135+
- name: cargo publish pcf-dcp
136+
shell: bash
137+
run: |
138+
if [ "${{ needs.resolve.outputs.dry_run }}" = "true" ]; then
139+
cargo publish -p pcf-dcp --allow-dirty --dry-run
140+
else
141+
cargo publish -p pcf-dcp --allow-dirty --token "${{ steps.cargo-auth.outputs.token }}"
142+
fi
143+
144+
- name: Wait for crates.io index
145+
if: needs.resolve.outputs.dry_run != 'true'
146+
run: sleep 45
147+
135148
- name: cargo publish pcf-debug
136149
shell: bash
137150
run: |

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"reference/PCF-v1.0",
55
"reference/PFS-MS-v1.0",
66
"reference/PCF-SIG-v1.0",
7+
"reference/PCF-DCP-v1.0",
78
"tools/pcf-debug",
89
"tools/pcf-compact",
910
"tools/pcf-sig",

reference/PCF-DCP-v1.0/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "pcf-dcp"
3+
version = "0.0.8"
4+
edition = "2021"
5+
description = "Reference implementation of PCF-DCP v1.0, the PCF Dynamic Container Partition profile"
6+
license = "MIT OR Apache-2.0"
7+
repository = "https://github.com/kduma-OSS/Partitioned-Container-Format"
8+
homepage = "https://github.com/kduma-OSS/Partitioned-Container-Format"
9+
readme = "README.md"
10+
keywords = ["pcf", "dcp", "container", "deduplication", "fragmentation"]
11+
categories = ["encoding", "filesystem"]
12+
13+
# This crate is a *reference* implementation of the PCF-DCP profile. Like the
14+
# `pcf` crate it builds on, it favours a direct, auditable mapping onto the
15+
# written specification (`specs/PCF-DCP-spec-v1.0.txt`) over raw performance.
16+
17+
[[bin]]
18+
name = "dcp"
19+
path = "src/bin/dcp.rs"
20+
21+
[dependencies]
22+
# The PCF-DCP profile is layered strictly above PCF v1.0; every byte container
23+
# operation goes through the reference PCF crate. The arena reuses PCF's Table
24+
# Block, Partition Entry, and table-hash primitives directly.
25+
pcf = { path = "../PCF-v1.0", version = "0.0.8" }

reference/PCF-DCP-v1.0/README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# pcf-dcp — PCF Dynamic Container Partition (reference implementation)
2+
3+
Reference reader/writer for **PCF-DCP v1.0**, an application-level profile that
4+
adds *dynamic*, fragmentable, dedup-friendly sub-partitions to the
5+
[Partitioned Container Format](../PCF-v1.0) without modifying the PCF byte
6+
container.
7+
8+
This crate mirrors the written specification (`specs/PCF-DCP-spec-v1.0.txt`)
9+
field-for-field and is intended as the *normative* implementation against which
10+
language ports are checked. It favours auditability over performance.
11+
12+
## Model at a glance
13+
14+
PCF-DCP defines one new PCF partition type:
15+
16+
| Type | Name | Holds |
17+
|--------------|-----------------|----------------------------------------------------|
18+
| `0xAAAC0001` | `DCP_CONTAINER` | An *arena*: a header, an inner partition table, fragment tables, and data extents |
19+
20+
A DCP container's bytes are an **arena** addressed by arena-relative offsets:
21+
22+
```
23+
arena:
24+
[ DCP Header (24 B) | data extents | Fragment Tables | Inner Table Block(s) ]
25+
```
26+
27+
* **DCP Header**`"PDCP"` magic, profile version, `inner_table_offset`,
28+
`arena_used` (a bump pointer).
29+
* **Inner Table Block** — a chain of reused PCF Table Blocks (74 B header +
30+
141 B entries), byte-for-byte identical to the top-level table, listing the
31+
*inner* partitions. Two entry fields are reinterpreted: `start_offset` points
32+
at the partition's Fragment Table, and `max_length` equals `used_bytes`.
33+
* **Fragment Table** — per inner partition, a chain of 9-byte block headers each
34+
followed by 18-byte **Fragment Entries**. Each entry names one extent
35+
`(offset, length, kind, flags)`. The logical content of an inner partition is
36+
the concatenation of its DATA extents.
37+
38+
A generic PCF reader sees a DCP file as **one opaque partition**; only a
39+
DCP-aware reader looks inside. A DCP file is always a conforming PCF v1.0 file.
40+
41+
## Why a profile
42+
43+
PCF stores each partition as a contiguous, statically-reserved region. PCF-DCP
44+
makes each *inner* partition grow, shrink, and be edited in the middle without
45+
relocating its neighbours, by describing it as a list of extents rather than one
46+
range. This buys:
47+
48+
* **Fragmentation / random edits** — append, insert, overwrite, delete, and
49+
truncate are edits of the Fragment Table (copy-on-write for shared bytes); no
50+
data is moved.
51+
* **Deduplication** — two extents may name the same arena bytes; identical
52+
chunks are stored once. The per-extent `SHARED` flag makes safe in-place
53+
editing explicit.
54+
* **Hash / signature stability** — an inner partition's `data_hash` covers its
55+
*logical content*, so fragmentation, dedup, compaction, and promotion all
56+
leave the hash (and any PCF-SIG signature over it) unchanged.
57+
58+
## Library example
59+
60+
```rust
61+
use std::io::Cursor;
62+
use pcf_dcp::{Arena, Chunker, DcpReader, DcpWriter, HashAlgo};
63+
64+
let mut arena = Arena::new();
65+
arena.add_inner(0x10, [0xA1; 16], "A", b"Hello, World!", HashAlgo::Sha256, Chunker::Fixed(7))?;
66+
arena.add_inner(0x10, [0xB2; 16], "B", b"World!", HashAlgo::Sha256, Chunker::Whole)?;
67+
68+
let mut w = DcpWriter::new();
69+
w.add_container([0xDC; 16], "dcp", arena)?;
70+
let image = w.to_image()?;
71+
72+
let mut r = DcpReader::open(Cursor::new(image))?;
73+
r.verify()?;
74+
assert_eq!(r.read_inner(&[0xB2; 16])?, b"World!");
75+
# Ok::<(), pcf_dcp::Error>(())
76+
```
77+
78+
## Promotion / demotion
79+
80+
`DcpWriter::promote` moves an inner partition out to a top-level PCF partition
81+
(dynamic → fixed); `demote` moves a top-level partition into a container
82+
(fixed → dynamic). Both preserve `uid`, `partition_type`, `label`,
83+
`data_hash_algo_id`, and `data_hash` — the **promotion invariant**, identical to
84+
the set of fields a PCF-SIG signature protects.
85+
86+
## Command-line tool
87+
88+
The `dcp` binary inspects and rewrites DCP files; every mutating command
89+
re-verifies before writing:
90+
91+
```
92+
dcp info <file>
93+
dcp dedup <file> [--fixed N] [--trailer]
94+
dcp defrag <file> [--trailer]
95+
dcp promote <file> <container-uid> <inner-uid> [--trailer]
96+
dcp demote <file> <part-uid> <container-uid> [--trailer]
97+
```
98+
99+
UIDs are 32 hex digits, or `0xNN` for a uid of 16 identical bytes (e.g. `0xDC`).
100+
101+
## Build & test
102+
103+
```
104+
cargo test -p pcf-dcp
105+
cargo run -p pcf-dcp --example gen_testvector -- /tmp/dcp.bin # the 700-byte vector
106+
cargo run -p pcf-dcp --bin dcp -- info /tmp/dcp.bin
107+
```
108+
109+
The example reproduces the byte-exact 700-byte test vector from Section 17 of
110+
the specification.
111+
112+
## Relationship to `pcf`
113+
114+
This crate is layered strictly above [`pcf`](../PCF-v1.0): every container byte
115+
operation goes through the reference PCF crate, and the arena reuses PCF's Table
116+
Block, Partition Entry, and table-hash primitives directly.
117+
118+
## Licence
119+
120+
MIT OR Apache-2.0.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! Generates the canonical PCF-DCP v1.0 test-vector file used in spec
2+
//! Section 17.
3+
//!
4+
//! Run with: `cargo run --example gen_testvector -- <output-path>`
5+
//! (defaults to ./pcf_dcp_testvector.bin). Everything is fixed and
6+
//! deterministic so that ports can reproduce the file byte-for-byte.
7+
8+
use std::io::Cursor;
9+
10+
use pcf::Container;
11+
use pcf_dcp::{build_reference_vector, DcpReader};
12+
13+
fn main() {
14+
let path = std::env::args()
15+
.nth(1)
16+
.unwrap_or_else(|| "pcf_dcp_testvector.bin".to_string());
17+
18+
let image = build_reference_vector().expect("build reference vector");
19+
std::fs::write(&path, &image).expect("write file");
20+
21+
// It is a conforming PCF v1.0 file ...
22+
let mut pcf = Container::open(Cursor::new(image.clone())).expect("pcf open");
23+
pcf.verify().expect("pcf verify");
24+
25+
// ... and a conforming DCP file.
26+
let mut dcp = DcpReader::open(Cursor::new(image.clone())).expect("dcp open");
27+
dcp.verify().expect("dcp verify");
28+
29+
eprintln!("wrote {} ({} bytes)", path, image.len());
30+
for c in dcp.containers().expect("containers") {
31+
let arena = dcp.open_arena(&c).expect("arena");
32+
eprintln!(
33+
" container {:<6} type=0x{:08X} used={} inners={}",
34+
c.label_string().unwrap_or_default(),
35+
c.partition_type,
36+
c.used_bytes,
37+
arena.len()
38+
);
39+
for info in arena.inners() {
40+
let n = info.data_hash_algo.digest_len();
41+
let hex: String = info.data_hash[..n]
42+
.iter()
43+
.map(|b| format!("{b:02x}"))
44+
.collect();
45+
let shared = info.extents.iter().filter(|e| e.shared).count();
46+
eprintln!(
47+
" inner {:<3} type=0x{:08X} used={} extents={} shared={} data_hash={}",
48+
info.label,
49+
info.partition_type,
50+
info.used_bytes,
51+
info.extents.len(),
52+
shared,
53+
hex
54+
);
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)