@aurabx/jmix-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai/security.md +184 -0
- package/.eslintrc.cjs +23 -0
- package/.idea/jmix-ts.iml +13 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc +9 -0
- package/README.md +335 -0
- package/WARP.md +143 -0
- package/dist/JmixBuilder.d.ts +82 -0
- package/dist/JmixBuilder.d.ts.map +1 -0
- package/dist/JmixBuilder.js +353 -0
- package/dist/JmixBuilder.js.map +1 -0
- package/dist/crypto/PayloadDecryptor.d.ts +12 -0
- package/dist/crypto/PayloadDecryptor.d.ts.map +1 -0
- package/dist/crypto/PayloadDecryptor.js +39 -0
- package/dist/crypto/PayloadDecryptor.js.map +1 -0
- package/dist/crypto/PayloadEncryptor.d.ts +21 -0
- package/dist/crypto/PayloadEncryptor.d.ts.map +1 -0
- package/dist/crypto/PayloadEncryptor.js +79 -0
- package/dist/crypto/PayloadEncryptor.js.map +1 -0
- package/dist/demo-decrypt-existing.js +27 -0
- package/dist/demo-decrypt.js +39 -0
- package/dist/demo-no-validation.js +95 -0
- package/dist/demo-package-encrypted.js +34 -0
- package/dist/demo-package.js +28 -0
- package/dist/demo-verify-hash.js +30 -0
- package/dist/demo.js +73 -0
- package/dist/dicom/DicomProcessor.d.ts +40 -0
- package/dist/dicom/DicomProcessor.d.ts.map +1 -0
- package/dist/dicom/DicomProcessor.js +231 -0
- package/dist/dicom/DicomProcessor.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +165 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +31 -0
- package/dist/types/index.js.map +1 -0
- package/dist/validation/SchemaValidator.d.ts +25 -0
- package/dist/validation/SchemaValidator.d.ts.map +1 -0
- package/dist/validation/SchemaValidator.js +125 -0
- package/dist/validation/SchemaValidator.js.map +1 -0
- package/jest.config.json +20 -0
- package/package.json +74 -0
- package/samples/sample_config.json +52 -0
- package/samples/study_1/series_1/CT.1.1.dcm +0 -0
- package/samples/study_1/series_1/CT.1.2.dcm +0 -0
- package/samples/study_1/series_1/CT.1.3.dcm +0 -0
- package/samples/study_1/series_1/CT.1.4.dcm +0 -0
- package/samples/study_1/series_1/CT.1.5.dcm +0 -0
- package/samples/study_1/series_2/CT.2.1.dcm +0 -0
- package/samples/study_1/series_2/CT.2.2.dcm +0 -0
- package/samples/study_1/series_2/CT.2.3.dcm +0 -0
- package/samples/study_1/series_2/CT.2.4.dcm +0 -0
- package/samples/study_1/series_2/CT.2.5.dcm +0 -0
- package/samples/study_1/series_3/CT.3.1.dcm +0 -0
- package/samples/study_1/series_3/CT.3.2.dcm +0 -0
- package/samples/study_1/series_3/CT.3.3.dcm +0 -0
- package/samples/study_1/series_3/CT.3.4.dcm +0 -0
- package/samples/study_1/series_3/CT.3.5.dcm +0 -0
- package/src/JmixBuilder.ts +572 -0
- package/src/crypto/PayloadDecryptor.ts +45 -0
- package/src/crypto/PayloadEncryptor.ts +97 -0
- package/src/dicom/DicomProcessor.ts +262 -0
- package/src/index.ts +30 -0
- package/src/types/daikon.d.ts +1 -0
- package/src/types/index.ts +201 -0
- package/src/validation/SchemaValidator.ts +156 -0
- package/test-encrypt.js +10 -0
- package/tests/JmixBuilder.test.ts +170 -0
- package/tests/SchemaValidator.test.ts +196 -0
- package/tsconfig.json +21 -0
package/WARP.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# WARP.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to WARP (warp.dev) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
Repository: jmix-ts - TypeScript library for JMIX (JSON Medical Interchange) format
|
|
6
|
+
|
|
7
|
+
## What This Library Does
|
|
8
|
+
|
|
9
|
+
JMIX-TS builds secure JMIX envelopes from DICOM directories, supporting:
|
|
10
|
+
- AES-256-GCM encryption with X25519 key agreement and HKDF key derivation
|
|
11
|
+
- Three JSON components: manifest.json (routing/security), metadata.json (clinical), audit.json (trail)
|
|
12
|
+
- Deterministic payload hash verification over plaintext payload directories
|
|
13
|
+
- Full-featured packaging including original DICOM files and encrypted payload support
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- Node.js 20+ LTS
|
|
18
|
+
- npm or compatible package manager
|
|
19
|
+
- TypeScript 5+
|
|
20
|
+
|
|
21
|
+
## Essential Commands
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Install dependencies
|
|
25
|
+
npm install
|
|
26
|
+
|
|
27
|
+
# Build the library
|
|
28
|
+
npm run build
|
|
29
|
+
|
|
30
|
+
# Run all tests (Jest with ESM support)
|
|
31
|
+
npm test
|
|
32
|
+
|
|
33
|
+
# Run specific test
|
|
34
|
+
npm test -- JmixBuilder.test.ts
|
|
35
|
+
|
|
36
|
+
# Test with coverage report (outputs to coverage/)
|
|
37
|
+
npm run test:coverage
|
|
38
|
+
|
|
39
|
+
# Watch mode for development
|
|
40
|
+
npm run test:watch
|
|
41
|
+
|
|
42
|
+
# Type checking only
|
|
43
|
+
npm run typecheck
|
|
44
|
+
|
|
45
|
+
# Format code
|
|
46
|
+
npm run format:fix
|
|
47
|
+
|
|
48
|
+
# Clean build artifacts and temporary files
|
|
49
|
+
npm run clean
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Demo Commands
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Basic package creation (plaintext)
|
|
56
|
+
npm run demo:package
|
|
57
|
+
|
|
58
|
+
# Encrypted package with generated keys
|
|
59
|
+
npm run demo:package:encrypted
|
|
60
|
+
|
|
61
|
+
# Decrypt and verify workflow
|
|
62
|
+
npm run demo:decrypt
|
|
63
|
+
|
|
64
|
+
# Verify payload hash
|
|
65
|
+
npm run demo:verify:hash
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## High-Level Architecture
|
|
69
|
+
|
|
70
|
+
**Core Flow**: JmixBuilder orchestrates DICOM processing → JSON component generation → schema validation → file output/packaging.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
DICOM Directory → DicomProcessor → JmixBuilder → {manifest, metadata, audit} → SchemaValidator → Output
|
|
74
|
+
↓
|
|
75
|
+
PayloadEncryptor/Decryptor → Encrypted JMIX Package
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Key Components**:
|
|
79
|
+
- `JmixBuilder`: Main orchestrator with buildFromDicom(), packageToDirectory(), and packageEncryptedToDirectory()
|
|
80
|
+
- `DicomProcessor`: Recursively scans directories, validates DICOM magic signatures, extracts metadata
|
|
81
|
+
- `SchemaValidator`: Ajv-based validation against JSON schemas in configurable path (default: ../jmix/schemas)
|
|
82
|
+
- `PayloadEncryptor/Decryptor`: AES-256-GCM with X25519+HKDF, base64-encoded ephemeral keys/IV/auth tags
|
|
83
|
+
- Deterministic payload hashing: SHA-256 over sorted (path + newline + bytes) for all payload files
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
### Schema Path
|
|
88
|
+
- Default: `../jmix/schemas` (works in monorepo structure)
|
|
89
|
+
- Override: `JMIX_SCHEMA_PATH` environment variable
|
|
90
|
+
- Constructor: `new SchemaValidator({ schemaPath: '/custom/path' })`
|
|
91
|
+
- Gracefully skips validation when schemas not found
|
|
92
|
+
|
|
93
|
+
### Output Directory
|
|
94
|
+
- Default: `./tmp` (not system /tmp)
|
|
95
|
+
- Constructor: `new JmixBuilder({ outputPath: './custom-tmp' })`
|
|
96
|
+
|
|
97
|
+
## Testing Strategy
|
|
98
|
+
|
|
99
|
+
- Uses sample DICOM files in `samples/` directory
|
|
100
|
+
- All test outputs written to `./tmp` directory
|
|
101
|
+
- Comprehensive coverage: unit tests, integration tests, schema validation
|
|
102
|
+
- Real DICOM file processing with fallback to configuration data
|
|
103
|
+
- Mock-free approach using actual sample data
|
|
104
|
+
|
|
105
|
+
## Cryptography Implementation
|
|
106
|
+
|
|
107
|
+
Based on JMIX security whitepaper (see `.ai/security.md`):
|
|
108
|
+
- **Algorithm**: AES-256-GCM
|
|
109
|
+
- **Key Exchange**: X25519 (Curve25519) ECDH
|
|
110
|
+
- **Key Derivation**: HKDF-SHA256 with info="JMIX-Payload-Encryption"
|
|
111
|
+
- **Forward Secrecy**: Ephemeral sender keys for each envelope
|
|
112
|
+
- **Authentication**: GCM authentication tag prevents tampering
|
|
113
|
+
|
|
114
|
+
Encryption creates `payload.encrypted` from tar-compressed payload directory, with all crypto material (ephemeral_public_key, iv, auth_tag) stored base64-encoded in manifest.security.encryption.
|
|
115
|
+
|
|
116
|
+
## File Structure Reference
|
|
117
|
+
|
|
118
|
+
**Generated JMIX Envelope**:
|
|
119
|
+
```
|
|
120
|
+
<envelope_id>.JMIX/
|
|
121
|
+
├── manifest.json # Routing, security, patient info
|
|
122
|
+
├── audit.json # Audit trail and events
|
|
123
|
+
└── payload/ # OR payload.encrypted (when encrypted)
|
|
124
|
+
├── metadata.json # Clinical metadata and DICOM info
|
|
125
|
+
└── dicom/ # Original DICOM files (structure preserved)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Development Notes
|
|
129
|
+
|
|
130
|
+
- **ESM modules**: Uses import/export syntax, .js extensions in TypeScript imports
|
|
131
|
+
- **Jest configuration**: Supports ESM with ts-jest preset
|
|
132
|
+
- **Schema validation**: Optional and graceful - continues if schemas missing
|
|
133
|
+
- **Error handling**: Structured error types (JmixError, ValidationError, DicomError, CryptographyError)
|
|
134
|
+
- **DICOM detection**: Magic number 'DICM' at byte offset 128 plus .dcm/.dicom extensions
|
|
135
|
+
|
|
136
|
+
## Monorepo Context
|
|
137
|
+
|
|
138
|
+
This library expects to work within a larger jmix monorepo structure where:
|
|
139
|
+
- Schema definitions are available at `../jmix/schemas`
|
|
140
|
+
- Related Rust CLI tools may consume this library's output
|
|
141
|
+
- Sample data in `samples/` is shared across implementations
|
|
142
|
+
|
|
143
|
+
For more detailed guidance on general WARP usage patterns, see `.ai/WARP.md`.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { SchemaValidator, SchemaValidatorOptions } from './validation/SchemaValidator.js';
|
|
2
|
+
import { DicomProcessor } from './dicom/DicomProcessor.js';
|
|
3
|
+
import { JmixEnvelope, Config } from './types/index.js';
|
|
4
|
+
export interface JmixBuilderOptions {
|
|
5
|
+
schemaValidatorOptions?: SchemaValidatorOptions;
|
|
6
|
+
outputPath?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class JmixBuilder {
|
|
9
|
+
private validator;
|
|
10
|
+
private dicomProcessor;
|
|
11
|
+
private outputPath;
|
|
12
|
+
constructor(options?: JmixBuilderOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Build a JMIX envelope from DICOM files and configuration
|
|
15
|
+
*/
|
|
16
|
+
buildFromDicom(dicomPath: string, config: Config): Promise<JmixEnvelope>;
|
|
17
|
+
/**
|
|
18
|
+
* Save envelope to JSON files
|
|
19
|
+
*/
|
|
20
|
+
saveToFiles(envelope: JmixEnvelope, outputPath?: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Build manifest component
|
|
23
|
+
*/
|
|
24
|
+
private buildManifest;
|
|
25
|
+
/**
|
|
26
|
+
* Build metadata component
|
|
27
|
+
*/
|
|
28
|
+
private buildMetadata;
|
|
29
|
+
/**
|
|
30
|
+
* Build audit component (audit trail)
|
|
31
|
+
*/
|
|
32
|
+
private buildAudit;
|
|
33
|
+
/**
|
|
34
|
+
* Load configuration from JSON file
|
|
35
|
+
*/
|
|
36
|
+
static loadConfig(configPath: string): Promise<Config>;
|
|
37
|
+
/**
|
|
38
|
+
* Get the schema validator instance
|
|
39
|
+
*/
|
|
40
|
+
getValidator(): SchemaValidator;
|
|
41
|
+
/**
|
|
42
|
+
* Get the DICOM processor instance
|
|
43
|
+
*/
|
|
44
|
+
getDicomProcessor(): DicomProcessor;
|
|
45
|
+
/**
|
|
46
|
+
* Package an ENCRYPTED JMIX envelope directory:
|
|
47
|
+
* - Builds plaintext payload/ first (metadata.json + dicom/)
|
|
48
|
+
* - Computes payload_hash over payload/
|
|
49
|
+
* - Tars payload/, encrypts to payload.encrypted with AES-256-GCM via X25519+HKDF
|
|
50
|
+
* - Removes plaintext payload/ directory
|
|
51
|
+
* Layout:
|
|
52
|
+
* <outputRoot>/<envelope_id>.JMIX/
|
|
53
|
+
* manifest.json (includes security.encryption & payload_hash)
|
|
54
|
+
* audit.json
|
|
55
|
+
* payload.encrypted
|
|
56
|
+
*/
|
|
57
|
+
packageEncryptedToDirectory(dicomPath: string, config: Config, outputRoot: string, recipientPublicKeyBase64: string): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Package a JMIX envelope to a directory structure that includes original DICOM files.
|
|
60
|
+
* Layout:
|
|
61
|
+
* <outputRoot>/<envelope_id>.JMIX/
|
|
62
|
+
* manifest.json
|
|
63
|
+
* audit.json
|
|
64
|
+
* payload/
|
|
65
|
+
* metadata.json
|
|
66
|
+
* dicom/ ... (copied from dicomPath, preserving structure)
|
|
67
|
+
* files/ (optional in future)
|
|
68
|
+
* files.json (optional in future)
|
|
69
|
+
*/
|
|
70
|
+
packageToDirectory(dicomPath: string, config: Config, outputRoot: string): Promise<string>;
|
|
71
|
+
private walkFiles;
|
|
72
|
+
private isLikelyDicom;
|
|
73
|
+
private computePayloadHash;
|
|
74
|
+
/**
|
|
75
|
+
* Decrypt an encrypted JMIX envelope in place: restores a plaintext payload/ directory.
|
|
76
|
+
* - envelopeDir: path to <id>.JMIX directory
|
|
77
|
+
* - recipientPrivateKeyBase64: base64 Curve25519 private key (32 bytes)
|
|
78
|
+
* Returns the extracted payload directory path.
|
|
79
|
+
*/
|
|
80
|
+
decryptEnvelope(envelopeDir: string, recipientPrivateKeyBase64: string): Promise<string>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=JmixBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JmixBuilder.d.ts","sourceRoot":"","sources":["../src/JmixBuilder.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,eAAe,EACf,sBAAsB,EACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,EACL,YAAY,EACZ,MAAM,EAMP,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,GAAE,kBAAuB;IAM5C;;OAEG;IACG,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC;IA2CxB;;OAEG;IACG,WAAW,CACf,QAAQ,EAAE,YAAY,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IA4ChB;;OAEG;IACH,OAAO,CAAC,aAAa;IAmCrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;OAEG;IACH,OAAO,CAAC,UAAU;IAqBlB;;OAEG;WACU,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa5D;;OAEG;IACH,YAAY,IAAI,eAAe;IAI/B;;OAEG;IACH,iBAAiB,IAAI,cAAc;IAInC;;;;;;;;;;;OAWG;IACG,2BAA2B,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,wBAAwB,EAAE,MAAM,GAC/B,OAAO,CAAC,MAAM,CAAC;IAyElB;;;;;;;;;;;OAWG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;YAoDJ,SAAS;YAeT,aAAa;YAmBb,kBAAkB;IAgBhC;;;;;OAKG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,yBAAyB,EAAE,MAAM,GAChC,OAAO,CAAC,MAAM,CAAC;CA6CnB"}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { randomUUID, createHash } from 'crypto';
|
|
4
|
+
import { SchemaValidator, } from './validation/SchemaValidator.js';
|
|
5
|
+
import { DicomProcessor } from './dicom/DicomProcessor.js';
|
|
6
|
+
import { PayloadEncryptor } from './crypto/PayloadEncryptor.js';
|
|
7
|
+
import tar from 'tar';
|
|
8
|
+
import { PayloadDecryptor } from './crypto/PayloadDecryptor.js';
|
|
9
|
+
import { JmixError, } from './types/index.js';
|
|
10
|
+
export class JmixBuilder {
|
|
11
|
+
validator;
|
|
12
|
+
dicomProcessor;
|
|
13
|
+
outputPath;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.validator = new SchemaValidator(options.schemaValidatorOptions);
|
|
16
|
+
this.dicomProcessor = new DicomProcessor();
|
|
17
|
+
this.outputPath = options.outputPath || './tmp';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a JMIX envelope from DICOM files and configuration
|
|
21
|
+
*/
|
|
22
|
+
async buildFromDicom(dicomPath, config) {
|
|
23
|
+
try {
|
|
24
|
+
// Generate transmission ID and timestamp
|
|
25
|
+
const transmissionId = randomUUID();
|
|
26
|
+
const timestamp = new Date().toISOString();
|
|
27
|
+
// Extract DICOM metadata
|
|
28
|
+
const dicomMetadata = await this.dicomProcessor.processDicomFolder(dicomPath, config);
|
|
29
|
+
// Build envelope components
|
|
30
|
+
const manifest = this.buildManifest(config, timestamp);
|
|
31
|
+
const metadata = this.buildMetadata(config, dicomMetadata);
|
|
32
|
+
const audit = this.buildAudit(transmissionId, timestamp, config);
|
|
33
|
+
const envelope = {
|
|
34
|
+
manifest,
|
|
35
|
+
metadata,
|
|
36
|
+
audit,
|
|
37
|
+
};
|
|
38
|
+
// Validate the envelope components
|
|
39
|
+
await this.validator.validateEnvelope({
|
|
40
|
+
manifest,
|
|
41
|
+
metadata,
|
|
42
|
+
audit,
|
|
43
|
+
});
|
|
44
|
+
return envelope;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new JmixError(`Failed to build JMIX envelope from ${dicomPath}`, error instanceof Error ? error : new Error(String(error)));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Save envelope to JSON files
|
|
52
|
+
*/
|
|
53
|
+
async saveToFiles(envelope, outputPath) {
|
|
54
|
+
const finalOutputPath = outputPath || this.outputPath;
|
|
55
|
+
try {
|
|
56
|
+
// Ensure output directory exists
|
|
57
|
+
await fs.mkdir(finalOutputPath, { recursive: true });
|
|
58
|
+
// Write manifest.json
|
|
59
|
+
await fs.writeFile(path.join(finalOutputPath, 'manifest.json'), JSON.stringify(envelope.manifest, null, 2), 'utf-8');
|
|
60
|
+
// Write metadata.json
|
|
61
|
+
await fs.writeFile(path.join(finalOutputPath, 'metadata.json'), JSON.stringify(envelope.metadata, null, 2), 'utf-8');
|
|
62
|
+
// Write audit.json (audit trail)
|
|
63
|
+
await fs.writeFile(path.join(finalOutputPath, 'audit.json'), JSON.stringify(envelope.audit, null, 2), 'utf-8');
|
|
64
|
+
// Write files.json if present
|
|
65
|
+
if (envelope.files) {
|
|
66
|
+
await fs.writeFile(path.join(finalOutputPath, 'files.json'), JSON.stringify(envelope.files, null, 2), 'utf-8');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new JmixError(`Failed to save envelope to ${finalOutputPath}`, error instanceof Error ? error : new Error(String(error)));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build manifest component
|
|
75
|
+
*/
|
|
76
|
+
buildManifest(config, timestamp) {
|
|
77
|
+
const manifest = {
|
|
78
|
+
jmix_version: config.version,
|
|
79
|
+
envelope_id: randomUUID(),
|
|
80
|
+
created_at: timestamp,
|
|
81
|
+
sender: config.sender,
|
|
82
|
+
receivers: config.receivers,
|
|
83
|
+
patient: config.patient,
|
|
84
|
+
security: config.security,
|
|
85
|
+
};
|
|
86
|
+
// Add optional fields
|
|
87
|
+
if (config.requester) {
|
|
88
|
+
manifest.requester = config.requester;
|
|
89
|
+
}
|
|
90
|
+
if (config.consent) {
|
|
91
|
+
manifest.consent = config.consent;
|
|
92
|
+
}
|
|
93
|
+
if (config.custom_tags) {
|
|
94
|
+
manifest.custom_tags = config.custom_tags;
|
|
95
|
+
}
|
|
96
|
+
if (config.report) {
|
|
97
|
+
manifest.report = config.report;
|
|
98
|
+
}
|
|
99
|
+
if (config.deid_keys) {
|
|
100
|
+
manifest.deid_keys = config.deid_keys;
|
|
101
|
+
}
|
|
102
|
+
return manifest;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Build metadata component
|
|
106
|
+
*/
|
|
107
|
+
buildMetadata(config, dicomMetadata) {
|
|
108
|
+
return {
|
|
109
|
+
patient: config.patient,
|
|
110
|
+
study: {
|
|
111
|
+
description: dicomMetadata.study_description,
|
|
112
|
+
uid: dicomMetadata.study_uid,
|
|
113
|
+
date: dicomMetadata.study_date,
|
|
114
|
+
},
|
|
115
|
+
dicom: dicomMetadata,
|
|
116
|
+
custom_metadata: {},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build audit component (audit trail)
|
|
121
|
+
*/
|
|
122
|
+
buildAudit(transmissionId, timestamp, config) {
|
|
123
|
+
const firstReceiver = config.receivers && config.receivers[0];
|
|
124
|
+
const entry = {
|
|
125
|
+
event: 'envelope_created',
|
|
126
|
+
timestamp,
|
|
127
|
+
by: { id: config.sender.id, name: config.sender.name },
|
|
128
|
+
to: firstReceiver ? { id: firstReceiver.id, name: firstReceiver.name } : undefined,
|
|
129
|
+
};
|
|
130
|
+
const audit = {
|
|
131
|
+
audit: [entry],
|
|
132
|
+
};
|
|
133
|
+
return audit;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Load configuration from JSON file
|
|
137
|
+
*/
|
|
138
|
+
static async loadConfig(configPath) {
|
|
139
|
+
try {
|
|
140
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
141
|
+
return JSON.parse(configContent);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw new JmixError(`Failed to load configuration from ${configPath}`, error instanceof Error ? error : new Error(String(error)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the schema validator instance
|
|
149
|
+
*/
|
|
150
|
+
getValidator() {
|
|
151
|
+
return this.validator;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get the DICOM processor instance
|
|
155
|
+
*/
|
|
156
|
+
getDicomProcessor() {
|
|
157
|
+
return this.dicomProcessor;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Package an ENCRYPTED JMIX envelope directory:
|
|
161
|
+
* - Builds plaintext payload/ first (metadata.json + dicom/)
|
|
162
|
+
* - Computes payload_hash over payload/
|
|
163
|
+
* - Tars payload/, encrypts to payload.encrypted with AES-256-GCM via X25519+HKDF
|
|
164
|
+
* - Removes plaintext payload/ directory
|
|
165
|
+
* Layout:
|
|
166
|
+
* <outputRoot>/<envelope_id>.JMIX/
|
|
167
|
+
* manifest.json (includes security.encryption & payload_hash)
|
|
168
|
+
* audit.json
|
|
169
|
+
* payload.encrypted
|
|
170
|
+
*/
|
|
171
|
+
async packageEncryptedToDirectory(dicomPath, config, outputRoot, recipientPublicKeyBase64) {
|
|
172
|
+
// Build the envelope first
|
|
173
|
+
const envelope = await this.buildFromDicom(dicomPath, config);
|
|
174
|
+
const packageRoot = path.resolve(outputRoot, `${envelope.manifest.envelope_id}.JMIX`);
|
|
175
|
+
const payloadDir = path.join(packageRoot, 'payload');
|
|
176
|
+
const dicomOutDir = path.join(payloadDir, 'dicom');
|
|
177
|
+
// Create directory structure and copy DICOM
|
|
178
|
+
await fs.mkdir(dicomOutDir, { recursive: true });
|
|
179
|
+
const allInputFiles = await this.walkFiles(dicomPath);
|
|
180
|
+
for (const absFile of allInputFiles) {
|
|
181
|
+
if (!(await this.isLikelyDicom(absFile)))
|
|
182
|
+
continue;
|
|
183
|
+
const rel = path.relative(dicomPath, absFile);
|
|
184
|
+
const dest = path.join(dicomOutDir, rel);
|
|
185
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
186
|
+
await fs.copyFile(absFile, dest);
|
|
187
|
+
}
|
|
188
|
+
// Write payload/metadata.json
|
|
189
|
+
await fs.mkdir(payloadDir, { recursive: true });
|
|
190
|
+
await fs.writeFile(path.join(payloadDir, 'metadata.json'), JSON.stringify(envelope.metadata, null, 2), 'utf-8');
|
|
191
|
+
// Compute payload hash over plaintext payload
|
|
192
|
+
const payloadHash = await this.computePayloadHash(payloadDir);
|
|
193
|
+
// Tar the payload directory to a temporary tar file under the package root
|
|
194
|
+
const tmpTarPath = path.join(packageRoot, 'payload.tmp.tar');
|
|
195
|
+
await tar.c({ cwd: packageRoot, file: tmpTarPath, portable: true, gzip: false }, ['payload']);
|
|
196
|
+
// Read tar and encrypt
|
|
197
|
+
const tarBuf = await fs.readFile(tmpTarPath);
|
|
198
|
+
const { ciphertext, result } = PayloadEncryptor.encryptTar(tarBuf, recipientPublicKeyBase64);
|
|
199
|
+
const encryptedPath = path.join(packageRoot, 'payload.encrypted');
|
|
200
|
+
await fs.writeFile(encryptedPath, ciphertext);
|
|
201
|
+
// Cleanup plaintext tar and payload directory
|
|
202
|
+
await fs.rm(tmpTarPath, { force: true });
|
|
203
|
+
await fs.rm(payloadDir, { recursive: true, force: true });
|
|
204
|
+
// Update manifest security
|
|
205
|
+
envelope.manifest.security = {
|
|
206
|
+
...envelope.manifest.security,
|
|
207
|
+
payload_hash: payloadHash,
|
|
208
|
+
encryption: {
|
|
209
|
+
algorithm: 'AES-256-GCM',
|
|
210
|
+
ephemeral_public_key: result.ephemeral_public_key,
|
|
211
|
+
iv: result.iv,
|
|
212
|
+
auth_tag: result.auth_tag,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
// Write manifest.json and audit.json
|
|
216
|
+
await fs.mkdir(packageRoot, { recursive: true });
|
|
217
|
+
await fs.writeFile(path.join(packageRoot, 'manifest.json'), JSON.stringify(envelope.manifest, null, 2), 'utf-8');
|
|
218
|
+
await fs.writeFile(path.join(packageRoot, 'audit.json'), JSON.stringify(envelope.audit, null, 2), 'utf-8');
|
|
219
|
+
return packageRoot;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Package a JMIX envelope to a directory structure that includes original DICOM files.
|
|
223
|
+
* Layout:
|
|
224
|
+
* <outputRoot>/<envelope_id>.JMIX/
|
|
225
|
+
* manifest.json
|
|
226
|
+
* audit.json
|
|
227
|
+
* payload/
|
|
228
|
+
* metadata.json
|
|
229
|
+
* dicom/ ... (copied from dicomPath, preserving structure)
|
|
230
|
+
* files/ (optional in future)
|
|
231
|
+
* files.json (optional in future)
|
|
232
|
+
*/
|
|
233
|
+
async packageToDirectory(dicomPath, config, outputRoot) {
|
|
234
|
+
// Build the envelope first
|
|
235
|
+
const envelope = await this.buildFromDicom(dicomPath, config);
|
|
236
|
+
const packageRoot = path.resolve(outputRoot, `${envelope.manifest.envelope_id}.JMIX`);
|
|
237
|
+
const payloadDir = path.join(packageRoot, 'payload');
|
|
238
|
+
const dicomOutDir = path.join(payloadDir, 'dicom');
|
|
239
|
+
// Create directory structure
|
|
240
|
+
await fs.mkdir(dicomOutDir, { recursive: true });
|
|
241
|
+
// Copy DICOM files preserving relative structure
|
|
242
|
+
const allInputFiles = await this.walkFiles(dicomPath);
|
|
243
|
+
for (const absFile of allInputFiles) {
|
|
244
|
+
if (!(await this.isLikelyDicom(absFile)))
|
|
245
|
+
continue;
|
|
246
|
+
const rel = path.relative(dicomPath, absFile);
|
|
247
|
+
const dest = path.join(dicomOutDir, rel);
|
|
248
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
249
|
+
await fs.copyFile(absFile, dest);
|
|
250
|
+
}
|
|
251
|
+
// Write payload/metadata.json
|
|
252
|
+
await fs.mkdir(payloadDir, { recursive: true });
|
|
253
|
+
await fs.writeFile(path.join(payloadDir, 'metadata.json'), JSON.stringify(envelope.metadata, null, 2), 'utf-8');
|
|
254
|
+
// Compute payload hash over all files under payload/
|
|
255
|
+
const payloadHash = await this.computePayloadHash(payloadDir);
|
|
256
|
+
envelope.manifest.security = {
|
|
257
|
+
...envelope.manifest.security,
|
|
258
|
+
payload_hash: payloadHash,
|
|
259
|
+
};
|
|
260
|
+
// Write manifest.json and audit.json at package root
|
|
261
|
+
await fs.writeFile(path.join(packageRoot, 'manifest.json'), JSON.stringify(envelope.manifest, null, 2), 'utf-8');
|
|
262
|
+
await fs.writeFile(path.join(packageRoot, 'audit.json'), JSON.stringify(envelope.audit, null, 2), 'utf-8');
|
|
263
|
+
return packageRoot;
|
|
264
|
+
}
|
|
265
|
+
// Recursively list all files under a directory (files only)
|
|
266
|
+
async walkFiles(root) {
|
|
267
|
+
const out = [];
|
|
268
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
const full = path.join(root, entry.name);
|
|
271
|
+
if (entry.isDirectory()) {
|
|
272
|
+
out.push(...(await this.walkFiles(full)));
|
|
273
|
+
}
|
|
274
|
+
else if (entry.isFile()) {
|
|
275
|
+
out.push(full);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
// Basic DICOM detection: extension or DICM magic at offset 128
|
|
281
|
+
async isLikelyDicom(filePath) {
|
|
282
|
+
try {
|
|
283
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
284
|
+
if (ext === '.dcm' || ext === '.dicom')
|
|
285
|
+
return true;
|
|
286
|
+
const fh = await fs.open(filePath, 'r');
|
|
287
|
+
try {
|
|
288
|
+
const buf = Buffer.alloc(4);
|
|
289
|
+
await fh.read(buf, 0, 4, 128);
|
|
290
|
+
return buf.toString('ascii') === 'DICM';
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
await fh.close();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Deterministic payload hash: sha256 over ordered (path + newline + bytes) for all files under payload/
|
|
301
|
+
async computePayloadHash(payloadDir) {
|
|
302
|
+
const all = await this.walkFiles(payloadDir);
|
|
303
|
+
const rels = all
|
|
304
|
+
.map((abs) => ({ abs, rel: path.posix.join(...path.relative(payloadDir, abs).split(path.sep)) }))
|
|
305
|
+
.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
|
|
306
|
+
const hash = createHash('sha256');
|
|
307
|
+
for (const f of rels) {
|
|
308
|
+
hash.update(Buffer.from(f.rel + '\n', 'utf-8'));
|
|
309
|
+
const data = await fs.readFile(f.abs);
|
|
310
|
+
hash.update(data);
|
|
311
|
+
}
|
|
312
|
+
const digestHex = hash.digest('hex');
|
|
313
|
+
return `sha256:${digestHex}`;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Decrypt an encrypted JMIX envelope in place: restores a plaintext payload/ directory.
|
|
317
|
+
* - envelopeDir: path to <id>.JMIX directory
|
|
318
|
+
* - recipientPrivateKeyBase64: base64 Curve25519 private key (32 bytes)
|
|
319
|
+
* Returns the extracted payload directory path.
|
|
320
|
+
*/
|
|
321
|
+
async decryptEnvelope(envelopeDir, recipientPrivateKeyBase64) {
|
|
322
|
+
const manifestPath = path.join(envelopeDir, 'manifest.json');
|
|
323
|
+
const encryptedPath = path.join(envelopeDir, 'payload.encrypted');
|
|
324
|
+
// Read manifest and encryption info
|
|
325
|
+
const manifestRaw = await fs.readFile(manifestPath, 'utf-8');
|
|
326
|
+
const manifest = JSON.parse(manifestRaw);
|
|
327
|
+
const enc = manifest?.security?.encryption;
|
|
328
|
+
if (!enc || !enc.ephemeral_public_key || !enc.iv || !enc.auth_tag) {
|
|
329
|
+
throw new Error('Envelope is not encrypted or missing encryption metadata');
|
|
330
|
+
}
|
|
331
|
+
// Read ciphertext
|
|
332
|
+
const ciphertext = await fs.readFile(encryptedPath);
|
|
333
|
+
// Decrypt to tar buffer
|
|
334
|
+
const tarBuf = PayloadDecryptor.decryptToBuffer(ciphertext, enc.ephemeral_public_key, enc.iv, enc.auth_tag, recipientPrivateKeyBase64);
|
|
335
|
+
// Write tar to temp and extract to payload/
|
|
336
|
+
const tmpTar = path.join(envelopeDir, 'payload.decrypted.tar');
|
|
337
|
+
await fs.writeFile(tmpTar, tarBuf);
|
|
338
|
+
const payloadDir = path.join(envelopeDir, 'payload');
|
|
339
|
+
await fs.mkdir(payloadDir, { recursive: true });
|
|
340
|
+
await tar.x({ cwd: envelopeDir, file: tmpTar });
|
|
341
|
+
// Remove temp tar
|
|
342
|
+
await fs.rm(tmpTar, { force: true });
|
|
343
|
+
// Optional: verify payload hash
|
|
344
|
+
if (manifest?.security?.payload_hash) {
|
|
345
|
+
const computed = await this.computePayloadHash(payloadDir);
|
|
346
|
+
if (computed !== manifest.security.payload_hash) {
|
|
347
|
+
throw new Error(`Payload hash mismatch: expected ${manifest.security.payload_hash} but got ${computed}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return payloadDir;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=JmixBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JmixBuilder.js","sourceRoot":"","sources":["../src/JmixBuilder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class PayloadDecryptor {
|
|
2
|
+
/**
|
|
3
|
+
* Decrypt AES-256-GCM ciphertext using X25519 + HKDF-SHA256 key derivation.
|
|
4
|
+
* - ephPubB64: base64 sender ephemeral public key (32 bytes)
|
|
5
|
+
* - ivB64: base64 12-byte IV
|
|
6
|
+
* - tagB64: base64 16-byte auth tag
|
|
7
|
+
* - recipientPrivB64: base64 32-byte recipient private key
|
|
8
|
+
* Returns plaintext Buffer (tar data).
|
|
9
|
+
*/
|
|
10
|
+
static decryptToBuffer(ciphertext: Buffer, ephPubB64: string, ivB64: string, tagB64: string, recipientPrivB64: string): Buffer;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=PayloadDecryptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PayloadDecryptor.d.ts","sourceRoot":"","sources":["../../src/crypto/PayloadDecryptor.ts"],"names":[],"mappings":"AAGA,qBAAa,gBAAgB;IAC3B;;;;;;;OAOG;IACH,MAAM,CAAC,eAAe,CACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,GACvB,MAAM;CA0BV"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createDecipheriv, hkdfSync } from 'crypto';
|
|
2
|
+
import nacl from 'tweetnacl';
|
|
3
|
+
export class PayloadDecryptor {
|
|
4
|
+
/**
|
|
5
|
+
* Decrypt AES-256-GCM ciphertext using X25519 + HKDF-SHA256 key derivation.
|
|
6
|
+
* - ephPubB64: base64 sender ephemeral public key (32 bytes)
|
|
7
|
+
* - ivB64: base64 12-byte IV
|
|
8
|
+
* - tagB64: base64 16-byte auth tag
|
|
9
|
+
* - recipientPrivB64: base64 32-byte recipient private key
|
|
10
|
+
* Returns plaintext Buffer (tar data).
|
|
11
|
+
*/
|
|
12
|
+
static decryptToBuffer(ciphertext, ephPubB64, ivB64, tagB64, recipientPrivB64) {
|
|
13
|
+
const ephPub = Buffer.from(ephPubB64, 'base64');
|
|
14
|
+
const iv = Buffer.from(ivB64, 'base64');
|
|
15
|
+
const tag = Buffer.from(tagB64, 'base64');
|
|
16
|
+
const recipPriv = Buffer.from(recipientPrivB64, 'base64');
|
|
17
|
+
if (ephPub.length !== 32)
|
|
18
|
+
throw new Error('Invalid ephemeral public key');
|
|
19
|
+
if (recipPriv.length !== 32)
|
|
20
|
+
throw new Error('Invalid recipient private key');
|
|
21
|
+
if (iv.length !== 12)
|
|
22
|
+
throw new Error('Invalid IV length (expected 12)');
|
|
23
|
+
if (tag.length !== 16)
|
|
24
|
+
throw new Error('Invalid auth tag length (expected 16)');
|
|
25
|
+
// X25519 shared secret
|
|
26
|
+
const shared = nacl.scalarMult(new Uint8Array(recipPriv), new Uint8Array(ephPub));
|
|
27
|
+
// Derive 32-byte AES key with HKDF-SHA256
|
|
28
|
+
const salt = Buffer.alloc(0);
|
|
29
|
+
const info = Buffer.from('JMIX-Payload-Encryption');
|
|
30
|
+
const hkdfOut = hkdfSync('sha256', Buffer.from(shared), salt, info, 32);
|
|
31
|
+
const key = Buffer.from(hkdfOut);
|
|
32
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
33
|
+
decipher.setAuthTag(tag);
|
|
34
|
+
const dec1 = decipher.update(ciphertext);
|
|
35
|
+
const dec2 = decipher.final();
|
|
36
|
+
return Buffer.concat([dec1, dec2]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=PayloadDecryptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PayloadDecryptor.js","sourceRoot":"","sources":["../../src/crypto/PayloadDecryptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,OAAO,gBAAgB;IAC3B;;;;;;;OAOG;IACH,MAAM,CAAC,eAAe,CACpB,UAAkB,EAClB,SAAiB,EACjB,KAAa,EACb,MAAc,EACd,gBAAwB;QAExB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QAE1D,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC1E,IAAI,SAAS,CAAC,MAAM,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAC9E,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACzE,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAEhF,uBAAuB;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAElF,0CAA0C;QAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEjC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QAC1D,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;CACF"}
|