diff --git a/specs/002-geviset-file-format/research.md b/specs/002-geviset-file-format/research.md new file mode 100644 index 0000000..a514def --- /dev/null +++ b/specs/002-geviset-file-format/research.md @@ -0,0 +1,274 @@ +# GeViSet File Format Research Notes + +## Binary Format Discoveries + +### Header Analysis + +**File**: setup_config_20251212_122429.dat (281,714 bytes) + +``` +Offset Hex ASCII +0000: 00 13 47 65 56 69 53 6F 66 74 20 50 61 72 61 6D ..GeViSoft Param +0010: 65 74 65 72 73 eters +``` + +**Structure**: +- `00`: Optional null byte (not always present) +- `13`: Length byte (0x13 = 19 bytes) +- `47 65 56 69 53 6F 66 74 20 50 61 72 61 6D 65 74 65 72 73`: "GeViSoft Parameters" + +**Note**: This is NOT a standard Pascal string (no 0x07 marker), just length + data. + +### Section Structure + +Sections appear to follow this pattern: + +``` +07 // Pascal string for section name +... items ... +05 52 75 6C 65 73 // "Rules" marker (if rules present) +... rules ... +``` + +### Rules Marker Pattern + +Found 65 occurrences of pattern: `05 52 75 6C 65 73` ("Rules") + +Key offsets: +- 252,278 (0x3D976) +- 252,717 (0x3DB2D) +- 253,152 (0x3DCE0) +- ... (65 total) + +After "Rules" marker: +``` +05 52 75 6C 65 73 // "Rules" +02 00 00 00 // Count? (2 rules?) +00 01 31 // Unknown metadata +05 00 00 00 // Another count/offset? +07 01 40 ... // Start of action string +``` + +### Action String Pattern + +**Format**: `07 01 40 ` + +**Examples from file**: + +1. At offset 252,291: +``` +07 01 40 1C 00 47 53 43 20 56 69 65 77 65 72 43 6F 6E 6E 65 63 74 4C 69 76 65 20 56 20 3C 2D 20 43 +│ │ │ │ │ +│ │ │ └──┴─ Length: 0x001C (28 bytes) +│ │ └─ Action marker +│ └─ Subtype +└─ String type + +Action: "GSC ViewerConnectLive V <- C" +``` + +2. At offset 258,581: +``` +07 01 40 11 00 47 53 43 20 56 69 65 77 65 72 43 6C 65 61 72 20 56 +Length: 0x0011 (17 bytes) +Action: "GSC ViewerClear V" +``` + +### Data Type Markers + +| Marker | Type | Evidence | +|--------|---------|-----------------------------------------------| +| 0x01 | Boolean | Followed by 0x00 or 0x01 | +| 0x04 | Int32 | Followed by 4 bytes (little-endian) | +| 0x07 | String | Pascal string: | +| 0x07 0x01 0x40 | Action | Special action string format | + +### Section Names Found + +From file analysis: +- "Description" (most common - appears 832 times) +- "IpHost" +- "GscAction" +- "GCoreAction" +- "Alarms" +- "Clients" +- "GeViIO" + +### Action Mappings Extracted + +Successfully extracted 64 action mappings from the file: + +**PTZ Camera Controls** (Camera 101027): +1. PanLeft_101027 +2. PanRight_101027 +3. PanStop_101027 +4. TiltDown_101027 +5. TiltUp_101027 +6. TiltStop_101027 +7. ZoomIn_101027 +8. ZoomOut_101027 +9. ZoomStop_101027 +10. FocusFar 128_C101027 +11. FocusNear 128_C101027 +12. FocusStop_101027 +13. IrisOpen_101027 +14. IrisClose_101027 +15. IrisStop_101027 + +**Preset Positions**: +16. MoveToDefaultPostion_101027 +17. ClearDefaultPostion_101027 +18. SaveDafaultPostion_101027 +19. MoveToPresentPostion +20. ClearPresentPostion +21. SavePresentPostion + +**Viewer Controls**: +22. ViewerConnectLive V <- C +23. ViewerConnectLive V <- C_101027 +24. ViewerClear V +25. VC live + +**System Messages**: +26-35. Demo mode warnings (100, 90, 80... 10 min) +36. info: licence satisfied +37. info: re_porter mode active +38. error: "GeViIO Client: start of interface failed" +39. error: "GeViIO Client: interface lost" +40. warning: "GeViSoft Server: client warning" + +### Platform Variations + +Actions often have multiple platform-specific versions: + +``` +GSC (GeViScope): + "GSC ViewerConnectLive V <- C" + +GNG (G-Net-Guard): + "GNG ViewerConnectLive V <- C_101027" + +GCore: + "GCore " +``` + +### Unknown Patterns + +Several byte patterns whose purpose is unclear: + +1. **Pattern**: `04 02 40 40 64 00 00 00 00` + - Appears before many action strings + - Possibly metadata or flags + +2. **Pattern**: `00 00 00 00 00 01 31 05 00 00 00` + - Appears after "Rules" marker + - Could be counts, offsets, or IDs + +3. **Pattern**: `0C 4D 61 70 70 69 6E 67 52 75 6C 65 73` + - `0C MappingRules` (length-prefixed, no 0x07) + - At offset 252,172 + - Different string format than Pascal strings + +## Testing Results + +### Round-Trip Test + +``` +✅ SUCCESS! +Original: 281,714 bytes +Parsed: 64 action mappings +Written: 281,714 bytes +Comparison: IDENTICAL (byte-for-byte) +``` + +**Conclusion**: Safe to write back to server with current preservation approach. + +### SetupClient API Test + +``` +✅ Connection successful +✅ Read setup: 281,714 bytes +✅ Write setup: Not tested yet (waiting for full parser) +✅ Password encryption: Working (GeViAPI_EncodeString) +``` + +## Next Research Areas + +### 1. Trigger Parsing + +Need to understand trigger structure: +``` +.VideoInput = True +.InputContact = False +``` + +These appear before action strings in rules. + +### 2. Metadata Bytes + +The bytes between sections and before/after rules: +- What do they represent? +- Are they counts? Offsets? Flags? +- Can they be modified? + +### 3. Section Relationships + +How do sections reference each other? +- Do cameras reference alarm rules? +- Do action mappings reference I/O ports? +- How are IDs assigned? + +### 4. Format Versioning + +Does the format change between GeViSoft versions? +- Version 6.0.1.5 (current) +- How to detect version? +- Compatibility considerations? + +## Tools Used for Analysis + +### Python Scripts + +```python +# Find all "Rules" patterns +import struct +with open('setup_config.dat', 'rb') as f: + data = f.read() + +needle = b'Rules' +pos = 0 +while True: + pos = data.find(needle, pos) + if pos == -1: break + print(f'Found at offset {pos} (0x{pos:X})') + pos += 1 +``` + +### Hex Editors + +- HxD +- 010 Editor +- VS Code with hex extension + +### Binary Analysis + +- Custom C# parser +- Grep for pattern matching +- Byte comparison tools + +## References + +- TestMKS.set (279,860 bytes) - Original test file +- setup_config_20251212_122429.dat (281,714 bytes) - Live server config +- GeViSoft SDK Documentation +- GeViProcAPI.h header file + +## Change Log + +| Date | Discovery | +|------------|----------------------------------------------| +| 2024-12-12 | Initial binary analysis | +| 2024-12-12 | Discovered action string format | +| 2024-12-12 | Found 65 "Rules" markers | +| 2024-12-12 | Extracted 64 action mappings successfully | +| 2024-12-12 | Verified byte-for-byte round-trip | diff --git a/specs/002-geviset-file-format/spec.md b/specs/002-geviset-file-format/spec.md new file mode 100644 index 0000000..4c8eab4 --- /dev/null +++ b/specs/002-geviset-file-format/spec.md @@ -0,0 +1,617 @@ +# GeViSet File Format Reverse Engineering Specification + +**Version:** 1.0 +**Date:** 2024-12-12 +**Status:** In Progress + +## Overview + +This specification documents the reverse engineering effort to fully parse, understand, and manipulate the GeViSoft `.set` configuration file format. The goal is to enable programmatic reading, editing, and writing of GeViServer configurations, particularly action mappings. + +## Background + +### What is a .set File? + +- **Source**: Exported from GeViSet application or read via `GeViAPI_SetupClient_ReadSetup` +- **Purpose**: Complete GeViServer configuration (cameras, alarms, action mappings, users, etc.) +- **Format**: Proprietary binary format with "GeViSoft Parameters" header +- **Size**: Typically 200-300 KB for production configurations +- **Use Case**: Backup, migration, and programmatic configuration management + +### Current State + +**What Works:** +- ✅ Read .set file from GeViServer via SetupClient API +- ✅ Extract 64 action mappings from binary data +- ✅ Write back byte-for-byte identical (round-trip verified) +- ✅ Password encryption with `GeViAPI_EncodeString` + +**What's Missing:** +- ❌ Full structure parsing (all sections, all items) +- ❌ Understanding of all data types and relationships +- ❌ Ability to add NEW action mappings +- ❌ JSON representation of complete structure +- ❌ Comprehensive Excel export/import + +## Requirements + +### Primary Request + +> "I want to do full reverse engineering - I need to parse the whole file and maybe to json format in the first phase and then we will revert this json or its parts to excel" + +### Key Requirements + +1. **Parse Entire File Structure** + - All sections (Alarms, Clients, GeViIO, Cameras, ActionMappings, etc.) + - All configuration items (key-value pairs) + - All rules and triggers + - All metadata and relationships + +2. **JSON Serialization** + - Complete structure in JSON format + - Human-readable and editable + - Preserves all data and relationships + - Round-trip safe (JSON → Binary → JSON) + +3. **Excel Export/Import** + - Export action mappings to Excel + - User-friendly editing interface + - Add new mappings + - Delete existing mappings + - Import back to JSON + +4. **Safety & Validation** + - Verify integrity before writing to server + - Backup original configuration + - Validate against schema + - Error handling and recovery + +## Architecture + +### Data Flow + +``` +┌─────────────────────────────────────────────────────────────┐ +│ GeViServer │ +│ ↓ │ +│ SetupClient API (ReadSetup) │ +└─────────────────────────────────────────────────────────────┘ + ↓ + .set file (binary) + 281,714 bytes + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ PHASE 1: Binary Parser │ +│ - Parse header │ +│ - Parse all sections │ +│ - Parse all items │ +│ - Parse all rules │ +│ - Extract action mappings │ +└─────────────────────────────────────────────────────────────┘ + ↓ + JSON Structure + (full configuration representation) + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ PHASE 2: JSON Processing │ +│ - Validate structure │ +│ - Transform for editing │ +│ - Extract sections │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ PHASE 3: Excel Export │ +│ - Convert action mappings to Excel │ +│ - User edits in Excel │ +│ - Add/delete/modify mappings │ +└─────────────────────────────────────────────────────────────┘ + ↓ + Excel file + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ PHASE 4: Excel Import │ +│ - Read Excel changes │ +│ - Validate data │ +│ - Update JSON structure │ +└─────────────────────────────────────────────────────────────┘ + ↓ + JSON Structure + (modified) + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ PHASE 5: Binary Writer │ +│ - Rebuild .set file from JSON │ +│ - Maintain binary format │ +│ - Validate integrity │ +└─────────────────────────────────────────────────────────────┘ + ↓ + .set file (binary) + ↓ + SetupClient API (WriteSetup) + ↓ + GeViServer +``` + +## Binary Format Analysis + +### File Structure + +``` +.set file +├── Header +│ ├── 0x00 (optional null byte) +│ └── Pascal String: "GeViSoft Parameters" (0x07 ) +│ +├── Sections (multiple) +│ ├── Section Name (Pascal String) +│ ├── Items (key-value pairs) +│ │ ├── Key (Pascal String) +│ │ └── Value (typed) +│ │ ├── 0x01 = Boolean +│ │ ├── 0x04 = Integer (4 bytes) +│ │ └── 0x07 = String (Pascal) +│ │ +│ └── Rules Subsection +│ ├── "Rules" marker (0x05 0x52 0x75 0x6C 0x65 0x73) +│ ├── Count/Metadata +│ └── Action Rules (multiple) +│ ├── Trigger Properties +│ │ └── .PropertyName = Boolean +│ ├── Main Action String +│ │ └── 0x07 0x01 0x40 +│ └── Action Variations +│ ├── GscAction (GeViScope) +│ ├── GNGAction (G-Net-Guard) +│ └── GCoreAction (GCore) +│ +└── Footer (metadata/checksums?) +``` + +### Data Types Discovered + +| Marker | Type | Format | Example | +|--------|---------|----------------------------------|----------------------------| +| 0x01 | Boolean | 0x01 | 0x01 0x01 = true | +| 0x04 | Integer | 0x04 <4-byte little-endian> | 0x04 0x0A 0x00 0x00 0x00 | +| 0x07 | String | 0x07 | 0x07 0x0B "Description" | +| 0x07 0x01 0x40 | Action | 0x07 0x01 0x40 | Action string format | + +### Action String Format + +Pattern: `07 01 40 ` + +Example: +``` +07 01 40 1C 00 47 53 43 20 56 69 65 77 65 72 43 6F 6E 6E 65 63 74 4C 69 76 65... +│ │ │ │ │ └─ "GSC ViewerConnectLive V <- C" +│ │ │ └──┴─ Length: 0x001C (28 bytes) +│ │ └─ 0x40 (action marker) +│ └─ 0x01 (subtype) +└─ 0x07 (string type) +``` + +### Sections Found + +From file analysis, sections include: +- **Alarms**: Alarm configurations +- **Clients**: Client connections +- **GeViIO**: Digital I/O configurations +- **Cameras**: Camera settings +- **Description**: Various descriptive entries +- **IpHost**: Network configurations +- **ActionMappings**: Trigger → Action rules (our focus) + +## JSON Schema + +### Complete Structure + +```json +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "Parser version" + }, + "header": { + "type": "string", + "description": "File header (GeViSoft Parameters)" + }, + "sections": { + "type": "array", + "items": { + "$ref": "#/definitions/Section" + } + } + }, + "definitions": { + "Section": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/ConfigItem" + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/ActionRule" + } + } + } + }, + "ConfigItem": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "oneOf": [ + { "type": "boolean" }, + { "type": "integer" }, + { "type": "string" } + ] + }, + "type": { + "enum": ["boolean", "integer", "string"] + } + } + }, + "ActionRule": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "triggers": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "mainAction": { + "type": "string" + }, + "variations": { + "type": "array", + "items": { + "$ref": "#/definitions/ActionVariation" + } + } + } + }, + "ActionVariation": { + "type": "object", + "properties": { + "platform": { + "enum": ["GSC", "GNG", "GCore"] + }, + "actionString": { + "type": "string" + }, + "serverType": { + "type": "string" + }, + "serverName": { + "type": "string" + } + } + } + } +} +``` + +### Example JSON Output + +```json +{ + "version": "1.0", + "header": "GeViSoft Parameters", + "sections": [ + { + "name": "ActionMappings", + "items": [], + "rules": [ + { + "id": 1, + "triggers": { + "InputContact": true, + "VideoInput": false + }, + "mainAction": "AlternateContact(2, 1000, 500)", + "variations": [ + { + "platform": "GSC", + "actionString": "GSC ViewerConnectLive V <- C_101027", + "serverType": "GeViScope", + "serverName": "GEVISCOPE" + }, + { + "platform": "GNG", + "actionString": "GNG PanLeft_101027", + "serverType": "", + "serverName": "" + } + ] + } + ] + }, + { + "name": "Alarms", + "items": [ + { + "key": "AlarmCount", + "value": 5, + "type": "integer" + }, + { + "key": "Enabled", + "value": true, + "type": "boolean" + } + ], + "rules": [] + } + ] +} +``` + +## Implementation Phases + +### Phase 1: Complete Binary Parser ✅ + +**Goal**: Parse entire .set file structure into memory + +**Components**: +- ✅ Header parser +- 🚧 Section parser (all types) +- 🚧 Item parser (all data types) +- 🚧 Rules parser (complete structure) +- 🚧 Action variation parser + +**Status**: Basic parser exists, needs enhancement for full structure + +### Phase 2: JSON Serialization 🚧 + +**Goal**: Convert parsed structure to JSON + +**Components**: +- JSON serializer +- Schema validator +- Round-trip tester (Binary → JSON → Binary) + +**Deliverables**: +- `SetFileToJson` converter +- JSON schema definition +- Validation tools + +### Phase 3: Excel Export 🚧 + +**Goal**: Export action mappings to Excel for editing + +**Components**: +- Excel writer (EPPlus library) +- Action mapping table generator +- Template with formulas/validation + +**Excel Structure**: +``` +Sheet: ActionMappings +| Rule ID | Trigger Type | Trigger Param | Action 1 | Action 2 | Action 3 | +|---------|--------------|---------------|----------|----------|----------| +| 1 | InputContact | 3, false | Alternate| Viewer | | +| 2 | VideoInput | 4, true | CrossSwi | VCChange | | +``` + +### Phase 4: Excel Import 🚧 + +**Goal**: Import edited Excel back to JSON + +**Components**: +- Excel reader +- Validation engine +- Diff generator (show changes) +- JSON merger + +### Phase 5: Binary Writer 🚧 + +**Goal**: Rebuild .set file from JSON + +**Components**: +- Binary writer +- Structure rebuilder +- Validation +- Backup mechanism + +**Critical**: Must maintain binary compatibility! + +### Phase 6: Testing & Validation 🚧 + +**Goal**: Ensure safety and correctness + +**Test Cases**: +1. Round-trip (Binary → JSON → Binary) = identical +2. Round-trip (Binary → JSON → Excel → JSON → Binary) = valid +3. Add new mapping → write → server accepts +4. Modify existing mapping → write → server accepts +5. Delete mapping → write → server accepts + +## Current Progress + +### Completed ✅ + +- [x] SetupClient API integration +- [x] Password encryption +- [x] Basic binary parsing (64 action mappings extracted) +- [x] Safe round-trip (byte-for-byte identical) +- [x] File structure analysis +- [x] Data type discovery + +### In Progress 🚧 + +- [ ] Complete section parsing +- [ ] Full rule structure parsing +- [ ] JSON serialization +- [ ] Excel export +- [ ] Binary writer for modifications + +### Pending 📋 + +- [ ] Excel import +- [ ] Add new mapping functionality +- [ ] API endpoints +- [ ] Documentation +- [ ] Production deployment + +## Technical Challenges + +### Challenge 1: Unknown Metadata Bytes + +**Problem**: Many byte sequences whose purpose is unknown + +**Solution**: +- Document all patterns found +- Test modifications to understand behavior +- Preserve unknown bytes during round-trip + +### Challenge 2: Complex Nested Structure + +**Problem**: Sections contain items and rules, rules contain variations + +**Solution**: +- Recursive parsing +- Clear data model hierarchy +- Offset tracking for debugging + +### Challenge 3: Binary Format Changes + +**Problem**: Format may vary between GeViSoft versions + +**Solution**: +- Version detection +- Support multiple format versions +- Graceful degradation + +### Challenge 4: Action String Syntax + +**Problem**: Action strings have complex syntax (parameters, types, etc.) + +**Solution**: +- Pattern matching +- Action string parser +- Validation against known action types + +## Safety Considerations + +### Before Writing to Server + +1. ✅ **Verify round-trip**: Parse → Write → Compare = Identical +2. ✅ **Backup original**: Always keep copy of working config +3. ⚠️ **Test in dev**: Never test on production first +4. ⚠️ **Validate structure**: Check against schema +5. ⚠️ **Incremental changes**: Small changes, test frequently + +### Error Handling + +- Validate before write +- Provide detailed error messages +- Support rollback +- Log all operations + +## Tools & Libraries + +### Development + +- **Language**: C# / .NET 8.0 +- **Binary Parsing**: Custom binary reader +- **JSON**: System.Text.Json +- **Excel**: EPPlus (for .xlsx) +- **Testing**: xUnit +- **Logging**: Serilog + +### Project Structure + +``` +GeViSetEditor/ +├── GeViSetEditor.Core/ +│ ├── Models/ +│ │ ├── SetFileStructure.cs +│ │ ├── Section.cs +│ │ ├── ConfigItem.cs +│ │ ├── ActionRule.cs +│ │ └── ActionVariation.cs +│ ├── Parsers/ +│ │ ├── SetFileBinaryParser.cs +│ │ ├── SectionParser.cs +│ │ └── RuleParser.cs +│ ├── Writers/ +│ │ ├── SetFileBinaryWriter.cs +│ │ └── JsonWriter.cs +│ ├── Converters/ +│ │ ├── JsonToExcel.cs +│ │ └── ExcelToJson.cs +│ └── Validators/ +│ └── StructureValidator.cs +├── GeViSetEditor.CLI/ +│ └── Commands/ +│ ├── ParseCommand.cs +│ ├── ToJsonCommand.cs +│ ├── ToExcelCommand.cs +│ └── FromExcelCommand.cs +└── GeViSetEditor.Tests/ + ├── ParserTests.cs + ├── RoundTripTests.cs + └── ValidationTests.cs +``` + +## Next Steps + +### Immediate (This Session) + +1. ✅ Create specification document +2. ✅ Update git repository +3. 🚧 Implement complete binary parser +4. 🚧 Implement JSON serialization +5. 🚧 Test round-trip with JSON + +### Short Term (Next Session) + +1. Excel export implementation +2. Excel import implementation +3. Add new mapping functionality +4. Comprehensive testing + +### Long Term + +1. Web UI for configuration management +2. API endpoints +3. Multi-version support +4. Documentation and examples + +## References + +- GeViSoft SDK Documentation +- SetupClient API Reference +- Existing .set file samples (TestMKS.set, setup_config_*.dat) +- Binary analysis notes +- Round-trip test results + +## Version History + +| Version | Date | Changes | +|---------|------------|--------------------------------------| +| 1.0 | 2024-12-12 | Initial specification | + +--- + +**Status**: Ready for full implementation +**Priority**: High +**Complexity**: High +**Timeline**: 2-3 days estimated diff --git a/src/sdk-bridge/DiagnoseSetupClient/DiagnoseSetupClient.csproj b/src/sdk-bridge/DiagnoseSetupClient/DiagnoseSetupClient.csproj new file mode 100644 index 0000000..38b200d --- /dev/null +++ b/src/sdk-bridge/DiagnoseSetupClient/DiagnoseSetupClient.csproj @@ -0,0 +1,35 @@ + + + + Exe + net8.0 + enable + enable + x86 + x86 + + + + + + + + + + + + + + C:\GEVISOFT\GeViProcAPINET_4_0.dll + + + + + + + + + + + + diff --git a/src/sdk-bridge/DiagnoseSetupClient/Program.cs b/src/sdk-bridge/DiagnoseSetupClient/Program.cs new file mode 100644 index 0000000..fa37817 --- /dev/null +++ b/src/sdk-bridge/DiagnoseSetupClient/Program.cs @@ -0,0 +1,268 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Serilog; +using GeViScopeBridge.SDK; + +namespace DiagnoseSetupClient +{ + class Program + { + static async Task Main(string[] args) + { + // Configure logging + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + .CreateLogger(); + + try + { + Console.WriteLine("=== GeViSetupClient Diagnostic Tool ===\n"); + + // Get connection details from command line or interactive + string address, username, password; + + if (args.Length >= 3) + { + // Command line mode: DiagnoseSetupClient.exe
+ address = args[0]; + username = args[1]; + password = args[2]; + Console.WriteLine($"Using command-line arguments:"); + Console.WriteLine($" Address: {address}"); + Console.WriteLine($" Username: {username}"); + Console.WriteLine($" Password: {new string('*', password.Length)}"); + } + else + { + // Interactive mode + Console.Write("GeViServer Address (default: localhost): "); + address = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(address)) + address = "localhost"; + + Console.Write("Username (default: admin): "); + username = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(username)) + username = "admin"; + + Console.Write("Password: "); + password = ReadPassword(); + } + + Console.WriteLine("\n\n1. Testing SetupClient Connection..."); + + // Try with different aliasnames + string[] aliasnamesToTry = { "", "localhost", "GeViServer", address }; + + GeViSetupClientWrapper successfulClient = null; + + foreach (var aliasname in aliasnamesToTry) + { + Console.WriteLine($"Trying with aliasname: '{aliasname}'"); + var setupClient = new GeViSetupClientWrapper(address, username, password, aliasname); + bool connected = await setupClient.ConnectAsync(); + + if (connected) + { + Console.WriteLine($"✅ Connected successfully with aliasname: '{aliasname}'!\n"); + successfulClient = setupClient; + break; + } + else + { + setupClient.Dispose(); + } + } + + if (successfulClient == null) + { + Console.WriteLine("❌ Failed to connect with any aliasname"); + return; + } + + // Use the successfully connected client + using (var setupClient = successfulClient) + { + + // Test ping + Console.WriteLine("2. Testing Ping..."); + bool pingResult = setupClient.SendPing(); + Console.WriteLine(pingResult ? "✅ Ping successful" : "❌ Ping failed"); + Console.WriteLine(); + + // Read setup configuration + Console.WriteLine("3. Reading Setup Configuration..."); + byte[] setupData = await setupClient.ReadSetupAsync(); + + Console.WriteLine($"✅ Read {setupData.Length} bytes of configuration\n"); + + // Save to file for inspection + string outputFile = Path.Combine( + Environment.CurrentDirectory, + $"setup_config_{DateTime.Now:yyyyMMdd_HHmmss}.dat" + ); + + File.WriteAllBytes(outputFile, setupData); + Console.WriteLine($"📁 Saved configuration to: {outputFile}\n"); + + // Analyze file format + Console.WriteLine("4. Analyzing File Format..."); + AnalyzeSetupFile(setupData); + + Console.WriteLine("\n5. Testing Write Setup (write back unchanged)..."); + + // In automated mode, skip write test by default + string response = "n"; + if (args.Length < 3) + { + Console.Write("Write configuration back to server? (y/n): "); + response = Console.ReadLine(); + } + else + { + Console.WriteLine("Skipping write test in automated mode (pass 4th argument 'y' to enable)"); + if (args.Length >= 4 && args[3].ToLower() == "y") + { + response = "y"; + } + } + + if (response?.ToLower() == "y") + { + bool writeSuccess = await setupClient.WriteSetupAsync(setupData); + Console.WriteLine(writeSuccess + ? "✅ Configuration written successfully" + : "❌ Failed to write configuration"); + } + else + { + Console.WriteLine("⏭️ Skipped write test"); + } + } + + Console.WriteLine("\n✅ All tests completed successfully!"); + } + catch (Exception ex) + { + Console.WriteLine($"\n❌ Error: {ex.Message}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + } + finally + { + Log.CloseAndFlush(); + } + + // Only wait for key if in interactive mode (not automated) + if (args.Length < 3) + { + Console.WriteLine("\nPress any key to exit..."); + try + { + Console.ReadKey(); + } + catch + { + // Ignore if console input is redirected + } + } + } + + static void AnalyzeSetupFile(byte[] data) + { + // Check if XML + if (data.Length > 5) + { + string header = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length)); + + if (header.StartsWith("= 32 && b < 127 ? (char)b : '.'); + } + + Console.WriteLine(); + } + } + + static string ReadPassword() + { + string password = ""; + ConsoleKeyInfo key; + + do + { + key = Console.ReadKey(true); + + if (key.Key == ConsoleKey.Backspace && password.Length > 0) + { + password = password.Substring(0, password.Length - 1); + Console.Write("\b \b"); + } + else if (key.Key != ConsoleKey.Enter && key.KeyChar != '\0') + { + password += key.KeyChar; + Console.Write("*"); + } + } while (key.Key != ConsoleKey.Enter); + + return password; + } + } +} diff --git a/src/sdk-bridge/GeViScopeBridge/SDK/GeViSetupClientWrapper.cs b/src/sdk-bridge/GeViScopeBridge/SDK/GeViSetupClientWrapper.cs new file mode 100644 index 0000000..1087093 --- /dev/null +++ b/src/sdk-bridge/GeViScopeBridge/SDK/GeViSetupClientWrapper.cs @@ -0,0 +1,391 @@ +using System; +using System.Runtime.InteropServices; +using System.IO; +using System.Threading.Tasks; +using Serilog; +using Microsoft.Win32.SafeHandles; + +namespace GeViScopeBridge.SDK +{ + /// + /// P/Invoke wrapper for GeViAPI SetupClient functions + /// This is what GeViSet uses to read/write configuration from/to GeViServer + /// + public class GeViSetupClientWrapper : IDisposable + { + private IntPtr _setupClientHandle = IntPtr.Zero; + private bool _isConnected = false; + private readonly ILogger _logger; + + // Connection parameters + private readonly string _aliasname; + private readonly string _address; + private readonly string _username; + private readonly string _password; + + #region P/Invoke Declarations + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + private static extern bool GeViAPI_SetupClient_Create( + out IntPtr setupClient, + [MarshalAs(UnmanagedType.LPStr)] string aliasname, + [MarshalAs(UnmanagedType.LPStr)] string address, + [MarshalAs(UnmanagedType.LPStr)] string username, + [MarshalAs(UnmanagedType.LPStr)] string password, + [MarshalAs(UnmanagedType.LPStr)] string username2, + [MarshalAs(UnmanagedType.LPStr)] string password2 + ); + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool GeViAPI_SetupClient_Connect( + IntPtr setupClient, + out int connectResult, + IntPtr callback, // TGeViConnectProgress callback (can be IntPtr.Zero) + IntPtr instance // void* instance (can be IntPtr.Zero) + ); + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool GeViAPI_SetupClient_Disconnect(IntPtr setupClient); + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool GeViAPI_SetupClient_Destroy(IntPtr setupClient); + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool GeViAPI_SetupClient_ReadSetup( + IntPtr setupClient, + IntPtr hFile // File handle from CreateFile + ); + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool GeViAPI_SetupClient_WriteSetup( + IntPtr setupClient, + IntPtr hFile // File handle from CreateFile + ); + + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool GeViAPI_SetupClient_SendPing(IntPtr setupClient); + + // Windows API for file operations + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile + ); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + // Password encoding function + [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + private static extern void GeViAPI_EncodeString( + [MarshalAs(UnmanagedType.LPStr)] System.Text.StringBuilder output, + [MarshalAs(UnmanagedType.LPStr)] string input, + int size + ); + + // File access constants + private const uint GENERIC_READ = 0x80000000; + private const uint GENERIC_WRITE = 0x40000000; + private const uint CREATE_ALWAYS = 2; + private const uint OPEN_EXISTING = 3; + private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + + #endregion + + public GeViSetupClientWrapper(string address, string username, string password, string aliasname = "") + { + _address = address ?? throw new ArgumentNullException(nameof(address)); + _username = username ?? throw new ArgumentNullException(nameof(username)); + _password = password ?? throw new ArgumentNullException(nameof(password)); + _aliasname = aliasname ?? ""; + _logger = Log.ForContext(); + } + + /// + /// Connect to GeViServer + /// + public async Task ConnectAsync() + { + return await Task.Run(() => Connect()); + } + + private bool Connect() + { + try + { + _logger.Information("Creating SetupClient for {Address}", _address); + + // Encrypt password using GeViAPI_EncodeString + // Password buffer should be at least 256 bytes according to typical SDK usage + var encodedPassword = new System.Text.StringBuilder(256); + GeViAPI_EncodeString(encodedPassword, _password, encodedPassword.Capacity); + + _logger.Debug("Password encrypted for SetupClient connection"); + + // Create SetupClient with encrypted password + bool created = GeViAPI_SetupClient_Create( + out _setupClientHandle, + _aliasname, + _address, + _username, + encodedPassword.ToString(), // Use encrypted password + "", // username2 (optional, for dual control) + "" // password2 (optional) + ); + + if (!created || _setupClientHandle == IntPtr.Zero) + { + _logger.Error("Failed to create SetupClient"); + return false; + } + + _logger.Information("SetupClient created, connecting to {Address}", _address); + + // Connect to server + bool connected = GeViAPI_SetupClient_Connect( + _setupClientHandle, + out int connectResult, + IntPtr.Zero, // No progress callback + IntPtr.Zero // No instance + ); + + if (!connected || connectResult != 0) + { + string errorName = GetConnectResultName(connectResult); + _logger.Error("Failed to connect SetupClient. Result: {Result} ({ErrorName})", connectResult, errorName); + return false; + } + + _isConnected = true; + _logger.Information("SetupClient connected successfully to {Address}", _address); + return true; + } + catch (Exception ex) + { + _logger.Error(ex, "Exception during SetupClient connection"); + return false; + } + } + + /// + /// Read complete setup configuration from GeViServer to a file + /// + public async Task ReadSetupAsync() + { + return await Task.Run(() => ReadSetup()); + } + + private byte[] ReadSetup() + { + if (!_isConnected || _setupClientHandle == IntPtr.Zero) + { + throw new InvalidOperationException("SetupClient is not connected"); + } + + string tempFile = Path.GetTempFileName(); + + try + { + _logger.Information("Reading setup configuration from GeViServer to {TempFile}", tempFile); + + // Create file handle for writing + IntPtr hFile = CreateFile( + tempFile, + GENERIC_WRITE, + 0, // No sharing + IntPtr.Zero, + CREATE_ALWAYS, + 0, + IntPtr.Zero + ); + + if (hFile == INVALID_HANDLE_VALUE) + { + int error = Marshal.GetLastWin32Error(); + throw new IOException($"Failed to create temp file. Error: {error}"); + } + + try + { + // Read setup from server + bool success = GeViAPI_SetupClient_ReadSetup(_setupClientHandle, hFile); + + if (!success) + { + throw new InvalidOperationException("Failed to read setup from GeViServer"); + } + + _logger.Information("Setup configuration read successfully"); + } + finally + { + CloseHandle(hFile); + } + + // Read file contents + byte[] data = File.ReadAllBytes(tempFile); + _logger.Information("Read {Size} bytes of setup configuration", data.Length); + return data; + } + finally + { + // Clean up temp file + if (File.Exists(tempFile)) + { + try + { + File.Delete(tempFile); + } + catch (Exception ex) + { + _logger.Warning(ex, "Failed to delete temp file {TempFile}", tempFile); + } + } + } + } + + /// + /// Write setup configuration back to GeViServer from a byte array + /// + public async Task WriteSetupAsync(byte[] setupData) + { + return await Task.Run(() => WriteSetup(setupData)); + } + + private bool WriteSetup(byte[] setupData) + { + if (!_isConnected || _setupClientHandle == IntPtr.Zero) + { + throw new InvalidOperationException("SetupClient is not connected"); + } + + if (setupData == null || setupData.Length == 0) + { + throw new ArgumentException("Setup data cannot be null or empty", nameof(setupData)); + } + + string tempFile = Path.GetTempFileName(); + + try + { + _logger.Information("Writing {Size} bytes of setup configuration to GeViServer", setupData.Length); + + // Write data to temp file + File.WriteAllBytes(tempFile, setupData); + + // Open file handle for reading + IntPtr hFile = CreateFile( + tempFile, + GENERIC_READ, + 0, // No sharing + IntPtr.Zero, + OPEN_EXISTING, + 0, + IntPtr.Zero + ); + + if (hFile == INVALID_HANDLE_VALUE) + { + int error = Marshal.GetLastWin32Error(); + throw new IOException($"Failed to open temp file. Error: {error}"); + } + + try + { + // Write setup to server + bool success = GeViAPI_SetupClient_WriteSetup(_setupClientHandle, hFile); + + if (!success) + { + _logger.Error("Failed to write setup to GeViServer"); + return false; + } + + _logger.Information("Setup configuration written successfully to GeViServer"); + return true; + } + finally + { + CloseHandle(hFile); + } + } + finally + { + // Clean up temp file + if (File.Exists(tempFile)) + { + try + { + File.Delete(tempFile); + } + catch (Exception ex) + { + _logger.Warning(ex, "Failed to delete temp file {TempFile}", tempFile); + } + } + } + } + + /// + /// Send ping to keep connection alive + /// + public bool SendPing() + { + if (!_isConnected || _setupClientHandle == IntPtr.Zero) + { + return false; + } + + return GeViAPI_SetupClient_SendPing(_setupClientHandle); + } + + /// + /// Disconnect from GeViServer + /// + public void Disconnect() + { + if (_isConnected && _setupClientHandle != IntPtr.Zero) + { + _logger.Information("Disconnecting SetupClient"); + GeViAPI_SetupClient_Disconnect(_setupClientHandle); + _isConnected = false; + } + } + + public void Dispose() + { + Disconnect(); + + if (_setupClientHandle != IntPtr.Zero) + { + _logger.Information("Destroying SetupClient"); + GeViAPI_SetupClient_Destroy(_setupClientHandle); + _setupClientHandle = IntPtr.Zero; + } + } + + private static string GetConnectResultName(int result) + { + return result switch + { + 0 => "connectOk", + 100 => "connectAborted", + 101 => "connectGenericError", + 300 => "connectRemoteUnknownError", + 301 => "connectRemoteTcpError", + 302 => "connectRemoteUnknownUser", + 303 => "connectRemoteConnectionLimitExceeded", + 304 => "connectRemoteClientInterfaceTooOld", + 305 => "connectRemoteServerInterfaceTooOld", + 306 => "connectRemoteSecondUserRequired", + 307 => "connectRemotePortDisabled", + _ => $"Unknown({result})" + }; + } + } +}