Skip to content

Commit a4ff6df

Browse files
authored
Merge pull request #1 from kduma-OSS/claude/practical-curie-rVFhG
Add C# / .NET implementation of PCF (Partitioned Container Format)
2 parents a16441d + 79bc43c commit a4ff6df

19 files changed

Lines changed: 2357 additions & 0 deletions

.github/workflows/dotnet-ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: .NET CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
paths:
7+
- 'implementations/dotnet/**'
8+
- '.github/workflows/dotnet-ci.yml'
9+
pull_request:
10+
branches: [master]
11+
paths:
12+
- 'implementations/dotnet/**'
13+
- '.github/workflows/dotnet-ci.yml'
14+
15+
defaults:
16+
run:
17+
working-directory: implementations/dotnet
18+
19+
jobs:
20+
test:
21+
name: build & test (${{ matrix.os }})
22+
runs-on: ${{ matrix.os }}
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
os: [ubuntu-latest, macos-latest, windows-latest]
27+
steps:
28+
- uses: actions/checkout@v4
29+
- uses: actions/setup-dotnet@v4
30+
with:
31+
dotnet-version: '8.0.x'
32+
- run: dotnet restore
33+
- run: dotnet build -c Release --no-restore
34+
- run: dotnet test -c Release --no-build --verbosity normal

implementations/dotnet/.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Build output
2+
bin/
3+
obj/
4+
5+
# Test / coverage artifacts
6+
TestResults/
7+
*.trx
8+
coverage*.xml
9+
coverage*.json
10+
lcov.info
11+
12+
# IDE
13+
.vs/
14+
*.user

implementations/dotnet/Pcf.sln

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{63A6E215-7EF4-4962-9B27-CF2319CFB786}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pcf", "src\Pcf\Pcf.csproj", "{56ECBB20-66D5-46BF-814F-75314C47F12F}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D8F1EE7A-DB12-40AB-9285-E83D94EA85D9}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pcf.Tests", "tests\Pcf.Tests\Pcf.Tests.csproj", "{E9BF8A73-B5FE-415D-933F-B9D9D7C7A3D3}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Release|Any CPU = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
23+
{56ECBB20-66D5-46BF-814F-75314C47F12F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24+
{56ECBB20-66D5-46BF-814F-75314C47F12F}.Debug|Any CPU.Build.0 = Debug|Any CPU
25+
{56ECBB20-66D5-46BF-814F-75314C47F12F}.Release|Any CPU.ActiveCfg = Release|Any CPU
26+
{56ECBB20-66D5-46BF-814F-75314C47F12F}.Release|Any CPU.Build.0 = Release|Any CPU
27+
{E9BF8A73-B5FE-415D-933F-B9D9D7C7A3D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28+
{E9BF8A73-B5FE-415D-933F-B9D9D7C7A3D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
29+
{E9BF8A73-B5FE-415D-933F-B9D9D7C7A3D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
30+
{E9BF8A73-B5FE-415D-933F-B9D9D7C7A3D3}.Release|Any CPU.Build.0 = Release|Any CPU
31+
EndGlobalSection
32+
GlobalSection(NestedProjects) = preSolution
33+
{56ECBB20-66D5-46BF-814F-75314C47F12F} = {63A6E215-7EF4-4962-9B27-CF2319CFB786}
34+
{E9BF8A73-B5FE-415D-933F-B9D9D7C7A3D3} = {D8F1EE7A-DB12-40AB-9285-E83D94EA85D9}
35+
EndGlobalSection
36+
EndGlobal

implementations/dotnet/README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Pcf — Partitioned Container Format (C# / .NET implementation)
2+
3+
A C# reader/writer for **PCF v1.0**, a language-agnostic binary container that
4+
stores multiple independent byte regions ("partitions") in one file.
5+
6+
This is the first port of the Rust [reference implementation](../../reference/PCF-v1.0)
7+
and tracks the written specification ([`PCF-spec-v1.0.txt`](../../specs/PCF-spec-v1.0.txt))
8+
field-for-field. It is verified byte-for-byte against the spec's canonical
9+
395-byte test vector (section 15) and, like the reference, favours auditability
10+
over performance.
11+
12+
## Layout
13+
14+
```
15+
[ 20-byte header ] [ table block(s) ] [ partition data regions ]
16+
```
17+
18+
* **Header** (20 B): magic `0x89 K P R T 0x0D 0x0A 0x1A`, major/minor version,
19+
absolute offset of the first table block.
20+
* **Table block**: 74-byte header (`partition_count`, `next_table_offset`,
21+
hash algo + 64-byte block hash) followed by `partition_count` entries.
22+
Blocks form a singly linked chain to hold more than 255 partitions.
23+
* **Entry** (141 B): `type`, 16-byte UID, 32-byte ASCII label, `start_offset`,
24+
`max_length`, `used_bytes`, 1-byte data-hash algorithm, 64-byte data hash.
25+
26+
All integers are little-endian. Free space is `max_length - used_bytes`.
27+
28+
## Hash registry
29+
30+
| id | algorithm | id | algorithm |
31+
|----|------------------|----|-----------|
32+
| 0 | none | 5 | SHA-1 |
33+
| 1 | CRC-32/ISO-HDLC | 16 | SHA-256 (default) |
34+
| 2 | CRC-32C | 17 | SHA-512 |
35+
| 3 | CRC-64/XZ | 18 | BLAKE3 |
36+
| 4 | MD5 | | |
37+
38+
SHA-1/2 and MD5 use `System.Security.Cryptography`; the three CRCs are
39+
self-contained reflected-CRC routines (`Crc.cs`); BLAKE3 uses the
40+
[`Blake3`](https://www.nuget.org/packages/Blake3) NuGet package.
41+
42+
## Usage
43+
44+
```csharp
45+
using System.IO;
46+
using System.Text;
47+
using Pcf;
48+
49+
var c = Container.Create(new MemoryStream());
50+
c.AddPartition(
51+
partitionType: 0x10,
52+
uid: new byte[16] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
53+
label: "notes",
54+
data: Encoding.ASCII.GetBytes("hello"),
55+
extraReserve: 64,
56+
dataHashAlgo: HashAlgo.Sha256);
57+
58+
c.Verify();
59+
60+
foreach (PartitionEntry e in c.Entries())
61+
{
62+
byte[] data = c.ReadPartitionData(e);
63+
// ...
64+
}
65+
66+
// Reclaim unused reservations and produce the canonical, tightly packed form:
67+
byte[] image = c.CompactedImage();
68+
```
69+
70+
`Container` is backed by any readable/writable/seekable `System.IO.Stream`
71+
(both `MemoryStream` and `FileStream` work) — the C# analogue of the
72+
reference's `Read + Write + Seek` store. Operations raise `PcfException`, whose
73+
`Kind` (`PcfError`) identifies the exact failure.
74+
75+
## Project structure
76+
77+
```
78+
implementations/dotnet/
79+
├── Pcf.sln
80+
├── src/Pcf/ # the library (targets netstandard2.0)
81+
│ ├── Constants.cs # on-disk constants (spec Appendix A)
82+
│ ├── PcfError.cs # PcfError + PcfException
83+
│ ├── LittleEndian.cs # explicit little-endian helpers
84+
│ ├── Crc.cs # CRC-32 / CRC-32C / CRC-64
85+
│ ├── HashAlgo.cs # hash-algorithm registry (spec 8)
86+
│ ├── FileHeader.cs # 20-byte header (spec 4)
87+
│ ├── PartitionEntry.cs # 141-byte entry + labels (spec 5.2, 10)
88+
│ ├── TableBlockHeader.cs # 74-byte block header + table hash (spec 5.1, 8.4)
89+
│ └── Container.cs # high-level reader/writer
90+
└── tests/Pcf.Tests/ # xUnit suite (targets net8.0)
91+
├── SpecComplianceTests.cs # one assertion per normative MUST/SHALL
92+
├── RoundtripTests.cs # end-to-end create/read/verify/update/remove/compact
93+
└── CoverageTests.cs # error paths, every hash algo, label edge cases
94+
```
95+
96+
The library targets `netstandard2.0` for broad reach; the test project targets
97+
`net8.0`. BLAKE3 is provided by `Blake3` 0.6.1 — the last release that ships a
98+
`netstandard2.0` assembly together with native runtimes for linux/macOS/windows
99+
(x64 + arm64).
100+
101+
## Building and testing
102+
103+
```sh
104+
cd implementations/dotnet
105+
dotnet build -c Release
106+
dotnet test -c Release
107+
```
108+
109+
The section-15 test (`S15_canonical_vector_is_byte_exact`) proves this
110+
implementation emits the spec's exact 395-byte file.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace Pcf;
2+
3+
/// <summary>
4+
/// On-disk constants defined by PCF v1.0. Every value here is normative and
5+
/// corresponds directly to a figure in the specification (Appendix A,
6+
/// "Field Layout Summary").
7+
/// </summary>
8+
public static class Constants
9+
{
10+
/// <summary>File signature, 8 bytes: <c>0x89 'K' 'P' 'R' 'T' 0x0D 0x0A 0x1A</c>.</summary>
11+
public static readonly byte[] Magic =
12+
{ 0x89, (byte)'K', (byte)'P', (byte)'R', (byte)'T', 0x0D, 0x0A, 0x1A };
13+
14+
/// <summary>Major format version implemented by this library.</summary>
15+
public const ushort VersionMajor = 1;
16+
17+
/// <summary>Minor format version implemented by this library.</summary>
18+
public const ushort VersionMinor = 0;
19+
20+
/// <summary>Fixed size of the file header, in bytes.</summary>
21+
public const long HeaderSize = 20;
22+
23+
/// <summary>Fixed size of a table-block header, in bytes.</summary>
24+
public const long TableHeaderSize = 74;
25+
26+
/// <summary>Fixed size of a single partition entry, in bytes.</summary>
27+
public const long EntrySize = 141;
28+
29+
/// <summary>Size of every hash field, in bytes (large enough for the widest digest).</summary>
30+
public const int HashFieldSize = 64;
31+
32+
/// <summary>Size of the partition label field, in bytes.</summary>
33+
public const int LabelSize = 32;
34+
35+
/// <summary>Size of the partition UID field, in bytes.</summary>
36+
public const int UidSize = 16;
37+
38+
/// <summary>Reserved partition type: invalid / uninitialised. MUST NOT label a live partition.</summary>
39+
public const uint TypeReserved = 0x0000_0000;
40+
41+
/// <summary>Reserved partition type: raw / blob, interpreted entirely by the application.</summary>
42+
public const uint TypeRaw = 0xFFFF_FFFF;
43+
44+
/// <summary>
45+
/// Maximum number of entries a single table block can hold
46+
/// (<c>partition_count</c> is a <c>u8</c>).
47+
/// </summary>
48+
public const uint MaxEntriesPerBlock = 255;
49+
50+
/// <summary>The NIL UID (all zero). MUST NOT label a live partition.</summary>
51+
public static readonly byte[] NilUid = new byte[UidSize];
52+
}

0 commit comments

Comments
 (0)