# CLAUDE.md — OPC UA Discovery Project ## Project Overview C project exploring OPC UA discovery using the open62541 library (git submodule at `deps/open62541`). CMake build system (C11). Source files live in `src/`, certificates in `certs/`, helper scripts in `tools/`. ## Build ```sh cmake --build build --parallel ``` If the build directory doesn't exist yet or dependencies need reconfiguring: ```sh cmake -B build cmake --build build --parallel ``` open62541 is built automatically via `cmake/BuildDeps.cmake` with `UA_ENABLE_ENCRYPTION=OPENSSL` and `UA_ENABLE_DISCOVERY=ON`, installed to `build/deps/open62541-install`. The open62541 build directory is `build/deps/open62541`. After building, verify that `compile_commands.json` is symlinked from `build/` to the project root. ### Building Documentation To build the open62541 HTML documentation (requires `python3-sphinx`, `python3-sphinx-rtd-theme`, `graphviz`): ```sh cmake -B build -DBUILD_DOC=ON cmake --build build --target doc ``` Output goes to `build/deps/open62541/doc/index.html`. ## Code Style - Follow the `.clang-format` file in the project root (GNU-based style). - Key points: 2-space indent, braces on own line (`BreakBeforeBraces: GNU`), space before parens (`SpaceBeforeParens: Always`), pointer star on the right (`PointerAlignment: Right`), 79-column limit, return type on its own line for definitions. - Do **not** reformat code you didn't change. ## Workflow Preferences - **Ask before committing.** Never commit without explicit confirmation. - **Ask when ambiguous.** If a task or requirement is unclear, ask rather than guess. - **Plan mode:** Ask before entering plan mode — don't assume it's needed. - **Validation:** After making changes, confirm the project compiles with `cmake --build build --parallel`. When asked to run or test, start all three programs (LDS, ServerRegister, ClientFindServers) using the commands in the **Running** section below, then verify the client output. - **Verbosity:** Give detailed explanations of what was done and why. ## Certificates Generate DER certificates with `tools/generate_certificate.sh `. This creates `_cert.der` and `_key.der` in the given directory. Four identities are needed: ```sh tools/generate_certificate.sh certs ServerLDS tools/generate_certificate.sh certs ServerRegister tools/generate_certificate.sh certs ServerRegisterClient tools/generate_certificate.sh certs ClientFindServers ``` Existing certs live in `certs/`. Only regenerate if missing. ## Running Each program takes a single argument: a configuration file. Example config files are in `config/`. Start programs in order in separate terminals from the project root: **1. Local Discovery Server (LDS)** ```sh build/ServerLDS config/server_lds.conf ``` **2. Register Server** ```sh build/ServerRegister config/server_register.conf ``` **3. Find Servers Client** ```sh build/ClientFindServers config/client_find_servers.conf ``` ## Configuration Files Config files use a simple `key = value` format, one entry per line. Lines starting with `#` are comments. Blank lines are ignored. Repeated keys are used for list values (e.g. multiple `trustList` entries). ### ServerLDS Keys | Key | Required | Description | |-----|----------|-------------| | `port` | yes | Server port number | | `applicationUri` | yes | OPC UA application URI | | `certificate` | yes | Path to server certificate (.der) | | `privateKey` | yes | Path to server private key (.der) | | `cleanupTimeout` | yes | Seconds before stale registrations are removed (must be > 10) | | `authMode` | yes | `anonymous` or `user` | | `username` | if user | Username for authentication | | `password` | if user | Password for authentication | | `trustList` | no | Trusted certificate path (repeat for multiple) | ### ServerRegister Keys | Key | Required | Description | |-----|----------|-------------| | `port` | yes | Server port number | | `applicationUri` | yes | OPC UA application URI | | `serverCertificate` | yes | Path to server certificate (.der) | | `serverPrivateKey` | yes | Path to server private key (.der) | | `clientCertificate` | yes | Path to client certificate for LDS connection (.der) | | `clientPrivateKey` | yes | Path to client private key for LDS connection (.der) | | `discoveryEndpoint` | yes | LDS endpoint URL (e.g. `opc.tcp://localhost:4840`) | | `registerInterval` | yes | Seconds between re-registrations | | `securityMode` | yes | `None`, `Sign`, or `SignAndEncrypt` | | `securityPolicy` | yes | `None`, `Basic256Sha256`, `Aes256_Sha256_RsaPss`, `Aes128_Sha256_RsaOaep`, or `ECC_nistP256` | | `serverAuthMode` | yes | Auth for clients connecting to this server: `anonymous` or `user` | | `serverUsername` | if user | Server-side username | | `serverPassword` | if user | Server-side password | | `clientAuthMode` | yes | Auth for LDS connection: `anonymous` or `user` | | `clientUsername` | if user | Client-side username (for LDS) | | `clientPassword` | if user | Client-side password (for LDS) | | `trustList` | no | Trusted certificate path (repeat for multiple) | ### ClientFindServers Keys | Key | Required | Description | |-----|----------|-------------| | `discoveryEndpoint` | yes | LDS endpoint URL (e.g. `opc.tcp://localhost:4840`) | | `applicationUri` | yes | OPC UA application URI | | `certificate` | yes | Path to client certificate (.der) | | `privateKey` | yes | Path to client private key (.der) | | `securityMode` | yes | `None`, `Sign`, or `SignAndEncrypt` | | `securityPolicy` | yes | `None`, `Basic256Sha256`, `Aes256_Sha256_RsaPss`, `Aes128_Sha256_RsaOaep`, or `ECC_nistP256` | | `authMode` | yes | `anonymous` or `user` | | `username` | if user | Username for session auth | | `password` | if user | Password for session auth | | `trustList` | no | Trusted certificate path (repeat for multiple) | ## Testing Integration tests use CTest and cover 6 combinations of security mode/policy and authentication: | Test | Security | Auth | |------|----------|------| | `none_anon` | None / None | anonymous | | `none_user` | None / None | user | | `basic256sha256_anon` | SignAndEncrypt / Basic256Sha256 | anonymous | | `basic256sha256_user` | SignAndEncrypt / Basic256Sha256 | user | | `aes128_anon` | SignAndEncrypt / Aes128_Sha256_RsaOaep | anonymous | | `aes128_user` | SignAndEncrypt / Aes128_Sha256_RsaOaep | user | Run all tests: ```sh ctest --test-dir build --output-on-failure ``` Each test starts ServerLDS (port 14840) and ServerRegister (port 14841), runs ClientFindServers, then validates: 1. Client exits with code 0 2. FindServers output contains `urn:bobink.ServerRegister` 3. Client read the current time (`date is:`) 4. Endpoint listing contains the expected security policy To add a new test case: create a directory under `tests/` with 3 config files (`server_lds.conf`, `server_register.conf`, `client_find_servers.conf`), then add a `"name;ExpectedPolicy"` entry to the `INTEGRATION_TESTS` list in `CMakeLists.txt`. ## Project Structure | Path | Purpose | |------|---------| | `src/common.h` / `src/common.c` | Shared helpers: `loadFile()`, `createSecureServer()`, `createSecureClientConfig()`, `parseSecurityMode()`, `resolveSecurityPolicyUri()` | | `src/config.h` / `src/config.c` | Config file parser: `configLoad()`, `configGet()`, `configRequire()`, `configRequireInt()`, `configGetAll()`, `configFree()` | | `src/server_lds.c` | Local Discovery Server | | `src/server_register.c` | Server that registers with LDS | | `src/client_find_servers.c` | Client that queries LDS and displays endpoints | | `config/` | Example configuration files for all three programs | | `tests/` | Integration tests: `run_test.sh` helper and per-test config directories | | `certs/` | TLS certificates for server, client, and LDS | | `tools/` | Helper scripts | | `cmake/BuildDeps.cmake` | Configures, builds, and installs open62541 | | `deps/open62541` | open62541 git submodule |