Compare commits

..

4 commits

Author SHA1 Message Date
dtrentin
3312a57757 feat(08-license-integration): wire RSA license check, remove -chiave arg
Replace AES checkLicense with MachineFingerprint+LicenseFile+LicenseValidator.
License.lic hardcoded next to EXE; -chiave= CLI arg and InputArgs.chiave removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 23:10:05 +02:00
dtrentin
39a9566858 feat(07-license-core): RSA offline license core
Phase 07 complete:
- IMachineFingerprint + ILicenseValidator interfaces
- MachineFingerprint: WMI CPU ProcessorID + BaseBoard SerialNumber
- LicenseValidator: RSACryptoServiceProvider + SHA256, injectable public key
- LicenseFile.TryRead: out-param bool, no exceptions from file I/O
- 6 NUnit tests (valid/wrong-machine/tampered/empty/not-base64/missing-file)
- 118 tests total, 116 pass, 2 Fanuc FOCAS ignored (Windows DLL)
- Fix: missing 'using System' in CncMachineFactoryTests.cs
- Fix: Dockerfile.test updated for NcProgramManager rename

PublicKeyXml = PLACEHOLDER until Phase 09 generates real key pair.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:58:14 +02:00
dtrentin
a5d73db69a refactor(rename): FanucProgramManager → NcProgramManager
Phase 06 complete:
- Namespace renamed in 62 .cs files (namespace + using directives)
- AssemblyName + RootNamespace updated in csproj files
- FanucProgramManager.csproj → NcProgramManager.csproj
- FanucProgramManager.sln → NcProgramManager.sln
- FanucProgramManager.Tests/ → NcProgramManager.Tests/
- FANUCMachine.cs deleted (dead code, zero references)

Fanuc-prefixed class names kept (FanucMachine, FanucProgram,
IFanucMachine) — manufacturer identifiers, not project names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:31:36 +02:00
dtrentin
9bb65c4748 docs(paul): unify phase 5 and close v0.1 milestone
Phase 5 (NC Validator) UNIFY complete.
v0.1 Multi-CN Release milestone closed — all 5 phases done.

- 05-01-SUMMARY.md: NcProgramValidator + 30+ tests documented
- ROADMAP.md: all phases marked complete, v0.2 scope drafted
- PROJECT.md: requirements evolved, tech stack updated to 4 manufacturers
- STATE.md: milestone 100%, loop closed, next action documented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:19:50 +02:00
81 changed files with 1860 additions and 1412 deletions

View file

@ -1,8 +1,8 @@
# FanucProgramManager # NcProgramManager
## What This Is ## What This Is
Console EXE (C# .NET 4.7.2) for upload/download of CNC programs to/from machine controllers. Targets any CN manufacturer, not Fanuc-only. Runs on Windows, distributed to licensed machine operators and integrators. Console EXE (C# .NET 4.7.2) for upload/download of CNC programs to/from machine controllers. Supports Fanuc FOCAS, Heidenhain LSV2, Siemens Sinumerik FTP, and Mitsubishi FTP. Runs on Windows, distributed to licensed machine operators and integrators.
## Core Value ## Core Value
@ -13,9 +13,9 @@ Machinists and operators transfer CNC programs to/from any controller without ma
| Attribute | Value | | Attribute | Value |
|-----------|-------| |-----------|-------|
| Type | Application | | Type | Application |
| Version | 0.0.0 | | Version | 0.2.0-dev |
| Status | In development | | Status | v0.2 in progress — Phase 07 complete |
| Last Updated | 2026-05-13 | | Last Updated | 2026-05-17 |
## Requirements ## Requirements
@ -31,12 +31,20 @@ Machinists and operators transfer CNC programs to/from any controller without ma
- [x] AES encryption for program files - [x] AES encryption for program files
- [x] CLI argument parsing - [x] CLI argument parsing
- [x] Machine-bound license check (hardcoded) - [x] Machine-bound license check (hardcoded)
- [x] Multi-CN support — Fanuc, Heidenhain, Siemens, Mitsubishi — Phase 14
- [x] 3-file management mode (Fanuc) via IFanucMachine — Phase 4
- [x] NC program validation before upload (all 4 manufacturers) — Phase 5
- [x] Project renamed FanucProgramManager → NcProgramManager — Phase 6
### Active (In Progress) ### Validated (Shipped) — continued
- [ ] 3-file management mode — branch `3FilesManagement` - [x] RSA offline license core: IMachineFingerprint, ILicenseValidator, LicenseValidator, LicenseFile — Phase 7
### Planned (Next) ### Planned (Next)
- To be defined during /paul:plan - [ ] Wire license into Program.cs — replace checkLicense, replace -chiave= with -licfile= (Phase 08)
- [ ] License generator tool — sign fingerprint with RSA private key, write .lic file (Phase 09)
- [ ] Replace PublicKeyXml PLACEHOLDER with real vendor key pair (Phase 09 output)
- [ ] ReadActiveProgramAsync / SelectMainProgram for Heidenhain and Siemens
- [ ] Integration test against real hardware (Heidenhain, Siemens, Mitsubishi)
### Out of Scope ### Out of Scope
- Cloud sync - Cloud sync
@ -63,14 +71,21 @@ Machinists and operators transfer CNC programs to/from any controller without ma
| .NET 4.7.2 | Max compatibility with operator machines | 2026-05-13 | Active | | .NET 4.7.2 | Max compatibility with operator machines | 2026-05-13 | Active |
| Console EXE output | Simple deployment, scriptable | 2026-05-13 | Active | | Console EXE output | Simple deployment, scriptable | 2026-05-13 | Active |
| Offline license only | Operators may have no internet on shop floor | 2026-05-13 | Active | | Offline license only | Operators may have no internet on shop floor | 2026-05-13 | Active |
| `#` = Warning in Fanuc/Mitsubishi | Macro variable indicator — valid in context | 2026-05-13 | Active |
| Constructor injection for validator | Internal ctor accepts mock — enables unit testing without Windows DLL | 2026-05-13 | Active |
| RSA asymmetric licensing | Private key vendor-only; public key embedded in binary — decompiler can't forge licenses | 2026-05-17 | Active |
| PublicKeyXml PLACEHOLDER until Phase 09 | Real key pair not yet generated; prevents blocking Phase 07 on Phase 09 | 2026-05-17 | Active |
| Heidenhain LSV2 port 19000 | Standard LSV2 port; needs real hardware verification | 2026-05-13 | Active |
| Siemens FTP dir `/_N_MPF_DIR/` | Sinumerik standard MPF directory; FTP option must be enabled | 2026-05-13 | Active |
## Success Metrics ## Success Metrics
| Metric | Target | Current | Status | | Metric | Target | Current | Status |
|--------|--------|---------|--------| |--------|--------|---------|--------|
| Program transfer correctness | 100% (byte-verified) | Unknown | Not measured | | Program transfer correctness | 100% (byte-verified) | Unknown | Pending hardware test |
| License validation (offline) | Works without network | Hardcoded | At risk | | License validation (offline) | Works without network | RSA core built, wiring Phase 08 | In progress |
| Multi-CN support | ≥2 manufacturers | Fanuc only | In progress | | Multi-CN support | ≥2 manufacturers | 4 (Fanuc/Heidenhain/Siemens/Mitsubishi) | Done |
| NC validation coverage | All manufacturers | 4 manufacturers, 30+ tests | Done |
## Tech Stack / Tools ## Tech Stack / Tools
@ -78,11 +93,15 @@ Machinists and operators transfer CNC programs to/from any controller without ma
|-------|------------|-------| |-------|------------|-------|
| Language | C# | .NET Framework 4.7.2 | | Language | C# | .NET Framework 4.7.2 |
| UI | Console EXE | CLI args via InputArgs.cs | | UI | Console EXE | CLI args via InputArgs.cs |
| CN Protocol (Fanuc) | FOCAS fwlib32 | 32-bit DLL | | CN Protocol (Fanuc) | FOCAS fwlib32 | 32-bit DLL, Windows-only |
| CN Protocol (others) | To be defined | Multi-CN expansion | | CN Protocol (Heidenhain) | LSV2 over TCP | Port 19000, CRC-16/CCITT |
| CN Protocol (Siemens) | FTP (FtpWebRequest) | ASCII mode, passive, `/_N_MPF_DIR/` |
| CN Protocol (Mitsubishi) | FTP (FtpWebRequest) | Port 21, `/PRG/`, no extensions |
| Validation | NcProgramValidator | Universal + per-manufacturer rules |
| Encryption | AES (AesCrypt.cs) | Program file protection | | Encryption | AES (AesCrypt.cs) | Program file protection |
| Licensing | Machine fingerprint | Hardcoded now, needs design | | Licensing | RSA-SHA256 asymmetric | IMachineFingerprint + ILicenseValidator; wiring in Phase 08 |
| Testing | NUnit 3.13.3 + Moq 4.18.4 | .NET 4.7.2 test project |
--- ---
*PROJECT.md — Updated when requirements or context change* *PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-05-13* *Last updated: 2026-05-17 after Phase 7 (License Core)*

View file

@ -2,82 +2,117 @@
## Overview ## Overview
Console tool for CNC program transfer and file management. Fanuc FOCAS base, expanding to multi-CN (Heidenhain LSV2, Siemens FTP) with robust offline licensing. Console tool for CNC program transfer and file management. Fanuc FOCAS base, expanding to multi-CN (Heidenhain LSV2, Siemens FTP, Mitsubishi FTP) with NC program validation.
## Current Milestone ## Current Milestone
**v0.1 Multi-CN Release** (v0.1.0) **v0.1 Multi-CN Release** (v0.1.0)
Status: In progress Status: ✅ Complete
Phases: 0 of 4 complete Phases: 5 of 5 complete
Completed: 2026-05-13
## Phases ## Phases
| Phase | Name | Plans | Status | Completed | | Phase | Name | Plans | Status | Completed |
|-------|------|-------|--------|-----------| |-------|------|-------|--------|-----------|
| 1 | cnc-abstraction | 2 | In progress | - | | 1 | cnc-abstraction | 1 | ✅ Complete | 2026-05-13 |
| 2 | heidenhain | 1 | Not started | - | | 2 | heidenhain | 1 | ✅ Complete | 2026-05-13 |
| 3 | siemens | 1 | Not started | - | | 3 | siemens | 1 | ✅ Complete | 2026-05-13 |
| 4 | program-wiring | 1 | Not started | - | | 4 | program-wiring | 1 | ✅ Complete | 2026-05-13 |
| 5 | nc-validator | 1 | ✅ Complete | 2026-05-13 |
## Phase Details ## Phase Details
### Phase 1: cnc-abstraction ### Phase 1: cnc-abstraction
**Goal:** Fix namespace, add all Cnc/ files to csproj, complete ICncMachine + IFanucMachine with 3-file support **Goal:** Fix namespace, add all Cnc/ files to csproj, complete ICncMachine + IFanucMachine with 3-file support
**Depends on:** Nothing **Completed:** 2026-05-13
**Research:** Unlikely
**Scope:** **Delivered:**
- Fix namespace Fanuc_integration → FanucProgramManager.Cnc - Namespace `Fanuc_integration.Cnc.*``FanucProgramManager.Cnc.*` (29 files)
- Add using FanucProgramManager to Fanuc-specific files - `IFocasDialect` extended: `UploadStart`/`DownloadStart` accept `short fileType`
- Extend IFocasDialect to accept fileType for tool/work data - `IFanucMachine` interface: extends `ICncMachine` with tool offset + work zero R/W
- Add IFanucMachine interface with tool offset + work zero ops - `CncManufacturer` enum: Fanuc, Heidenhain, Siemens
- Add CncManufacturer enum, CncMachineFactory stub - All 29 `Cnc/**/*.cs` files added to csproj
- Add all Cnc/ to csproj
**Plans:** ### Phase 2: heidenhain ✅
- [ ] 01-01: Fix namespace, extend dialect interface, IFanucMachine
- [ ] 01-02: CncManufacturer, CncMachineFactory, add to csproj
### Phase 2: heidenhain
**Goal:** Implement ICncMachine for Heidenhain TNC via LSV2 protocol **Goal:** Implement ICncMachine for Heidenhain TNC via LSV2 protocol
**Depends on:** Phase 1 **Completed:** 2026-05-13
**Research:** Likely (LSV2 binary protocol)
**Scope:** **Delivered:**
- HeidenhainConnectionConfig - `HeidenhainConnectionConfig` — IP, port 19000, credentials, timeout
- Lsv2Client (TCP framing, CRC16, commands) - `Lsv2Client` — TCP framing, CRC-16/CCITT, login (SELECT+LOG_IN), ReceiveFile/SendFile
- HeidenhainMachine : ICncMachine - `HeidenhainMachine : ICncMachine` — RunGuardedAsync pattern, state machine
**Plans:** ### Phase 3: siemens ✅
- [ ] 02-01: Heidenhain LSV2 full implementation
### Phase 3: siemens
**Goal:** Implement ICncMachine for Siemens Sinumerik via FTP **Goal:** Implement ICncMachine for Siemens Sinumerik via FTP
**Depends on:** Phase 1 **Completed:** 2026-05-13
**Research:** Unlikely (standard FTP)
**Scope:** **Delivered:**
- SiemensConnectionConfig - `SiemensConnectionConfig` — IP, port 21, creds, program dir `/_N_MPF_DIR/`
- SiemensMachine : ICncMachine via FtpWebRequest - `SiemensFtpClient` — FtpWebRequest wrapper, ASCII mode, passive
- `SiemensMachine : ICncMachine`
**Plans:** **Bonus (post-apply):**
- [ ] 03-01: Siemens FTP implementation - `MitsubishiConnectionConfig` / `MitsubishiFtpClient` / `MitsubishiMachine : ICncMachine`
- `CncManufacturer.Mitsubishi` added to enum + factory + CLI
### Phase 4: program-wiring ### Phase 4: program-wiring
**Goal:** Wire all 3 manufacturers into Program.cs + InputArgs, replace old FANUCMachine **Goal:** Wire all manufacturers into Program.cs + InputArgs
**Depends on:** Phase 1, 2, 3 **Completed:** 2026-05-13
**Scope:** **Delivered:**
- Update InputArgs with -manufacturer= param - `CncMachineFactory` — creates right `ICncMachine` from `InputArgs`
- Refactor Program.cs to use ICncMachine factory - `InputArgs` — added `manufacturer`, `username`, `password` fields
- Keep 3-file logic for Fanuc only - `Program.cs` — full rewrite using `ICncMachine` factory; Fanuc 3-file via `IFanucMachine` cast
**Plans:** ### Phase 5: nc-validator ✅
- [ ] 04-01: InputArgs + Program.cs refactor
**Goal:** Per-manufacturer NC program validator runs inside WriteProgramAsync before upload
**Completed:** 2026-05-13
**Delivered:**
- `ValidationSeverity` / `ValidationError` / `ValidationResult` model types
- `INcProgramValidator` + `NcProgramValidator` with universal + per-manufacturer rules
- Validator injected into all 4 machine classes
- `NcProgramValidatorTests` — 30+ NUnit tests
--- ---
*Roadmap created: 2026-05-13*
## Next Milestone
**v0.2 NcProgramManager** (in progress)
Status: In Progress
Phases: 3 of 4 complete
| Phase | Name | Plans | Status |
|-------|------|-------|--------|
| 06 | rename-cleanup | 1 | ✅ Complete |
| 07 | license-core | 1 | ✅ Complete |
| 08 | license-integration | 1 | ✅ Complete | 2026-05-17 |
| 09 | license-generator | 1 | Not started |
### Phase 06: rename-cleanup ✅
**Goal:** Rename FanucProgramManager → NcProgramManager (namespace, project files, test folder). Delete dead FANUCMachine.cs.
**Completed:** 2026-05-17
**Plans:** [x] 06-01: Namespace + file rename
### Phase 07: license-core ✅
**Goal:** `IMachineFingerprint`, `ILicenseValidator`, RSA signature verification, `.lic` file model
**Completed:** 2026-05-17
**Plans:** [x] 07-01: RSA core + interfaces + 6 unit tests
### Phase 08: license-integration ✅
**Goal:** Wire license system into Program.cs, delete `checkLicense`, remove `-chiave=`, hardcode `license.lic` next to EXE
**Completed:** 2026-05-17
**Plans:** [x] 08-01: Program.cs wiring
### Phase 09: license-generator (proposed)
**Goal:** Separate generator console app — reads machine fingerprint, signs, writes `.lic` file
**Depends on:** Phase 07
---
*Roadmap updated: 2026-05-17 — Phase 08 license-integration complete*

View file

@ -2,110 +2,127 @@
## Project Reference ## Project Reference
See: .paul/PROJECT.md (updated 2026-05-13) See: .paul/PROJECT.md (updated 2026-05-17)
**Core value:** Machinists transfer CNC programs to/from any controller without manual steps or internet **Core value:** Machinists transfer CNC programs to/from any controller without manual steps or internet
**Current focus:** 4-CN implementation complete — awaiting user verification and UNIFY **Current focus:** v0.2 — Phase 08 complete, Phase 09 (license-generator) next
## Current Position ## Current Position
Milestone: v0.1 Multi-CN Release Milestone: v0.2 NcProgramManager
Phase: 5 of 5 — Applied Phase: 4 of 4 (09-license-generator) — Not started
Plan: All plans applied, awaiting user UNIFY Status: Phase 08 complete, ready to plan Phase 09
Status: APPLY complete, ready for UNIFY Last activity: 2026-05-17 — Phase 08 license-integration complete (UNIFY done)
Last activity: 2026-05-13 — Phase 5 NC Validator applied by agent
Progress: Progress:
- Milestone: [█████████░] 90% - Milestone v0.1: [██████████] 100% (complete)
- Milestone v0.2: [███████░░░] 75%
- Phase 09: [░░░░░░░░░░] 0%
## Loop Position ## Loop Position
Current loop state:
``` ```
PLAN ──▶ APPLY ──▶ UNIFY PLAN ──▶ APPLY ──▶ UNIFY
✓ ✓ ○ [Awaiting user verification + /paul:unify] ✓ ✓ ✓ [Phase 08 closed]
``` ```
## What was built (all 4 phases) ## What Was Built (v0.1 Multi-CN Release)
### Phase 1 — Cnc abstraction fixed ### Phase 1 — CNC Abstraction
- Namespace: `Fanuc_integration.Cnc.*``FanucProgramManager.Cnc.*` across 29 files - Namespace: `Fanuc_integration.Cnc.*``FanucProgramManager.Cnc.*` (29 files)
- `using FanucProgramManager;` added to all FOCAS-using files - `IFocasDialect` extended: `UploadStart`/`DownloadStart` accept `short fileType`
- `IFocasDialect` extended: `UploadStart`/`DownloadStart` now accept `short fileType`
- `IFanucMachine` created: extends `ICncMachine` with tool offset + work zero R/W - `IFanucMachine` created: extends `ICncMachine` with tool offset + work zero R/W
- `FanucMachine` now implements `IFanucMachine` - `CncManufacturer` enum: Fanuc, Heidenhain, Siemens, Mitsubishi
- `CncManufacturer` enum: Fanuc, Heidenhain, Siemens - All 29 `Cnc/**/*.cs` files added to csproj
- All 29 `Cnc/**/*.cs` files added to `.csproj` (36 total Compile entries)
### Phase 2 — Heidenhain LSV2 ### Phase 2 — Heidenhain LSV2
- `HeidenhainConnectionConfig` — IP, port 19000, credentials, timeout - `HeidenhainConnectionConfig` — IP, port 19000, credentials, timeout
- `Lsv2Client` — TCP framing, CRC-16/CCITT, login (SELECT+LOG_IN), ReceiveFile/SendFile - `Lsv2Client` — TCP framing, CRC-16/CCITT, login (SELECT+LOG_IN), ReceiveFile/SendFile
- `HeidenhainMachine : ICncMachine` — RunGuardedAsync pattern, state machine - `HeidenhainMachine : ICncMachine` — RunGuardedAsync pattern, state machine
### Phase 3.1 — Mitsubishi M-series (added post-apply) ### Phase 3 — Siemens + Mitsubishi FTP
- `MitsubishiConnectionConfig` — port 21, dir `/PRG/` (no file extensions) - `SiemensConnectionConfig` / `SiemensFtpClient` / `SiemensMachine : ICncMachine`
- `MitsubishiFtpClient` / `MitsubishiMachine : ICncMachine` — FTP, same pattern as Siemens - `MitsubishiConnectionConfig` / `MitsubishiFtpClient` / `MitsubishiMachine : ICncMachine`
- `CncManufacturer.Mitsubishi` added to enum + factory + CLI parsing
### Phase 3 — Siemens Sinumerik FTP
- `SiemensConnectionConfig` — IP, port 21, creds, program dir `/_N_MPF_DIR/`
- `SiemensFtpClient` — FtpWebRequest wrapper, ASCII mode, passive
- `SiemensMachine : ICncMachine` — same pattern as FanucMachine
### Phase 4 — Wiring ### Phase 4 — Wiring
- `CncMachineFactory` — creates right ICncMachine from InputArgs - `CncMachineFactory` — creates right `ICncMachine` from `InputArgs`
- `InputArgs` — added `manufacturer`, `username`, `password` fields - `InputArgs` — added `manufacturer`, `username`, `password` fields
- `Program.cs` — full rewrite using ICncMachine; Fanuc 3-file via IFanucMachine cast - `Program.cs` — full rewrite using `ICncMachine`; Fanuc 3-file via `IFanucMachine` cast
### Phase 5 — NC Validator ### Phase 5 — NC Validator
- `ValidationSeverity` enum (Error/Warning) - `ValidationSeverity` / `ValidationError` / `ValidationResult` model types
- `ValidationError` — Line, Message, Severity - `INcProgramValidator` + `NcProgramValidator` (5 universal + per-manufacturer rules)
- `ValidationResult` — Success + IReadOnlyList<ValidationError>, static Ok()/Fail() - Validator injected into all 4 machine classes; upload blocked on Error severity
- `INcProgramValidator` interface - `NcProgramValidatorTests` — 30+ NUnit tests
- `NcProgramValidator` — universal rules (block length ≤80, no lowercase, no tabs, no nested parens, comment ≤32); per-manufacturer name format + forbidden char rules; `#` in Fanuc/Mitsubishi is Warning not Error
- All 4 machines injected: public constructor creates `NcProgramValidator(manufacturer)`, internal constructor accepts `INcProgramValidator` for testing
- All 4 machines call validator in `WriteProgramAsync` before upload; fail on any Error-severity violation
- `NcProgramValidatorTests` — 30+ test cases covering all rules and all manufacturers
- Existing machine unit tests updated: inject always-ok mock validator to preserve existing behaviour tests
- Integration tests updated: content+names corrected to pass validation per manufacturer
- 5 new Compile entries in main csproj + 1 in tests csproj
## New CLI args ### Test Infrastructure
- `-manufacturer=fanuc|heidenhain|siemens` (default: fanuc) - `NcProgramManager.Tests/` — NUnit 3.13.3 + Moq 4.18.4, .NET 4.7.2
- `-username=` (Heidenhain/Siemens) - `FtpServerStub` + `Lsv2ServerStub` — in-process TCP stubs
- `-password=` (Heidenhain/Siemens) - Unit + integration tests; Fanuc FOCAS tests `[Ignore]`d (Windows-only DLL)
## What Was Built (v0.2 NcProgramManager — in progress)
### Phase 06 — Rename Cleanup
- Namespace: `FanucProgramManager.*``NcProgramManager.*` (all files)
- Project renamed, test folder renamed, dead `FANUCMachine.cs` deleted
### Phase 07 — License Core
- `IMachineFingerprint` / `ILicenseValidator` interfaces
- `MachineFingerprint` — WMI CPU/BIOS fingerprint, SHA256 hash
- `LicenseFile.TryRead` — reads `.lic` file, returns base64 signature
- `LicenseValidator.Validate` — RSA-SHA256 verify; PLACEHOLDER public key until Phase 09
- `LicenseValidatorTests` — 6 unit tests (testable via internal ctor injection)
### Phase 08 — License Integration
- `Program.cs``checkLicense` deleted; RSA check wired at startup:
```csharp
string _licPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic");
if (!LicenseFile.TryRead(_licPath, out _licBase64)
|| !new LicenseValidator().Validate(_licFingerprint, _licBase64)) { ... }
```
- `InputArgs.cs``chiave` field removed
- `-chiave=` CLI arg removed; license file hardcoded next to EXE as `license.lic`
- `MostraAiuto` updated: instructs user to place `license.lic` next to executable
## CLI Args (current)
- `-manufacturer=fanuc|heidenhain|siemens|mitsubishi` (default: fanuc)
- `-azione=` — action (Scarica/Invia/etc.)
- `-ip=` — CNC IP
- `-porta=` — port
- `-username=` (Heidenhain/Siemens/Mitsubishi)
- `-password=` (Heidenhain/Siemens/Mitsubishi)
- License: place `license.lic` next to the EXE (no CLI arg)
## Accumulated Context ## Accumulated Context
### Decisions ### Decisions
- .NET 4.7.2 locked — max compatibility - .NET 4.7.2 locked — max compatibility
- Offline-only license check (AES, unchanged) - RSA asymmetric offline licensing: private key vendor-only, public key embedded in binary
- `license.lic` always at `AppDomain.CurrentDomain.BaseDirectory` — no CLI override
- `RSACryptoServiceProvider` + `SHA256CryptoServiceProvider` (not HashAlgorithmName) — .NET 4.7.2 compat
- Constructor injection pattern: internal ctor accepts key XML — testable without Windows WMI
- PublicKeyXml = PLACEHOLDER until Phase 09 generates real key pair
- Heidenhain: LSV2 port 19000, needs real hardware testing - Heidenhain: LSV2 port 19000, needs real hardware testing
- Siemens: FTP /_N_MPF_DIR/, needs Sinumerik FTP option enabled on machine - Siemens: FTP `/_N_MPF_DIR/`, needs Sinumerik FTP option enabled on machine
- `#` = Warning in Fanuc/Mitsubishi — macro variable indicator
### Phase 5 — Test project (added post-apply)
- `FanucProgramManager.Tests/` — NUnit 3.13.3 + Moq 4.18.4, .NET 4.7.2
- 7 unit test files (14+ tests on `FanucProgram`, `IsoProgramFormatter`, factory, all 4 machines)
- `FtpServerStub` — in-process TCP FTP (PASV); `Lsv2ServerStub` — in-process LSV2 TCP
- 3 integration test files — write→read round-trips against local stubs
- `InternalsVisibleTo("FanucProgramManager.Tests")` added to `AssemblyInfo.cs`
- Fanuc FOCAS tests that need Windows DLL are `[Ignore]`-d with explanation
### Deferred Issues ### Deferred Issues
- License system: still hardcoded AES key/IV — needs Phase 2.x redesign - Real RSA key pair — Phase 09 output; copy PublicKeyXml into LicenseValidator.cs
- Heidenhain LSV2: ReadActiveProgramAsync / SelectMainProgram not supported - `AesCrypt.cs` dead code — remove in future cleanup phase
- Siemens FTP: ReadActiveProgramAsync / SelectMainProgram not supported - Heidenhain: `ReadActiveProgramAsync` / `SelectMainProgram` not implemented
- Build not verifiable on Linux (fwlib32 is Windows-only DLL) - Siemens: same as Heidenhain
- Build + integration not verifiable on Linux (fwlib32 Windows-only)
- MachineFingerprint WMI — code-review verified, needs real Windows hardware test
### Blockers/Concerns ### Blockers/Concerns
None — code complete, pending hardware testing. None — Phase 09 can start.
## Session Continuity ## Session Continuity
Last session: 2026-05-13 Last session: 2026-05-17
Stopped at: Phase 5 (NC Validator) applied — all 12 files created/modified Stopped at: Phase 08 UNIFY complete
Next action: Build to verify compilation; run unit tests; /paul:unify when ready Next action: /paul:plan phase 09 license-generator
Resume file: .paul/STATE.md Resume file: .paul/ROADMAP.md
--- ---
*STATE.md — Updated after every significant action* *STATE.md — Updated after every significant action*

View file

@ -1,15 +1,15 @@
{ {
"name": "FanucProgramManager", "name": "NcProgramManager",
"version": "0.0.0", "version": "0.2.0-dev",
"milestone": { "milestone": {
"name": "None", "name": "v0.2 NcProgramManager",
"version": "0.0.0", "version": "0.2.0",
"status": "not_started" "status": "in_progress"
}, },
"phase": { "phase": {
"number": 0, "number": 8,
"name": "None", "name": "license-integration",
"status": "not_started" "status": "complete"
}, },
"loop": { "loop": {
"plan": null, "plan": null,
@ -17,7 +17,7 @@
}, },
"timestamps": { "timestamps": {
"created_at": "2026-05-13T00:00:00Z", "created_at": "2026-05-13T00:00:00Z",
"updated_at": "2026-05-13T00:00:00Z" "updated_at": "2026-05-17T22:00:00Z"
}, },
"satellite": { "satellite": {
"groom": true "groom": true

View file

@ -0,0 +1,134 @@
---
phase: 05-nc-validator
plan: 01
subsystem: validation
tags: [nc-program, validation, csharp, dotnet472, nunit]
requires:
- phase: 04-program-wiring
provides: ICncMachine implementations for all 4 manufacturers
provides:
- INcProgramValidator interface + NcProgramValidator implementation
- Pre-upload validation in all 4 machine WriteProgramAsync methods
- 30+ unit tests covering all rules and all manufacturers
affects: []
tech-stack:
added: []
patterns:
- "Validator injected via constructor (public creates default, internal accepts mock)"
- "ValidationResult static factory: Ok() / Fail(errors)"
key-files:
created:
- Cnc/Models/ValidationSeverity.cs
- Cnc/Models/ValidationError.cs
- Cnc/Models/ValidationResult.cs
- Cnc/INcProgramValidator.cs
- Cnc/NcProgramValidator.cs
- FanucProgramManager.Tests/Unit/NcProgramValidatorTests.cs
modified:
- Cnc/Fanuc/FanucMachine.cs
- Cnc/Heidenhain/HeidenhainMachine.cs
- Cnc/Siemens/SiemensMachine.cs
- Cnc/Mitsubishi/MitsubishiMachine.cs
- FanucProgramManager.csproj
- FanucProgramManager.Tests/FanucProgramManager.Tests.csproj
key-decisions:
- "# in Fanuc/Mitsubishi is Warning not Error — macro variable indicator, not a format error"
- "Internal constructor accepts INcProgramValidator — enables unit testing without mocking filesystem"
patterns-established:
- "Constructor injection for testability: public ctor creates real validator, internal ctor accepts mock"
- "ValidationResult.Ok() / Fail(errors) static factory pattern"
duration: unknown
started: 2026-05-13T00:00:00Z
completed: 2026-05-13T00:00:00Z
---
# Phase 5 Plan 1: NC Validator Summary
**Per-manufacturer NC program validator integrated into all 4 machine WriteProgramAsync methods, with 30+ unit tests.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~2h (estimated) |
| Started | 2026-05-13 |
| Completed | 2026-05-13 |
| Tasks | 12 files created/modified |
| Files modified | 12 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| NcProgramValidator validates all 4 manufacturers | Pass | Fanuc, Heidenhain, Siemens, Mitsubishi |
| Universal rules: block ≤80, no lowercase, no tabs, no nested parens, comment ≤32 | Pass | All rules implemented |
| Per-manufacturer name format + forbidden chars | Pass | Per spec |
| Fanuc # char is Warning not Error | Pass | Mitsubishi same |
| All 4 machines call validator in WriteProgramAsync | Pass | Verified by grep |
| Unit tests cover all rules + all manufacturers | Pass | 30+ test cases |
| csproj Compile entries added (5 main + 1 test) | Pass | Verified by grep count |
## Accomplishments
- `NcProgramValidator` with 5 universal + 4 per-manufacturer rule sets
- All 4 machine classes fail upload on any Error-severity violation
- Constructor injection pattern enables testability without test pollution
- 30+ NUnit test cases; existing machine tests updated with always-ok mock validator
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `Cnc/Models/ValidationSeverity.cs` | Created | Error/Warning enum |
| `Cnc/Models/ValidationError.cs` | Created | Line + message + severity |
| `Cnc/Models/ValidationResult.cs` | Created | Success flag + error list, Ok()/Fail() |
| `Cnc/INcProgramValidator.cs` | Created | Validator interface |
| `Cnc/NcProgramValidator.cs` | Created | Full rule implementation |
| `Cnc/Fanuc/FanucMachine.cs` | Modified | Validator injected, WriteProgramAsync gated |
| `Cnc/Heidenhain/HeidenhainMachine.cs` | Modified | Same |
| `Cnc/Siemens/SiemensMachine.cs` | Modified | Same |
| `Cnc/Mitsubishi/MitsubishiMachine.cs` | Modified | Same |
| `FanucProgramManager.Tests/Unit/NcProgramValidatorTests.cs` | Created | 30+ test cases |
| `FanucProgramManager.csproj` | Modified | +5 Compile entries |
| `FanucProgramManager.Tests/FanucProgramManager.Tests.csproj` | Modified | +1 Compile entry |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| `#` = Warning in Fanuc/Mitsubishi | Macro variable indicator — valid in context | Uploads proceed; operator notified |
| Internal constructor for mock injection | .NET 4.7.2 — no Moq interface without internal access | All machine tests remain isolated |
## Deviations from Plan
None — plan executed as specified.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Build not verifiable on Linux | fwlib32 is Windows-only DLL — tests using FOCAS marked `[Ignore]` |
## Next Phase Readiness
**Ready:**
- Validator fully integrated — any new machine implementing `ICncMachine` can inject it
- Test infrastructure in place for future expansion
**Concerns:**
- Hardware testing still pending for Heidenhain and Siemens
- License system hardcoded — deferred from earlier phases
**Blockers:** None
---
*Phase: 05-nc-validator, Plan: 01*
*Completed: 2026-05-13*

View file

@ -0,0 +1,220 @@
---
phase: 06-rename-cleanup
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- "**/*.cs (all ~62 files — namespace string replace)"
- Properties/AssemblyInfo.cs
- FanucProgramManager.csproj → NcProgramManager.csproj
- FanucProgramManager.sln → NcProgramManager.sln
- FanucProgramManager.Tests/FanucProgramManager.Tests.csproj → NcProgramManager.Tests/NcProgramManager.Tests.csproj
- FANUCMachine.cs (delete)
autonomous: true
---
<objective>
## Goal
Rename project from FanucProgramManager to NcProgramManager: namespace, assembly, project files, test folder, and delete dead legacy file.
## Purpose
Product is no longer Fanuc-only. Name FanucProgramManager is wrong. NcProgramManager reflects multi-CN scope.
## Output
- All namespaces: `FanucProgramManager``NcProgramManager`
- Project/solution files renamed
- Test folder renamed
- `FANUCMachine.cs` deleted (zero references outside itself)
- Solution builds with no errors
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Key Files
@FanucProgramManager.csproj
@FanucProgramManager.sln
@Properties/AssemblyInfo.cs
@FanucProgramManager.Tests/FanucProgramManager.Tests.csproj
</context>
<acceptance_criteria>
## AC-1: Namespace renamed everywhere
```gherkin
Given any .cs file in the project
When opened
Then namespace declarations and using directives reference NcProgramManager (not FanucProgramManager)
```
## AC-2: Project and solution files updated
```gherkin
Given the renamed solution
When opened in Visual Studio or built with msbuild
Then assembly name is NcProgramManager, test assembly is NcProgramManager.Tests, project reference resolves
```
## AC-3: Dead code removed
```gherkin
Given FANUCMachine.cs
When cleanup completes
Then file does not exist (confirmed zero references before deletion)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Replace FanucProgramManager string in all .cs files</name>
<files>All *.cs files under project root (recursively)</files>
<action>
Run find+sed to replace all occurrences of `FanucProgramManager` with `NcProgramManager` in every .cs file:
```bash
find /path/to/project -name "*.cs" -exec sed -i 's/FanucProgramManager/NcProgramManager/g' {} +
```
This covers:
- `namespace FanucProgramManager``namespace NcProgramManager`
- `namespace FanucProgramManager.Cnc``namespace NcProgramManager.Cnc`
- `using FanucProgramManager;``using NcProgramManager;`
- `using FanucProgramManager.Cnc.Fanuc;``using NcProgramManager.Cnc.Fanuc;`
- All sub-namespaces (.Cnc, .Cnc.Fanuc, .Cnc.Heidenhain, .Cnc.Siemens, .Cnc.Mitsubishi, .Cnc.Models)
- `InternalsVisibleTo("FanucProgramManager.Tests")` in AssemblyInfo.cs
- `AssemblyTitle`, `AssemblyProduct` in AssemblyInfo.cs
Avoid: editing binary files, NuGet package cache, obj/bin folders.
Use: `-not -path "*/obj/*" -not -path "*/bin/*" -not -path "*/.paul/*"`
</action>
<verify>
```bash
grep -r "FanucProgramManager" --include="*.cs" .
```
Must return zero results.
</verify>
<done>AC-1 satisfied: no .cs file contains FanucProgramManager</done>
</task>
<task type="auto">
<name>Task 2: Update project and solution metadata files</name>
<files>
FanucProgramManager.csproj,
FanucProgramManager.Tests/FanucProgramManager.Tests.csproj,
FanucProgramManager.sln
</files>
<action>
In `FanucProgramManager.csproj`:
- `<RootNamespace>FanucProgramManager</RootNamespace>``<RootNamespace>NcProgramManager</RootNamespace>`
- `<AssemblyName>FanucProgramManager</AssemblyName>``<AssemblyName>NcProgramManager</AssemblyName>`
In `FanucProgramManager.Tests/FanucProgramManager.Tests.csproj`:
- `<RootNamespace>FanucProgramManager.Tests</RootNamespace>``<RootNamespace>NcProgramManager.Tests</RootNamespace>`
- `<AssemblyName>FanucProgramManager.Tests</AssemblyName>``<AssemblyName>NcProgramManager.Tests</AssemblyName>`
- `<ProjectReference Include="..\FanucProgramManager.csproj">``<ProjectReference Include="..\NcProgramManager.csproj">`
- `<Name>FanucProgramManager</Name>``<Name>NcProgramManager</Name>`
In `FanucProgramManager.sln`:
- Project line: `"FanucProgramManager", "FanucProgramManager.csproj"``"NcProgramManager", "NcProgramManager.csproj"`
- Project line: `"FanucProgramManager.Tests", "FanucProgramManager.Tests\FanucProgramManager.Tests.csproj"``"NcProgramManager.Tests", "NcProgramManager.Tests\NcProgramManager.Tests.csproj"`
</action>
<verify>
```bash
grep -n "FanucProgramManager" FanucProgramManager.csproj FanucProgramManager.Tests/FanucProgramManager.Tests.csproj FanucProgramManager.sln
```
Must return zero results.
</verify>
<done>AC-2 satisfied: all project metadata references NcProgramManager</done>
</task>
<task type="auto">
<name>Task 3: Rename files/folder and delete dead code</name>
<files>
FANUCMachine.cs (delete),
FanucProgramManager.csproj → NcProgramManager.csproj (rename),
FanucProgramManager.sln → NcProgramManager.sln (rename),
FanucProgramManager.Tests/ → NcProgramManager.Tests/ (folder rename),
FanucProgramManager.Tests/FanucProgramManager.Tests.csproj → NcProgramManager.Tests/NcProgramManager.Tests.csproj (rename)
</files>
<action>
1. Delete `FANUCMachine.cs` — confirmed dead: no file outside itself references `FANUCMachine`. Also remove its `<Compile Include="FANUCMachine.cs" />` entry from csproj.
2. Rename project file:
```bash
mv FanucProgramManager.csproj NcProgramManager.csproj
```
3. Rename solution file:
```bash
mv FanucProgramManager.sln NcProgramManager.sln
```
4. Rename test folder (this moves all contents):
```bash
mv FanucProgramManager.Tests NcProgramManager.Tests
```
5. Rename test csproj inside the now-renamed folder:
```bash
mv NcProgramManager.Tests/FanucProgramManager.Tests.csproj NcProgramManager.Tests/NcProgramManager.Tests.csproj
```
Order matters: rename folder BEFORE renaming csproj inside it.
The .sln file was already updated in Task 2 to reference new paths — verify paths match after rename.
Avoid: renaming `Cnc/Fanuc/` folder or any Fanuc-prefixed class files — `FanucMachine`, `FanucConnectionConfig` etc. are manufacturer-specific names, keep them.
</action>
<verify>
```bash
ls NcProgramManager.csproj NcProgramManager.sln NcProgramManager.Tests/NcProgramManager.Tests.csproj
ls FANUCMachine.cs 2>&1 # must say: No such file
grep "FANUCMachine" NcProgramManager.csproj # must return nothing
```
</verify>
<done>AC-2 and AC-3 satisfied: files renamed, dead code deleted, .sln paths match physical files</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `Cnc/Fanuc/` folder name — Fanuc is a manufacturer name, not a project name
- `FanucMachine.cs`, `FanucConnectionConfig.cs`, `FanucCapabilities.cs` — manufacturer-specific class names, keep
- `FanucProgram.cs` — Fanuc ISO program format class, name is accurate
- `FanucProgramParsingTests.cs` — test file for FanucProgram, name is accurate
- `IFanucMachine.cs` — interface name correct (Fanuc-specific capability set)
- Any class name that describes Fanuc-specific behavior
## SCOPE LIMITS
- No Italian → English rename in this plan (deferred)
- No logic changes — pure rename/delete only
- No NuGet package changes
- No new files created
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `grep -r "FanucProgramManager" --include="*.cs" .` returns zero results
- [ ] `grep -r "FanucProgramManager" --include="*.csproj" .` returns zero results
- [ ] `grep "FanucProgramManager" NcProgramManager.sln` returns zero results
- [ ] `ls FANUCMachine.cs` returns "No such file"
- [ ] `ls NcProgramManager.csproj NcProgramManager.sln NcProgramManager.Tests/` all exist
- [ ] All acceptance criteria met
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No FanucProgramManager references remain in .cs, .csproj, or .sln files
- FANUCMachine.cs deleted
- Project file names match NcProgramManager
</success_criteria>
<output>
After completion, create `.paul/phases/06-rename-cleanup/06-01-SUMMARY.md`
</output>

View file

@ -0,0 +1,102 @@
---
phase: 06-rename-cleanup
plan: 01
subsystem: infra
tags: [rename, namespace, csharp, dotnet472, cleanup]
requires: []
provides:
- NcProgramManager namespace across all 62 .cs files
- NcProgramManager.csproj / NcProgramManager.sln / NcProgramManager.Tests/
- FANUCMachine.cs deleted
affects: [07-license-core, 08-license-integration, 09-license-generator]
tech-stack:
added: []
patterns: []
key-files:
created: []
modified:
- NcProgramManager.csproj (was FanucProgramManager.csproj)
- NcProgramManager.sln (was FanucProgramManager.sln)
- NcProgramManager.Tests/NcProgramManager.Tests.csproj
- All 62 .cs files (namespace string replace)
key-decisions:
- "Fanuc-prefixed class names kept: FanucMachine, FanucProgram, IFanucMachine — manufacturer names, not project names"
patterns-established: []
duration: 5min
started: 2026-05-17T00:00:00Z
completed: 2026-05-17T00:00:00Z
---
# Phase 6 Plan 1: Rename Cleanup Summary
**FanucProgramManager → NcProgramManager across 62 .cs files, 3 project/solution files, test folder rename, FANUCMachine.cs deleted.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~5 min |
| Started | 2026-05-17 |
| Completed | 2026-05-17 |
| Tasks | 3 completed |
| Files modified | 62 .cs + 3 project files + 1 folder rename + 1 file deleted |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Namespace renamed everywhere | Pass | grep returns zero results across all .cs files |
| AC-2: Project/solution files updated | Pass | AssemblyName, RootNamespace, ProjectReference all updated; files renamed |
| AC-3: Dead code removed | Pass | FANUCMachine.cs deleted; Compile entry removed from csproj |
## Accomplishments
- All namespaces, using directives, AssemblyTitle, AssemblyProduct, InternalsVisibleTo updated in one sed pass
- Project and test folder physically renamed to match new name
- Zero FanucProgramManager strings remain in .cs, .csproj, or .sln
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| All 62 `.cs` files | Modified | `FanucProgramManager``NcProgramManager` namespace/usings |
| `NcProgramManager.csproj` | Renamed + modified | AssemblyName + RootNamespace updated |
| `NcProgramManager.sln` | Renamed + modified | Project references updated |
| `NcProgramManager.Tests/NcProgramManager.Tests.csproj` | Renamed + modified | AssemblyName, RootNamespace, ProjectReference updated |
| `FANUCMachine.cs` | Deleted | Dead code — zero references outside itself |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Keep `FanucMachine`, `FanucProgram`, `IFanucMachine` names | These are manufacturer-specific identifiers, not project names | Future phases reference these names unchanged |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- All code under `NcProgramManager` namespace
- Project builds under new name (pending Windows build verification)
- Clean foundation for license system phases
**Concerns:**
- Build not verifiable on Linux (fwlib32 Windows-only DLL) — same as before
**Blockers:** None
---
*Phase: 06-rename-cleanup, Plan: 01*
*Completed: 2026-05-17*

View file

@ -0,0 +1,441 @@
---
phase: 07-license-core
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- Licensing/IMachineFingerprint.cs
- Licensing/ILicenseValidator.cs
- Licensing/LicenseFile.cs
- Licensing/MachineFingerprint.cs
- Licensing/LicenseValidator.cs
- NcProgramManager.Tests/Unit/LicenseValidatorTests.cs
- NcProgramManager.csproj
- NcProgramManager.Tests/NcProgramManager.Tests.csproj
autonomous: true
---
<objective>
## Goal
Create RSA-based offline license core: machine fingerprint reader, license file model, RSA signature validator — all injectable via interfaces for testability.
## Purpose
Current `checkLicense` in Program.cs uses hardcoded AES key — anyone with source or decompiler can generate licenses for any machine. RSA asymmetric crypto means only the holder of the private key (vendor) can issue valid licenses. Public key is safe to embed in binary.
## Output
- `Licensing/` folder with 5 new files
- `LicenseValidator` verifies RSA-SHA256 signature of machine fingerprint against embedded public key
- `MachineFingerprint` reads CPU ProcessorID + Motherboard SerialNumber via WMI (same data as current `checkLicense`)
- `LicenseFile` reads base64 signature from a `.lic` file on disk
- Unit tests: valid license passes, tampered/wrong-machine license fails
- 5 Compile entries added to main csproj, 1 to tests csproj
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files (existing license logic to extract from)
@Program.cs
@NcProgramManager.csproj
</context>
<acceptance_criteria>
## AC-1: Machine fingerprint readable via interface
```gherkin
Given a Windows machine with WMI available
When MachineFingerprint.GetFingerprint() is called
Then it returns a non-empty string concatenating CPU ProcessorID and Motherboard SerialNumber
```
## AC-2: Valid license passes validation
```gherkin
Given a fingerprint string and a license file containing the RSA-SHA256 signature of that fingerprint
When LicenseValidator.Validate(fingerprint, licenseBase64) is called with the matching public key
Then it returns true
```
## AC-3: Invalid license rejected
```gherkin
Given a fingerprint string and a license signed for a different fingerprint (wrong machine)
When LicenseValidator.Validate(fingerprint, licenseBase64) is called
Then it returns false
```
## AC-4: Corrupted/missing license file handled
```gherkin
Given a path to a non-existent or empty .lic file
When LicenseFile.TryRead(path, out string b64) is called
Then it returns false and b64 is null (no exception thrown)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Create Licensing interfaces and LicenseFile</name>
<files>
Licensing/IMachineFingerprint.cs,
Licensing/ILicenseValidator.cs,
Licensing/LicenseFile.cs
</files>
<action>
Create folder `Licensing/` at project root.
**Licensing/IMachineFingerprint.cs**
```csharp
namespace NcProgramManager.Licensing
{
internal interface IMachineFingerprint
{
string GetFingerprint();
}
}
```
**Licensing/ILicenseValidator.cs**
```csharp
namespace NcProgramManager.Licensing
{
internal interface ILicenseValidator
{
bool Validate(string fingerprint, string licenseBase64);
}
}
```
**Licensing/LicenseFile.cs**
```csharp
using System;
using System.IO;
namespace NcProgramManager.Licensing
{
internal static class LicenseFile
{
internal static bool TryRead(string path, out string base64)
{
base64 = null;
try
{
if (!File.Exists(path))
return false;
string content = File.ReadAllText(path).Trim();
if (string.IsNullOrEmpty(content))
return false;
base64 = content;
return true;
}
catch
{
return false;
}
}
}
}
```
Avoid: throwing exceptions from TryRead — callers expect bool pattern.
</action>
<verify>
```bash
ls Licensing/IMachineFingerprint.cs Licensing/ILicenseValidator.cs Licensing/LicenseFile.cs
grep -n "namespace NcProgramManager.Licensing" Licensing/IMachineFingerprint.cs Licensing/ILicenseValidator.cs Licensing/LicenseFile.cs
```
All three files exist with correct namespace.
</verify>
<done>AC-4 satisfied: LicenseFile.TryRead handles missing/empty file without exception</done>
</task>
<task type="auto">
<name>Task 2: Implement MachineFingerprint and LicenseValidator</name>
<files>
Licensing/MachineFingerprint.cs,
Licensing/LicenseValidator.cs,
NcProgramManager.csproj
</files>
<action>
**Licensing/MachineFingerprint.cs**
Extract the WMI fingerprint logic from `Program.checkLicense`. Do NOT modify Program.cs yet — that is Phase 08.
```csharp
using System.Management;
namespace NcProgramManager.Licensing
{
internal class MachineFingerprint : IMachineFingerprint
{
public string GetFingerprint()
{
string pcInfo = string.Empty;
using (var mc = new ManagementClass("win32_processor"))
using (ManagementObjectCollection moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
pcInfo = mo.Properties["processorID"].Value.ToString();
break;
}
}
using (var searcher = new ManagementObjectSearcher(
"root\\CIMV2", "SELECT * FROM Win32_BaseBoard"))
{
foreach (ManagementObject obj in searcher.Get())
{
pcInfo += obj["SerialNumber"].ToString();
break;
}
}
return pcInfo;
}
}
}
```
**Licensing/LicenseValidator.cs**
RSA-SHA256 verify. Public key is a placeholder XML constant — Phase 09 (generator) produces the real key pair; replace `PublicKeyXml` at that point.
```csharp
using System;
using System.Security.Cryptography;
using System.Text;
namespace NcProgramManager.Licensing
{
internal class LicenseValidator : ILicenseValidator
{
// Placeholder — replace with real vendor public key XML after Phase 09 generates the key pair.
// Generate with: openssl genrsa 2048 | openssl rsa -pubout, then convert PEM → XML,
// or use RSACryptoServiceProvider.ToXmlString(false) from the generator tool.
private const string PublicKeyXml =
"<RSAKeyValue>" +
"<Modulus>PLACEHOLDER</Modulus>" +
"<Exponent>AQAB</Exponent>" +
"</RSAKeyValue>";
private readonly string _publicKeyXml;
// Public constructor: uses embedded vendor public key.
internal LicenseValidator() : this(PublicKeyXml) { }
// Internal constructor: accepts any public key XML — used in unit tests.
internal LicenseValidator(string publicKeyXml)
{
_publicKeyXml = publicKeyXml;
}
public bool Validate(string fingerprint, string licenseBase64)
{
try
{
byte[] signature = Convert.FromBase64String(licenseBase64);
byte[] data = Encoding.UTF8.GetBytes(fingerprint);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(_publicKeyXml);
return rsa.VerifyData(data, new SHA256CryptoServiceProvider(), signature);
}
}
catch
{
return false;
}
}
}
}
```
Note: `RSACryptoServiceProvider.VerifyData(byte[], object, byte[])` — the hash algorithm
argument is `new SHA256CryptoServiceProvider()`, not `HashAlgorithmName` (that overload
requires .NET 4.6+ RSA base class; `RSACryptoServiceProvider` overload uses the older
`object` signature which is available in .NET 4.7.2).
**NcProgramManager.csproj** — add 5 Compile entries after last existing `<Compile>` in the
`Licensing` group (create new ItemGroup if none exists):
```xml
<Compile Include="Licensing\IMachineFingerprint.cs" />
<Compile Include="Licensing\ILicenseValidator.cs" />
<Compile Include="Licensing\LicenseFile.cs" />
<Compile Include="Licensing\MachineFingerprint.cs" />
<Compile Include="Licensing\LicenseValidator.cs" />
```
System.Management reference already present — no new reference needed.
</action>
<verify>
```bash
ls Licensing/MachineFingerprint.cs Licensing/LicenseValidator.cs
grep "LicenseValidator\|MachineFingerprint\|LicenseFile\|IMachineFingerprint\|ILicenseValidator" NcProgramManager.csproj | grep "Compile"
```
Both files exist. Five Compile entries present in csproj.
</verify>
<done>AC-1 satisfied: MachineFingerprint implements IMachineFingerprint with WMI logic. AC-2/AC-3 groundwork: LicenseValidator implements ILicenseValidator with RSA verify.</done>
</task>
<task type="auto">
<name>Task 3: Unit tests for LicenseValidator and LicenseFile</name>
<files>
NcProgramManager.Tests/Unit/LicenseValidatorTests.cs,
NcProgramManager.Tests/NcProgramManager.Tests.csproj
</files>
<action>
Tests use the internal `LicenseValidator(string publicKeyXml)` constructor to inject a
test-generated key pair — same pattern as NcProgramValidatorTests.
```csharp
using System;
using System.Security.Cryptography;
using System.Text;
using NUnit.Framework;
using NcProgramManager.Licensing;
namespace NcProgramManager.Tests.Unit
{
[TestFixture]
public class LicenseValidatorTests
{
private string _publicKeyXml;
private string _privateKeyXml;
private const string TestFingerprint = "BFEBFBFF000906EAM80-80001234";
[SetUp]
public void SetUp()
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
_publicKeyXml = rsa.ToXmlString(false); // public only
_privateKeyXml = rsa.ToXmlString(true); // includes private
}
}
private string Sign(string fingerprint)
{
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(_privateKeyXml);
byte[] sig = rsa.SignData(
Encoding.UTF8.GetBytes(fingerprint),
new SHA256CryptoServiceProvider());
return Convert.ToBase64String(sig);
}
}
[Test]
public void Validate_ValidLicense_ReturnsTrue()
{
string licenseB64 = Sign(TestFingerprint);
var validator = new LicenseValidator(_publicKeyXml);
Assert.IsTrue(validator.Validate(TestFingerprint, licenseB64));
}
[Test]
public void Validate_WrongMachineFingerprint_ReturnsFalse()
{
string licenseB64 = Sign("DIFFERENT_MACHINE_ID");
var validator = new LicenseValidator(_publicKeyXml);
Assert.IsFalse(validator.Validate(TestFingerprint, licenseB64));
}
[Test]
public void Validate_TamperedLicense_ReturnsFalse()
{
string licenseB64 = Sign(TestFingerprint);
// Flip one char
char[] chars = licenseB64.ToCharArray();
chars[10] = chars[10] == 'A' ? 'B' : 'A';
var validator = new LicenseValidator(_publicKeyXml);
Assert.IsFalse(validator.Validate(TestFingerprint, new string(chars)));
}
[Test]
public void Validate_EmptyLicense_ReturnsFalse()
{
var validator = new LicenseValidator(_publicKeyXml);
Assert.IsFalse(validator.Validate(TestFingerprint, ""));
}
[Test]
public void Validate_NotBase64_ReturnsFalse()
{
var validator = new LicenseValidator(_publicKeyXml);
Assert.IsFalse(validator.Validate(TestFingerprint, "not-valid-base64!!!"));
}
[Test]
public void LicenseFile_TryRead_NonExistentPath_ReturnsFalse()
{
bool result = LicenseFile.TryRead(@"C:\does\not\exist.lic", out string b64);
Assert.IsFalse(result);
Assert.IsNull(b64);
}
}
}
```
**NcProgramManager.Tests/NcProgramManager.Tests.csproj** — add 1 Compile entry:
```xml
<Compile Include="Unit\LicenseValidatorTests.cs" />
```
Avoid: creating real .lic files on disk in tests — use in-memory signing only.
Note: `InternalsVisibleTo("NcProgramManager.Tests")` already present in AssemblyInfo.cs
from Phase 5 — no change needed.
</action>
<verify>
```bash
ls NcProgramManager.Tests/Unit/LicenseValidatorTests.cs
grep "LicenseValidatorTests" NcProgramManager.Tests/NcProgramManager.Tests.csproj
grep "LicenseValidatorTests\|LicenseFile" NcProgramManager.Tests/Unit/LicenseValidatorTests.cs | head -5
```
File exists. Compile entry present. Class contains both LicenseValidator and LicenseFile tests.
</verify>
<done>AC-2 satisfied: valid license returns true. AC-3 satisfied: wrong machine returns false. AC-4 satisfied: missing file returns false.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `Program.cs` — checkLicense stays untouched until Phase 08
- `InputArgs.cs``-chiave=` field stays until Phase 08
- `AesCrypt.cs` — still used by program file encryption, not licensing
- All existing machine implementations (`Cnc/` folder)
- Existing test files
## SCOPE LIMITS
- No wiring into Program.cs (Phase 08)
- No license generator tool (Phase 09)
- No CLI changes (`-licfile=` arg is Phase 08)
- Public key stays as placeholder — real key generated in Phase 09
- `MachineFingerprint` is new class; do NOT delete WMI code from Program.cs yet
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `ls Licensing/*.cs` shows 5 files
- [ ] `grep "Compile" NcProgramManager.csproj | grep Licensing` shows 5 entries
- [ ] `ls NcProgramManager.Tests/Unit/LicenseValidatorTests.cs` exists
- [ ] `grep "LicenseValidatorTests" NcProgramManager.Tests/NcProgramManager.Tests.csproj` finds entry
- [ ] `grep "NcProgramManager.Licensing" Licensing/*.cs` — all files correct namespace
- [ ] All acceptance criteria met
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No changes to Program.cs, InputArgs.cs, or any existing .cs file outside Licensing/
- 6 new files created, 2 csproj files updated
</success_criteria>
<output>
After completion, create `.paul/phases/07-license-core/07-01-SUMMARY.md`
</output>

View file

@ -0,0 +1,148 @@
---
phase: 07-license-core
plan: 01
subsystem: licensing
tags: [rsa, cryptography, wmi, fingerprint, unit-tests, net472]
requires:
- phase: 06-rename-cleanup
provides: NcProgramManager namespace + renamed csproj files
provides:
- IMachineFingerprint interface + MachineFingerprint WMI implementation
- ILicenseValidator interface + LicenseValidator RSA-SHA256 verifier
- LicenseFile.TryRead helper
- 6 unit tests covering valid/invalid/tampered/missing license scenarios
affects:
- 08-license-integration (wires these into Program.cs)
- 09-license-generator (produces real key pair; PublicKeyXml PLACEHOLDER replaced here)
tech-stack:
added: []
patterns:
- Constructor injection for testability (internal ctor accepts public key XML)
- RSACryptoServiceProvider + SHA256CryptoServiceProvider for .NET 4.7.2 compat
key-files:
created:
- Licensing/IMachineFingerprint.cs
- Licensing/ILicenseValidator.cs
- Licensing/LicenseFile.cs
- Licensing/MachineFingerprint.cs
- Licensing/LicenseValidator.cs
- NcProgramManager.Tests/Unit/LicenseValidatorTests.cs
modified:
- NcProgramManager.csproj
- NcProgramManager.Tests/NcProgramManager.Tests.csproj
key-decisions:
- "RSA asymmetric: private key vendor-only, public key embedded in binary"
- "PublicKeyXml is PLACEHOLDER until Phase 09 generates real key pair"
- "Constructor injection: internal LicenseValidator(string publicKeyXml) for test isolation"
- "SHA256CryptoServiceProvider (not HashAlgorithmName) — .NET 4.7.2 RSACryptoServiceProvider overload"
patterns-established:
- "LicenseValidator(string publicKeyXml) internal ctor mirrors NcProgramValidator injection pattern"
- "TryRead out-param bool pattern: no exceptions escape from file I/O"
duration: ~2h (split across sessions)
started: 2026-05-17T00:00:00Z
completed: 2026-05-17T21:00:00Z
---
# Phase 07 Plan 01: license-core Summary
**RSA offline license core shipped: IMachineFingerprint + ILicenseValidator interfaces, WMI fingerprint reader, RSA-SHA256 verifier with injectable key, LicenseFile TryRead helper — 118 tests pass (116 run, 2 Fanuc FOCAS ignored).**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~2h |
| Started | 2026-05-17 |
| Completed | 2026-05-17 |
| Tasks | 3 completed |
| Files modified | 8 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Machine fingerprint readable via interface | Pass | MachineFingerprint : IMachineFingerprint — WMI logic extracted (not testable on Linux; verified via code review) |
| AC-2: Valid license passes validation | Pass | Validate_ValidLicense_ReturnsTrue — in-memory RSA sign+verify |
| AC-3: Invalid license rejected | Pass | Validate_WrongMachineFingerprint_ReturnsFalse, Validate_TamperedLicense_ReturnsFalse |
| AC-4: Corrupted/missing license file handled | Pass | LicenseFile_TryRead_NonExistentPath_ReturnsFalse |
## Accomplishments
- 5 new files in `Licensing/` — interfaces, implementations, file helper — clean DI-ready layer
- 6 LicenseValidator tests all pass in Docker (Mono 6.12 / .NET 4.7.2)
- PublicKeyXml PLACEHOLDER documented — Phase 09 replaces with real key pair
- Full test suite: 118 total, 116 pass, 2 ignored (Fanuc FOCAS, Windows-only DLL, expected)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `Licensing/IMachineFingerprint.cs` | Created | Interface for fingerprint provider |
| `Licensing/ILicenseValidator.cs` | Created | Interface for RSA validator |
| `Licensing/LicenseFile.cs` | Created | TryRead: reads .lic base64, returns false on missing/empty |
| `Licensing/MachineFingerprint.cs` | Created | WMI: CPU ProcessorID + BaseBoard SerialNumber |
| `Licensing/LicenseValidator.cs` | Created | RSACryptoServiceProvider VerifyData with SHA256 |
| `NcProgramManager.Tests/Unit/LicenseValidatorTests.cs` | Created | 6 NUnit tests; generates ephemeral key pair in SetUp |
| `NcProgramManager.csproj` | Modified | +5 Compile entries for Licensing/*.cs |
| `NcProgramManager.Tests/NcProgramManager.Tests.csproj` | Modified | +1 Compile entry for LicenseValidatorTests.cs |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| RSA asymmetric (not AES) | Private key vendor-only; embedded public key safe to decompile | Phase 09 must generate real 2048-bit key pair |
| `RSACryptoServiceProvider` + `SHA256CryptoServiceProvider` | .NET 4.7.2: `VerifyData(byte[], object, byte[])` overload — `HashAlgorithmName` variant requires RSA base class | Locked to this API; no upgrade path issue |
| PublicKeyXml = PLACEHOLDER | Real key not yet generated; prevents Phase 07 from blocking on Phase 09 | Phase 09 outputs real XML; copy-paste into LicenseValidator.cs |
| Internal constructor injection | Testable without Windows WMI DLL | Same pattern as NcProgramValidator — consistent across codebase |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 1 | Pre-existing bug, zero scope creep |
| Scope additions | 0 | — |
| Deferred | 0 | — |
### Auto-fixed Issues
**1. Build — Missing `using System;` in CncMachineFactoryTests.cs**
- **Found during:** Docker build (msbuild step)
- **Issue:** `InvalidOperationException` unresolved — `using System;` absent from `CncMachineFactoryTests.cs`
- **Fix:** Added `using System;` at top of file
- **Files:** `NcProgramManager.Tests/Unit/CncMachineFactoryTests.cs`
- **Verification:** Docker build passed; all 118 tests ran
**2. Dockerfile.test — Stale FanucProgramManager paths**
- **Found during:** First Docker build attempt
- **Issue:** Dockerfile.test still referenced `FanucProgramManager.*` paths from before Phase 06 rename
- **Fix:** Updated all paths to `NcProgramManager.*`
- **Files:** `Dockerfile.test`
- **Verification:** Docker build and test run succeeded
## Next Phase Readiness
**Ready:**
- `IMachineFingerprint` / `ILicenseValidator` injectable into any caller
- `LicenseValidator` verified correct with real RSA operations
- `LicenseFile.TryRead` ready for `-licfile=` arg wiring
**Concerns:**
- PublicKeyXml is PLACEHOLDER — production build will silently reject all licenses until Phase 09 provides real key and it's copied in
- `MachineFingerprint` uses WMI — only tested via code review on Linux; validate on Windows hardware
**Blockers:**
- None — Phase 08 can proceed
---
*Phase: 07-license-core, Plan: 01*
*Completed: 2026-05-17*

View file

@ -0,0 +1,199 @@
---
phase: 08-license-integration
plan: 01
type: execute
wave: 1
depends_on: ["07-01"]
files_modified:
- Program.cs
autonomous: true
---
<objective>
## Goal
Wire the Phase 07 RSA license core into Program.cs: replace the hardcoded AES `checkLicense` call with `MachineFingerprint` + `LicenseFile` + `LicenseValidator`. Keep `-chiave=` as the CLI arg — it now carries a path to a `.lic` file instead of the AES key string.
## Purpose
The current `checkLicense` embeds a hardcoded AES key/IV in source — anyone with the source or a decompiler can generate licenses for any machine. The Phase 07 RSA core is built but not yet wired in. This phase closes the gap: the app will reject any run where the `.lic` file (passed via `-chiave=`) doesn't contain a valid RSA-SHA256 signature matching the current machine's fingerprint.
## Output
- `Program.cs` only — `checkLicense` method deleted; license check replaced with `MachineFingerprint` + `LicenseFile.TryRead` + `LicenseValidator.Validate`; `inputArgs.chiave` now treated as `.lic` file path; help text for `-chiave=` updated to describe the file path; dead `using System.Management` and `using System.Security.Cryptography` removed; `using NcProgramManager.Licensing` added
- `InputArgs.cs` — NO CHANGE (`chiave` field stays as-is)
- Docker build + all 118 tests still pass
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Phase 07 Output (direct inputs)
@Licensing/IMachineFingerprint.cs
@Licensing/ILicenseValidator.cs
@Licensing/LicenseFile.cs
@Licensing/MachineFingerprint.cs
@Licensing/LicenseValidator.cs
## File Being Modified
@Program.cs
</context>
<acceptance_criteria>
## AC-1: -chiave= arg still parsed, value treated as .lic file path
```gherkin
Given Program.cs parseArgomenti is called with args containing "-chiave=C:\license.lic"
When the arg is parsed
Then inputArgs.chiave equals "C:\license.lic"
And the parsing code is unchanged from the current implementation
```
## AC-2: RSA license check used at startup
```gherkin
Given Program.cs Main is called
When the license check runs
Then it calls MachineFingerprint().GetFingerprint(), LicenseFile.TryRead(inputArgs.chiave), and LicenseValidator.Validate(fingerprint, base64)
And the old checkLicense method no longer exists
```
## AC-3: License failure exits with -100
```gherkin
Given the .lic file is missing OR the signature does not match the machine fingerprint
When Main runs
Then it prints "Licenza ERRATA", calls vediErrore(-100), and returns -1
```
## AC-4: Help text updated for -chiave=
```gherkin
Given MostraAiuto is called
When its output is inspected
Then -chiave= is documented as the path to the .lic license file
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Wire RSA license check into Program.cs</name>
<files>Program.cs</files>
<action>
**1. Replace usings at top of file:**
Remove these two lines:
```csharp
using System.Management;
using System.Security.Cryptography;
```
Add:
```csharp
using NcProgramManager.Licensing;
```
Keep all other existing usings unchanged.
**2. Replace the license check call in Main:**
Remove:
```csharp
if (!checkLicense(inputArgs.chiave))
{
Console.WriteLine("Licenza ERRATA");
vediErrore(-100);
return -1;
}
```
Replace with:
```csharp
string _licFingerprint = new MachineFingerprint().GetFingerprint();
string _licBase64;
if (!LicenseFile.TryRead(inputArgs.chiave, out _licBase64)
|| !new LicenseValidator().Validate(_licFingerprint, _licBase64))
{
Console.WriteLine("Licenza ERRATA");
vediErrore(-100);
return -1;
}
```
`inputArgs.chiave` carries the `.lic` file path — parsing in `parseArgomenti` is unchanged.
**3. Delete the entire `checkLicense` static method:**
```csharp
static Boolean checkLicense(string chiave)
{
...
}
```
Remove from first line to closing brace entirely.
**4. Update MostraAiuto — update the description for `-chiave=`:**
Remove:
```csharp
Console.WriteLine("-chiave=");
Console.WriteLine(" la stringa chiave per eseguire il software.");
```
Replace with:
```csharp
Console.WriteLine("-chiave=");
Console.WriteLine(" Path del file di licenza (.lic) per autenticare il software.");
```
Avoid: touching `parseArgomenti` chiave parsing, `Scarica`, `Invia`, `vediErrore`, or any other part of the file.
</action>
<verify>
```bash
grep -n "checkLicense\|System\.Management\|System\.Security\.Cryptography" Program.cs
grep -n "MachineFingerprint\|LicenseFile\|LicenseValidator\|NcProgramManager\.Licensing" Program.cs
grep -n "chiave" Program.cs InputArgs.cs
```
First grep: zero hits.
Second grep: 4+ hits.
Third grep: `chiave` present in both files (parsing in Program.cs unchanged, field in InputArgs.cs unchanged).
</verify>
<done>AC-2 satisfied: RSA check wired. AC-3 satisfied: failure path unchanged (-1 return). AC-4 satisfied: help text updated. AC-1 satisfied: -chiave= parsing untouched.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `InputArgs.cs``chiave` field stays as-is; no rename
- `-chiave=` parsing in `parseArgomenti` — keep exactly as-is
- `AesCrypt.cs` — now dead code but removal is out of scope; leave for a cleanup phase
- `Licensing/*.cs` — Phase 07 output, must not be modified
- All `Cnc/` files
- All test files
## SCOPE LIMITS
- No changes to `Scarica`, `Invia`, `vediErrore` logic
- No changes to any other arg parsing in `parseArgomenti`
- No new test files (license core already tested in Phase 07)
- `AesCrypt.cs` stays in csproj and on disk
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `grep "checkLicense" Program.cs` — zero hits
- [ ] `grep "System.Management\|System.Security.Cryptography" Program.cs` — zero hits
- [ ] `grep "MachineFingerprint\|LicenseFile\|LicenseValidator" Program.cs` — 3 hits
- [ ] `grep "NcProgramManager.Licensing" Program.cs` — 1 hit
- [ ] `grep "chiave" InputArgs.cs` — still present (field unchanged)
- [ ] `grep "\-chiave=" Program.cs` — still present in parseArgomenti AND in MostraAiuto
- [ ] Docker build passes, all 118 tests pass
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- `checkLicense` method gone
- `-chiave=` CLI arg and `inputArgs.chiave` field unchanged
- RSA license check wired at startup using `inputArgs.chiave` as .lic path
- Help text describes `-chiave=` as the .lic file path
- No test regressions
</success_criteria>
<output>
After completion, create `.paul/phases/08-license-integration/08-01-SUMMARY.md`
</output>

View file

@ -0,0 +1,63 @@
---
phase: 08-license-integration
plan: 01
type: summary
status: complete
completed: 2026-05-17
---
# Phase 08 Summary: license-integration
## What Was Built
RSA license check wired into `Program.cs`. Old AES-based `checkLicense` deleted. License now validated at startup against `license.lic` in the EXE directory.
**Files modified:**
- `Program.cs` — license check replaced, `checkLicense` deleted, `-chiave=` parsing removed, `MostraAiuto` updated
- `InputArgs.cs``chiave` field removed
## Acceptance Criteria Results
| AC | Result | Notes |
|----|--------|-------|
| AC-1: -chiave= arg still parsed | **SUPERSEDED** | User directed removal mid-APPLY (see Deviations) |
| AC-2: RSA license check at startup | PASS | `MachineFingerprint` + `LicenseFile.TryRead` + `LicenseValidator.Validate` wired |
| AC-3: License failure exits -100 | PASS | `Console.WriteLine("Licenza ERRATA")` + `vediErrore(-100)` + `return -1` |
| AC-4: Help text updated | PASS | `-chiave=` block replaced with `license.lic` location note |
## Deviations
**Plan AC-1 superseded by user decision.**
Plan stated: keep `-chiave=` as the CLI arg carrying the `.lic` file path.
User directed mid-APPLY: remove `-chiave=` entirely; hardcode `license.lic` next to the EXE.
Actual license path:
```csharp
string _licPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic");
```
`inputArgs.chiave` field removed from `InputArgs.cs`. All `-chiave=` parsing removed from `parseArgomenti`.
## Verification (post-apply grep)
```
grep "checkLicense|chiave|System.Management|System.Security.Cryptography" Program.cs → 0 hits
grep "MachineFingerprint|LicenseFile|LicenseValidator|NcProgramManager.Licensing" Program.cs → 5 hits
grep "chiave" InputArgs.cs → 0 hits
```
## Decisions Made
- License file always at `AppDomain.CurrentDomain.BaseDirectory + "license.lic"` — no CLI override
- `InputArgs.chiave` field deleted (not just unused) — cleaner than leaving dead field
- `AesCrypt.cs` left on disk (still in csproj, no cleanup in scope)
## Deferred Issues
- Real RSA key pair — Phase 09 generates; copy `PublicKeyXml` into `LicenseValidator.cs`
- Phase 09: separate `LicenseGenerator` console app — reads WMI fingerprint, signs, writes `.lic`
- `AesCrypt.cs` dead code — remove in future cleanup phase
## Tests
No new tests (license core tested in Phase 07). Docker build + 116 tests pass (2 Fanuc FOCAS ignored, Windows-only DLL).

View file

@ -2,7 +2,7 @@
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace FanucProgramManager namespace NcProgramManager
{ {
class AesCrypt class AesCrypt
{ {

View file

@ -1,11 +1,11 @@
using System; using System;
using FanucProgramManager.Cnc.Fanuc; using NcProgramManager.Cnc.Fanuc;
using FanucProgramManager.Cnc.Heidenhain; using NcProgramManager.Cnc.Heidenhain;
using FanucProgramManager.Cnc.Mitsubishi; using NcProgramManager.Cnc.Mitsubishi;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Siemens; using NcProgramManager.Cnc.Siemens;
namespace FanucProgramManager.Cnc namespace NcProgramManager.Cnc
{ {
internal static class CncMachineFactory internal static class CncMachineFactory
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
public sealed class FanucCapabilities public sealed class FanucCapabilities
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
public enum FanucTransport public enum FanucTransport
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
public enum FanucDialect public enum FanucDialect
{ {

View file

@ -1,6 +1,6 @@
using FanucProgramManager; using NcProgramManager;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
internal static class FanucDialectDetector internal static class FanucDialectDetector
{ {

View file

@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using FanucProgramManager; using NcProgramManager;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
internal static class FanucErrorTranslator internal static class FanucErrorTranslator
{ {

View file

@ -5,11 +5,11 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FanucProgramManager; using NcProgramManager;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
public sealed class FanucMachine : IFanucMachine public sealed class FanucMachine : IFanucMachine
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
internal static class FocasDialectFactory internal static class FocasDialectFactory
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
internal interface IFocasDialect internal interface IFocasDialect
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
public static class IsoProgramFormatter public static class IsoProgramFormatter
{ {

View file

@ -1,6 +1,6 @@
using FanucProgramManager; using NcProgramManager;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
internal sealed class LegacyFocasDialect : IFocasDialect internal sealed class LegacyFocasDialect : IFocasDialect
{ {

View file

@ -1,7 +1,7 @@
using System.Text; using System.Text;
using FanucProgramManager; using NcProgramManager;
namespace FanucProgramManager.Cnc.Fanuc namespace NcProgramManager.Cnc.Fanuc
{ {
internal sealed class ModernFocasDialect : IFocasDialect internal sealed class ModernFocasDialect : IFocasDialect
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace FanucProgramManager.Cnc.Heidenhain namespace NcProgramManager.Cnc.Heidenhain
{ {
public sealed class HeidenhainConnectionConfig public sealed class HeidenhainConnectionConfig
{ {

View file

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc.Heidenhain namespace NcProgramManager.Cnc.Heidenhain
{ {
public sealed class HeidenhainMachine : ICncMachine public sealed class HeidenhainMachine : ICncMachine
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Heidenhain namespace NcProgramManager.Cnc.Heidenhain
{ {
internal interface ILsv2Client internal interface ILsv2Client
{ {

View file

@ -2,7 +2,7 @@ using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
namespace FanucProgramManager.Cnc.Heidenhain namespace NcProgramManager.Cnc.Heidenhain
{ {
/// <summary>LSV2 protocol client for Heidenhain TNC controls.</summary> /// <summary>LSV2 protocol client for Heidenhain TNC controls.</summary>
internal sealed class Lsv2Client : ILsv2Client, IDisposable internal sealed class Lsv2Client : ILsv2Client, IDisposable

View file

@ -1,9 +1,9 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc namespace NcProgramManager.Cnc
{ {
public interface ICncMachine : IDisposable public interface ICncMachine : IDisposable
{ {

View file

@ -1,8 +1,8 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc namespace NcProgramManager.Cnc
{ {
/// <summary>Fanuc-specific extensions: tool offset and work zero offset transfer.</summary> /// <summary>Fanuc-specific extensions: tool offset and work zero offset transfer.</summary>
public interface IFanucMachine : ICncMachine public interface IFanucMachine : ICncMachine

View file

@ -1,6 +1,6 @@
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc namespace NcProgramManager.Cnc
{ {
/// <summary>Validates NC program content before upload to a CNC controller.</summary> /// <summary>Validates NC program content before upload to a CNC controller.</summary>
public interface INcProgramValidator public interface INcProgramValidator

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Mitsubishi namespace NcProgramManager.Cnc.Mitsubishi
{ {
internal interface IMitsubishiFtpClient internal interface IMitsubishiFtpClient
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace FanucProgramManager.Cnc.Mitsubishi namespace NcProgramManager.Cnc.Mitsubishi
{ {
public sealed class MitsubishiConnectionConfig public sealed class MitsubishiConnectionConfig
{ {

View file

@ -3,7 +3,7 @@ using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
namespace FanucProgramManager.Cnc.Mitsubishi namespace NcProgramManager.Cnc.Mitsubishi
{ {
/// <summary>FTP client for Mitsubishi M-series CNC program transfer.</summary> /// <summary>FTP client for Mitsubishi M-series CNC program transfer.</summary>
internal sealed class MitsubishiFtpClient : IMitsubishiFtpClient internal sealed class MitsubishiFtpClient : IMitsubishiFtpClient

View file

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc.Mitsubishi namespace NcProgramManager.Cnc.Mitsubishi
{ {
public sealed class MitsubishiMachine : ICncMachine public sealed class MitsubishiMachine : ICncMachine
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public sealed class CncError public sealed class CncError
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public enum CncManufacturer { Fanuc, Heidenhain, Siemens, Mitsubishi } public enum CncManufacturer { Fanuc, Heidenhain, Siemens, Mitsubishi }
} }

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public sealed class CncProgram public sealed class CncProgram
{ {

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
/// <summary>Result wrapper for all CNC operations.</summary> /// <summary>Result wrapper for all CNC operations.</summary>
public sealed class CncResult<T> public sealed class CncResult<T>

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public enum ConnectionState public enum ConnectionState
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public enum MachineMode public enum MachineMode
{ {

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public sealed class MachineSnapshot public sealed class MachineSnapshot
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public enum MotionState public enum MotionState
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public enum RunState public enum RunState
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public sealed class ValidationError public sealed class ValidationError
{ {

View file

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public sealed class ValidationResult public sealed class ValidationResult
{ {

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Models namespace NcProgramManager.Cnc.Models
{ {
public enum ValidationSeverity { Error, Warning } public enum ValidationSeverity { Error, Warning }
} }

View file

@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc namespace NcProgramManager.Cnc
{ {
/// <summary>Validates NC program content per-manufacturer before upload.</summary> /// <summary>Validates NC program content per-manufacturer before upload.</summary>
public sealed class NcProgramValidator : INcProgramValidator public sealed class NcProgramValidator : INcProgramValidator

View file

@ -1,4 +1,4 @@
namespace FanucProgramManager.Cnc.Siemens namespace NcProgramManager.Cnc.Siemens
{ {
internal interface ISiemensFtpClient internal interface ISiemensFtpClient
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace FanucProgramManager.Cnc.Siemens namespace NcProgramManager.Cnc.Siemens
{ {
public sealed class SiemensConnectionConfig public sealed class SiemensConnectionConfig
{ {

View file

@ -3,7 +3,7 @@ using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
namespace FanucProgramManager.Cnc.Siemens namespace NcProgramManager.Cnc.Siemens
{ {
/// <summary>FTP client for Siemens Sinumerik CNC program transfer.</summary> /// <summary>FTP client for Siemens Sinumerik CNC program transfer.</summary>
internal sealed class SiemensFtpClient : ISiemensFtpClient internal sealed class SiemensFtpClient : ISiemensFtpClient

View file

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Cnc.Siemens namespace NcProgramManager.Cnc.Siemens
{ {
public sealed class SiemensMachine : ICncMachine public sealed class SiemensMachine : ICncMachine
{ {

View file

@ -6,16 +6,16 @@ WORKDIR /build
RUN cert-sync /etc/ssl/certs/ca-certificates.crt RUN cert-sync /etc/ssl/certs/ca-certificates.crt
# Copy solution + projects first for layer caching # Copy solution + projects first for layer caching
COPY FanucProgramManager.csproj ./ COPY NcProgramManager.csproj ./
COPY packages.config ./ COPY packages.config ./
COPY FanucProgramManager.Tests/FanucProgramManager.Tests.csproj ./FanucProgramManager.Tests/ COPY NcProgramManager.Tests/NcProgramManager.Tests.csproj ./NcProgramManager.Tests/
COPY FanucProgramManager.Tests/packages.config ./FanucProgramManager.Tests/ COPY NcProgramManager.Tests/packages.config ./NcProgramManager.Tests/
COPY FanucProgramManager.sln ./ COPY NcProgramManager.sln ./
# Restore all packages (sln now includes test project) # Restore all packages (sln now includes test project)
RUN nuget restore FanucProgramManager.sln -NonInteractive RUN nuget restore NcProgramManager.sln -NonInteractive
# Explicit fallback: restore test packages.config directly to same packages dir # Explicit fallback: restore test packages.config directly to same packages dir
RUN nuget restore FanucProgramManager.Tests/packages.config \ RUN nuget restore NcProgramManager.Tests/packages.config \
-OutputDirectory packages -NonInteractive -OutputDirectory packages -NonInteractive
# Grab NUnit console runner # Grab NUnit console runner
RUN nuget install NUnit.ConsoleRunner -Version 3.16.3 -OutputDirectory packages -NonInteractive RUN nuget install NUnit.ConsoleRunner -Version 3.16.3 -OutputDirectory packages -NonInteractive
@ -27,11 +27,11 @@ COPY . .
RUN ls Cnc/Fanuc/libs/ || true RUN ls Cnc/Fanuc/libs/ || true
# Build test project (Debug) # Build test project (Debug)
RUN msbuild FanucProgramManager.Tests/FanucProgramManager.Tests.csproj \ RUN msbuild NcProgramManager.Tests/NcProgramManager.Tests.csproj \
/p:Configuration=Debug /p:Platform=AnyCPU \ /p:Configuration=Debug /p:Platform=AnyCPU \
/v:minimal /nologo /v:minimal /nologo
# Run tests — skip Fanuc FOCAS tests (marked [Ignore] — need fwlib32.dll) # Run tests — skip Fanuc FOCAS tests (marked [Ignore] — need fwlib32.dll)
CMD mono packages/NUnit.ConsoleRunner.3.16.3/tools/nunit3-console.exe \ CMD mono packages/NUnit.ConsoleRunner.3.16.3/tools/nunit3-console.exe \
FanucProgramManager.Tests/bin/Debug/FanucProgramManager.Tests.dll \ NcProgramManager.Tests/bin/Debug/NcProgramManager.Tests.dll \
--labels=All --labels=All

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace FanucProgramManager namespace NcProgramManager
{ {
internal class FanucProgram internal class FanucProgram
{ {

View file

@ -1,6 +1,6 @@
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager namespace NcProgramManager
{ {
internal class InputArgs internal class InputArgs
{ {
@ -11,7 +11,6 @@ namespace FanucProgramManager
public string porta = ""; public string porta = "";
public short pathCNC = 1; public short pathCNC = 1;
public int nodoHssb = -1; public int nodoHssb = -1;
public string chiave = "";
public string username = ""; public string username = "";
public string password = ""; public string password = "";
public string commentoProgramma = ""; public string commentoProgramma = "";

View file

@ -0,0 +1,7 @@
namespace NcProgramManager.Licensing
{
internal interface ILicenseValidator
{
bool Validate(string fingerprint, string licenseBase64);
}
}

View file

@ -0,0 +1,7 @@
namespace NcProgramManager.Licensing
{
internal interface IMachineFingerprint
{
string GetFingerprint();
}
}

26
Licensing/LicenseFile.cs Normal file
View file

@ -0,0 +1,26 @@
using System.IO;
namespace NcProgramManager.Licensing
{
internal static class LicenseFile
{
internal static bool TryRead(string path, out string base64)
{
base64 = null;
try
{
if (!File.Exists(path))
return false;
string content = File.ReadAllText(path).Trim();
if (string.IsNullOrEmpty(content))
return false;
base64 = content;
return true;
}
catch
{
return false;
}
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace NcProgramManager.Licensing
{
internal class LicenseValidator : ILicenseValidator
{
// Placeholder — replace with real vendor public key XML after Phase 09 generates the key pair.
// Generate key pair with the LicenseGenerator tool, then copy the public XML here.
private const string PublicKeyXml =
"<RSAKeyValue>" +
"<Modulus>PLACEHOLDER</Modulus>" +
"<Exponent>AQAB</Exponent>" +
"</RSAKeyValue>";
private readonly string _publicKeyXml;
// Uses embedded vendor public key.
internal LicenseValidator() : this(PublicKeyXml) { }
// Accepts any public key XML — used in unit tests.
internal LicenseValidator(string publicKeyXml)
{
_publicKeyXml = publicKeyXml;
}
public bool Validate(string fingerprint, string licenseBase64)
{
try
{
byte[] signature = Convert.FromBase64String(licenseBase64);
byte[] data = Encoding.UTF8.GetBytes(fingerprint);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(_publicKeyXml);
return rsa.VerifyData(data, new SHA256CryptoServiceProvider(), signature);
}
}
catch
{
return false;
}
}
}
}

View file

@ -0,0 +1,34 @@
using System.Management;
namespace NcProgramManager.Licensing
{
internal class MachineFingerprint : IMachineFingerprint
{
public string GetFingerprint()
{
string pcInfo = string.Empty;
using (var mc = new ManagementClass("win32_processor"))
using (ManagementObjectCollection moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
pcInfo = mo.Properties["processorID"].Value.ToString();
break;
}
}
using (var searcher = new ManagementObjectSearcher(
"root\\CIMV2", "SELECT * FROM Win32_BaseBoard"))
{
foreach (ManagementObject obj in searcher.Get())
{
pcInfo += obj["SerialNumber"].ToString();
break;
}
}
return pcInfo;
}
}
}

View file

@ -1,10 +1,10 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Heidenhain; using NcProgramManager.Cnc.Heidenhain;
using FanucProgramManager.Tests.Integration.Stubs; using NcProgramManager.Tests.Integration.Stubs;
namespace FanucProgramManager.Tests.Integration namespace NcProgramManager.Tests.Integration
{ {
[TestFixture] [TestFixture]
[Category("Integration")] [Category("Integration")]

View file

@ -1,10 +1,10 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Mitsubishi; using NcProgramManager.Cnc.Mitsubishi;
using FanucProgramManager.Tests.Integration.Stubs; using NcProgramManager.Tests.Integration.Stubs;
namespace FanucProgramManager.Tests.Integration namespace NcProgramManager.Tests.Integration
{ {
[TestFixture] [TestFixture]
[Category("Integration")] [Category("Integration")]

View file

@ -1,10 +1,10 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Siemens; using NcProgramManager.Cnc.Siemens;
using FanucProgramManager.Tests.Integration.Stubs; using NcProgramManager.Tests.Integration.Stubs;
namespace FanucProgramManager.Tests.Integration namespace NcProgramManager.Tests.Integration
{ {
[TestFixture] [TestFixture]
[Category("Integration")] [Category("Integration")]

View file

@ -6,7 +6,7 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
namespace FanucProgramManager.Tests.Integration.Stubs namespace NcProgramManager.Tests.Integration.Stubs
{ {
/// <summary> /// <summary>
/// Minimal in-process FTP server using passive mode. /// Minimal in-process FTP server using passive mode.

View file

@ -6,7 +6,7 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
namespace FanucProgramManager.Tests.Integration.Stubs namespace NcProgramManager.Tests.Integration.Stubs
{ {
/// <summary> /// <summary>
/// Minimal in-process LSV2 server. /// Minimal in-process LSV2 server.

View file

@ -7,8 +7,8 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid> <ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>FanucProgramManager.Tests</RootNamespace> <RootNamespace>NcProgramManager.Tests</RootNamespace>
<AssemblyName>FanucProgramManager.Tests</AssemblyName> <AssemblyName>NcProgramManager.Tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
@ -46,12 +46,13 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FanucProgramManager.csproj"> <ProjectReference Include="..\NcProgramManager.csproj">
<Project>{1032188B-3321-42B2-87ED-343BF718CAF5}</Project> <Project>{1032188B-3321-42B2-87ED-343BF718CAF5}</Project>
<Name>FanucProgramManager</Name> <Name>NcProgramManager</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Unit\LicenseValidatorTests.cs" />
<Compile Include="Unit\NcProgramValidatorTests.cs" /> <Compile Include="Unit\NcProgramValidatorTests.cs" />
<Compile Include="Unit\FanucProgramParsingTests.cs" /> <Compile Include="Unit\FanucProgramParsingTests.cs" />
<Compile Include="Unit\IsoProgramFormatterTests.cs" /> <Compile Include="Unit\IsoProgramFormatterTests.cs" />

View file

@ -1,12 +1,13 @@
using System;
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Fanuc; using NcProgramManager.Cnc.Fanuc;
using FanucProgramManager.Cnc.Heidenhain; using NcProgramManager.Cnc.Heidenhain;
using FanucProgramManager.Cnc.Mitsubishi; using NcProgramManager.Cnc.Mitsubishi;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Siemens; using NcProgramManager.Cnc.Siemens;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class CncMachineFactoryTests public class CncMachineFactoryTests

View file

@ -1,10 +1,10 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using Moq; using Moq;
using FanucProgramManager.Cnc.Fanuc; using NcProgramManager.Cnc.Fanuc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
/// <summary> /// <summary>
/// FanucMachine calls real FOCAS DLL in ConnectCore and most methods guard /// FanucMachine calls real FOCAS DLL in ConnectCore and most methods guard

View file

@ -1,7 +1,7 @@
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager; using NcProgramManager;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class FanucProgramParsingTests public class FanucProgramParsingTests

View file

@ -1,11 +1,11 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using Moq; using Moq;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Heidenhain; using NcProgramManager.Cnc.Heidenhain;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class HeidenhainMachineTests public class HeidenhainMachineTests

View file

@ -1,7 +1,7 @@
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager.Cnc.Fanuc; using NcProgramManager.Cnc.Fanuc;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class IsoProgramFormatterTests public class IsoProgramFormatterTests

View file

@ -0,0 +1,80 @@
using System;
using System.Security.Cryptography;
using System.Text;
using NUnit.Framework;
using NcProgramManager.Licensing;
namespace NcProgramManager.Tests.Unit
{
[TestFixture]
internal class LicenseValidatorTests
{
private RSACryptoServiceProvider _rsa;
private string _publicKeyXml;
private LicenseValidator _validator;
[SetUp]
public void SetUp()
{
_rsa = new RSACryptoServiceProvider(2048);
_publicKeyXml = _rsa.ToXmlString(false);
_validator = new LicenseValidator(_publicKeyXml);
}
[TearDown]
public void TearDown()
{
_rsa.Dispose();
}
private string Sign(string fingerprint)
{
byte[] data = Encoding.UTF8.GetBytes(fingerprint);
byte[] sig = _rsa.SignData(data, new SHA256CryptoServiceProvider());
return Convert.ToBase64String(sig);
}
[Test]
public void Validate_ValidLicense_ReturnsTrue()
{
string fingerprint = "CPU123BOARD456";
string license = Sign(fingerprint);
Assert.IsTrue(_validator.Validate(fingerprint, license));
}
[Test]
public void Validate_WrongMachineFingerprint_ReturnsFalse()
{
string license = Sign("CPU123BOARD456");
Assert.IsFalse(_validator.Validate("DIFFERENT_MACHINE", license));
}
[Test]
public void Validate_TamperedLicense_ReturnsFalse()
{
string license = Sign("CPU123BOARD456");
string tampered = license.Substring(0, license.Length - 4) + "AAAA";
Assert.IsFalse(_validator.Validate("CPU123BOARD456", tampered));
}
[Test]
public void Validate_EmptyLicense_ReturnsFalse()
{
Assert.IsFalse(_validator.Validate("CPU123BOARD456", string.Empty));
}
[Test]
public void Validate_NotBase64_ReturnsFalse()
{
Assert.IsFalse(_validator.Validate("CPU123BOARD456", "not-base64!!!"));
}
[Test]
public void LicenseFile_TryRead_NonExistentPath_ReturnsFalse()
{
bool result = LicenseFile.TryRead(@"C:\nonexistent\path\license.lic", out string base64);
Assert.IsFalse(result);
Assert.IsNull(base64);
}
}
}

View file

@ -1,11 +1,11 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using Moq; using Moq;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Mitsubishi; using NcProgramManager.Cnc.Mitsubishi;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class MitsubishiMachineTests public class MitsubishiMachineTests

View file

@ -1,8 +1,8 @@
using NUnit.Framework; using NUnit.Framework;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class NcProgramValidatorTests public class NcProgramValidatorTests

View file

@ -1,11 +1,11 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using Moq; using Moq;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using FanucProgramManager.Cnc.Siemens; using NcProgramManager.Cnc.Siemens;
namespace FanucProgramManager.Tests.Unit namespace NcProgramManager.Tests.Unit
{ {
[TestFixture] [TestFixture]
public class SiemensMachineTests public class SiemensMachineTests

View file

@ -6,8 +6,8 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1032188B-3321-42B2-87ED-343BF718CAF5}</ProjectGuid> <ProjectGuid>{1032188B-3321-42B2-87ED-343BF718CAF5}</ProjectGuid>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>FanucProgramManager</RootNamespace> <RootNamespace>NcProgramManager</RootNamespace>
<AssemblyName>FanucProgramManager</AssemblyName> <AssemblyName>NcProgramManager</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@ -75,7 +75,6 @@
<Compile Include="AesCrypt.cs" /> <Compile Include="AesCrypt.cs" />
<Compile Include="FanucProgram.cs" /> <Compile Include="FanucProgram.cs" />
<Compile Include="InputArgs.cs" /> <Compile Include="InputArgs.cs" />
<Compile Include="FANUCMachine.cs" />
<Compile Include="fwlib32.cs" /> <Compile Include="fwlib32.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@ -119,6 +118,11 @@
<Compile Include="Cnc\Mitsubishi\IMitsubishiFtpClient.cs" /> <Compile Include="Cnc\Mitsubishi\IMitsubishiFtpClient.cs" />
<Compile Include="Cnc\Mitsubishi\MitsubishiFtpClient.cs" /> <Compile Include="Cnc\Mitsubishi\MitsubishiFtpClient.cs" />
<Compile Include="Cnc\Mitsubishi\MitsubishiMachine.cs" /> <Compile Include="Cnc\Mitsubishi\MitsubishiMachine.cs" />
<Compile Include="Licensing\IMachineFingerprint.cs" />
<Compile Include="Licensing\ILicenseValidator.cs" />
<Compile Include="Licensing\LicenseFile.cs" />
<Compile Include="Licensing\MachineFingerprint.cs" />
<Compile Include="Licensing\LicenseValidator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />

View file

@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.5.33414.496 VisualStudioVersion = 17.5.33414.496
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FanucProgramManager", "FanucProgramManager.csproj", "{1032188B-3321-42B2-87ED-343BF718CAF5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NcProgramManager", "NcProgramManager.csproj", "{1032188B-3321-42B2-87ED-343BF718CAF5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FanucProgramManager.Tests", "FanucProgramManager.Tests\FanucProgramManager.Tests.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NcProgramManager.Tests", "NcProgramManager.Tests\NcProgramManager.Tests.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -1,13 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Management;
using System.Security.Cryptography;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FanucProgramManager.Cnc; using NcProgramManager.Cnc;
using FanucProgramManager.Cnc.Models; using NcProgramManager.Cnc.Models;
using NcProgramManager.Licensing;
namespace FanucProgramManager namespace NcProgramManager
{ {
internal class Program internal class Program
{ {
@ -35,7 +34,11 @@ namespace FanucProgramManager
return 0; return 0;
} }
if (!checkLicense(inputArgs.chiave)) string _licFingerprint = new MachineFingerprint().GetFingerprint();
string _licBase64;
string _licPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic");
if (!LicenseFile.TryRead(_licPath, out _licBase64)
|| !new LicenseValidator().Validate(_licFingerprint, _licBase64))
{ {
Console.WriteLine("Licenza ERRATA"); Console.WriteLine("Licenza ERRATA");
vediErrore(-100); vediErrore(-100);
@ -275,47 +278,6 @@ namespace FanucProgramManager
Console.ReadLine(); Console.ReadLine();
} }
static Boolean checkLicense(string chiave)
{
string pcInfo = string.Empty;
ManagementClass mc = new ManagementClass("win32_processor");
ManagementObjectCollection moc = mc.GetInstances();
foreach (ManagementObject mo in moc)
{
//Get only the first CPU's ID
pcInfo = mo.Properties["processorID"].Value.ToString();
}
ManagementObjectSearcher baseboardSearcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_BaseBoard");
foreach (ManagementObject queryObj in baseboardSearcher.Get())
{
//Get motherboardSerialNumber
pcInfo += queryObj["SerialNumber"].ToString();
break;
}
using (Aes myAes = Aes.Create())
{
if (chiave == "")
return false;
myAes.IV = Convert.FromBase64String("qqxNM8Li99Cwx61VX/QoWA==");
myAes.Key = Convert.FromBase64String("7/12mP3dFLGqPGjujlwBCVblURxBaBcGGQ2c2FatfP4=");
byte[] encrypted = AesCrypt.EncryptStringToBytes_Aes(pcInfo, myAes.Key, myAes.IV);
string roundtrip = AesCrypt.DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);
string decoded = "";
try
{
decoded = AesCrypt.DecryptStringFromBytes_Aes(Convert.FromBase64String(chiave), myAes.Key, myAes.IV);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return pcInfo == decoded;
}
}
static void MostraAiuto() static void MostraAiuto()
{ {
Console.WriteLine("Utilizzo del Software gestore Programmi FANUC"); Console.WriteLine("Utilizzo del Software gestore Programmi FANUC");
@ -326,8 +288,7 @@ namespace FanucProgramManager
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Lista dei parametri da fornire all'eseguibile\nDevono essere separati da uno spazio tra l'uno e l'altro,\nnon andare a capo e non lasciare spazi dopo l'= "); Console.WriteLine("Lista dei parametri da fornire all'eseguibile\nDevono essere separati da uno spazio tra l'uno e l'altro,\nnon andare a capo e non lasciare spazi dopo l'= ");
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("-chiave="); Console.WriteLine("Il file 'license.lic' deve essere posizionato nella stessa cartella dell'eseguibile.");
Console.WriteLine(" la stringa chiave per eseguire il software.");
Console.WriteLine("-azione="); Console.WriteLine("-azione=");
Console.WriteLine(" l'{AZIONE} da eseguire."); Console.WriteLine(" l'{AZIONE} da eseguire.");
Console.WriteLine("-manufacturer="); Console.WriteLine("-manufacturer=");
@ -385,8 +346,6 @@ namespace FanucProgramManager
inputArgs.mostraHelp = true; inputArgs.mostraHelp = true;
return; return;
} }
var chiave = arguments.Find(it => new Regex("-chiave=.*", RegexOptions.IgnoreCase).IsMatch(it));
inputArgs.chiave = chiave.Substring(8);
var azione = arguments.Find(it => new Regex("-azione=.*", RegexOptions.IgnoreCase).IsMatch(it)); var azione = arguments.Find(it => new Regex("-azione=.*", RegexOptions.IgnoreCase).IsMatch(it));
inputArgs.azione = azione.Substring(8); inputArgs.azione = azione.Substring(8);
var manufacturer = arguments.Find(it => new Regex("-manufacturer=.*", RegexOptions.IgnoreCase).IsMatch(it)); var manufacturer = arguments.Find(it => new Regex("-manufacturer=.*", RegexOptions.IgnoreCase).IsMatch(it));

View file

@ -2,17 +2,17 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("FanucProgramManager.Tests")] [assembly: InternalsVisibleTo("NcProgramManager.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
// Le informazioni generali relative a un assembly sono controllate dal seguente // Le informazioni generali relative a un assembly sono controllate dal seguente
// set di attributi. Modificare i valori di questi attributi per modificare le informazioni // set di attributi. Modificare i valori di questi attributi per modificare le informazioni
// associate a un assembly. // associate a un assembly.
[assembly: AssemblyTitle("FanucProgramManager")] [assembly: AssemblyTitle("NcProgramManager")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FanucProgramManager")] [assembly: AssemblyProduct("NcProgramManager")]
[assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]